--- title: Quick Start sidebar_position: 2 --- # Quick Start This guide walks you through creating your first Superset extension - a simple "Hello World" panel that displays a message fetched from a backend API endpoint. You'll learn the essential structure and patterns for building full-stack Superset extensions. ## Prerequisites Before starting, ensure you have: - Node.js and npm compatible with your Superset version - Python compatible with your Superset version - A running Superset development environment - Basic knowledge of React, TypeScript, and Flask ## Step 1: Install the Extensions CLI First, install the Apache Superset Extensions CLI: ```bash pip install apache-superset-extensions-cli ``` ## Step 2: Create a New Extension Use the CLI to scaffold a new extension project. Extensions can include frontend functionality, backend functionality, or both, depending on your needs. This quickstart demonstrates a full-stack extension with both frontend UI components and backend API endpoints to show the complete integration pattern. ```bash superset-extensions init ``` The CLI will prompt you for information using a three-step publisher workflow: ``` Extension display name: Hello World Extension name (hello-world): hello-world Publisher (e.g., my-org): my-org Initial version [0.1.0]: 0.1.0 License [Apache-2.0]: Apache-2.0 Include frontend? [Y/n]: Y Include backend? [Y/n]: Y ``` **Publisher Namespaces**: Extensions use organizational namespaces similar to VS Code extensions, providing collision-safe naming across organizations: - **NPM package**: `@my-org/hello-world` (scoped package for frontend distribution) - **Module Federation name**: `myOrg_helloWorld` (collision-safe JavaScript identifier) - **Backend package**: `my_org-hello_world` (collision-safe Python distribution name) - **Python namespace**: `superset_extensions.my_org.hello_world` This approach ensures that extensions from different organizations cannot conflict, even if they use the same technical name (e.g., both `acme.dashboard-widgets` and `corp.dashboard-widgets` can coexist). This creates a complete project structure: ``` my-org.hello-world/ ├── extension.json # Extension metadata and configuration ├── backend/ # Backend Python code │ ├── src/ │ │ └── superset_extensions/ │ │ └── my_org/ │ │ └── hello_world/ │ │ └── entrypoint.py # Backend registration │ └── pyproject.toml └── frontend/ # Frontend TypeScript/React code ├── src/ │ └── index.tsx # Frontend entry point ├── package.json ├── tsconfig.json └── webpack.config.js ``` ## Step 3: Configure Extension Metadata The generated `extension.json` contains the extension's metadata. ```json { "publisher": "my-org", "name": "hello-world", "displayName": "Hello World", "version": "0.1.0", "license": "Apache-2.0", "permissions": ["can_read"] } ``` **Key fields:** - `publisher`: Organizational namespace for the extension - `name`: Technical identifier (kebab-case) - `displayName`: Human-readable name shown to users - `permissions`: List of permissions the extension requires ## Step 4: Create Backend API The CLI generated a basic `backend/src/superset_extensions/my_org/hello_world/entrypoint.py`. We'll create an API endpoint. **Create `backend/src/superset_extensions/my_org/hello_world/api.py`** ```python from flask import Response from flask_appbuilder.api import expose, protect, safe from superset_core.rest_api.api import RestApi from superset_core.rest_api.decorators import api @api( id="hello_world_api", name="Hello World API", description="API endpoints for the Hello World extension" ) class HelloWorldAPI(RestApi): openapi_spec_tag = "Hello World" class_permission_name = "hello_world" @expose("/message", methods=("GET",)) @protect() @safe def get_message(self) -> Response: """Gets a hello world message --- get: description: >- Get a hello world message from the backend responses: 200: description: Hello world message content: application/json: schema: type: object properties: result: type: object properties: message: type: string 401: $ref: '#/components/responses/401' """ return self.response( 200, result={"message": "Hello from the backend!"} ) ``` **Key points:** - Uses [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator with automatic context detection - Extends `RestApi` from `superset_core.rest_api.api` - Uses Flask-AppBuilder decorators (`@expose`, `@protect`, `@safe`) - Returns responses using `self.response(status_code, result=data)` - The endpoint will be accessible at `/extensions/my-org/hello-world/message` (automatic extension context) - OpenAPI docstrings are crucial - Flask-AppBuilder uses them to automatically generate interactive API documentation at `/swagger/v1`, allowing developers to explore endpoints, understand schemas, and test the API directly from the browser **Update `backend/src/superset_extensions/my_org/hello_world/entrypoint.py`** Replace the generated print statement with API import to trigger registration: ```python # Importing the API class triggers the @api decorator registration from .api import HelloWorldAPI # noqa: F401 ``` The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects extension context and registers your API with proper namespacing. ## Step 5: Create Frontend Component The CLI generates the frontend configuration files. Below are the key configurations that enable Module Federation integration with Superset. **`frontend/package.json`** The `@apache-superset/core` package must be listed in both `peerDependencies` (to declare runtime compatibility) and `devDependencies` (to provide TypeScript types during build): ```json { "name": "@my-org/hello-world", "version": "0.1.0", "private": true, "license": "Apache-2.0", "scripts": { "start": "webpack serve --mode development", "build": "webpack --stats-error-details --mode production" }, "peerDependencies": { "@apache-superset/core": "^x.x.x", "react": "^x.x.x", "react-dom": "^x.x.x" }, "devDependencies": { "@apache-superset/core": "^x.x.x", "@types/react": "^x.x.x", "ts-loader": "^x.x.x", "typescript": "^x.x.x", "webpack": "^5.x.x", "webpack-cli": "^x.x.x", "webpack-dev-server": "^x.x.x" } } ``` **`frontend/webpack.config.js`** The webpack configuration requires specific settings for Module Federation. Key settings include `externalsType: "window"` and `externals` to map `@apache-superset/core` to `window.superset` at runtime, `import: false` for shared modules to use the host's React instead of bundling a separate copy, and `remoteEntry.[contenthash].js` for cache busting. **Convention**: Superset always loads extensions by requesting the `./index` module from the Module Federation container. The `exposes` entry must be exactly `'./index': './src/index.tsx'` — do not rename or add additional entries. All API registrations must be reachable from that file. See [Architecture](./architecture#module-federation) for a full explanation. ```javascript const path = require('path'); const { ModuleFederationPlugin } = require('webpack').container; const packageConfig = require('./package'); const extensionConfig = require('../extension.json'); module.exports = (env, argv) => { const isProd = argv.mode === 'production'; return { entry: isProd ? {} : './src/index.tsx', mode: isProd ? 'production' : 'development', devServer: { port: 3000, headers: { 'Access-Control-Allow-Origin': '*', }, }, output: { clean: true, filename: isProd ? undefined : '[name].[contenthash].js', chunkFilename: '[name].[contenthash].js', path: path.resolve(__dirname, 'dist'), publicPath: `/api/v1/extensions/${extensionConfig.publisher}/${extensionConfig.name}/`, }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'], }, // Map @apache-superset/core imports to window.superset at runtime externalsType: 'window', externals: { '@apache-superset/core': 'superset', }, module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'myOrg_helloWorld', filename: 'remoteEntry.[contenthash].js', exposes: { './index': './src/index.tsx', }, shared: { react: { singleton: true, requiredVersion: packageConfig.peerDependencies.react, import: false, // Use host's React, don't bundle }, 'react-dom': { singleton: true, requiredVersion: packageConfig.peerDependencies['react-dom'], import: false, }, antd: { singleton: true, requiredVersion: packageConfig.peerDependencies['antd'], import: false, }, }, }), ], }; }; ``` **`frontend/tsconfig.json`** ```json { "compilerOptions": { "target": "es5", "module": "esnext", "moduleResolution": "node10", "jsx": "react", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src"] } ``` **Create `frontend/src/HelloWorldPanel.tsx`** Create a new file for the component implementation: ```tsx import React, { useEffect, useState } from 'react'; import { authentication } from '@apache-superset/core'; const HelloWorldPanel: React.FC = () => { const [message, setMessage] = useState(''); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { const fetchMessage = async () => { try { const csrfToken = await authentication.getCSRFToken(); const response = await fetch('/extensions/my-org/hello-world/message', { method: 'GET', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken!, }, }); if (!response.ok) { throw new Error(`Server returned ${response.status}`); } const data = await response.json(); setMessage(data.result.message); } catch (err) { setError(err instanceof Error ? err.message : 'An error occurred'); } finally { setLoading(false); } }; fetchMessage(); }, []); if (loading) { return (

Loading...

); } if (error) { return (
Error: {error}
); } return (

Hello World Extension

{message}

This message was fetched from the backend API! 🎉

); }; export default HelloWorldPanel; ``` **Update `frontend/src/index.tsx`** This file is the single entry point Superset loads from every extension. All registrations — views, commands, menus, editors, event listeners — must be made here (or imported and executed from here). Replace the generated code with: ```tsx import React from 'react'; import { views } from '@apache-superset/core'; import HelloWorldPanel from './HelloWorldPanel'; views.registerView( { id: 'my-org.hello-world.main', name: 'Hello World' }, 'sqllab.panels', () => , ); ``` **Key patterns:** - `views.registerView` is called at module load time — no `activate`/`deactivate` lifecycle needed - The first argument is a `{ id, name }` descriptor; the second is the contribution area (e.g., `sqllab.panels`); the third is a factory returning the React component - `authentication.getCSRFToken()` retrieves the CSRF token for API calls (used inside components) - Fetch calls to `/extensions/{publisher}/{name}/{endpoint}` reach your backend API ## Step 6: Install Dependencies Install the frontend dependencies: ```bash cd frontend npm install cd .. ``` ## Step 7: Package the Extension Create a `.supx` bundle for deployment: ```bash superset-extensions bundle ``` This command automatically: - Builds frontend assets using Webpack with Module Federation - Collects backend Python source files - Creates a `dist/` directory with: - `manifest.json` - Build metadata and asset references - `frontend/dist/` - Built frontend assets (remoteEntry.js, chunks) - `backend/` - Python source files - Packages everything into `my-org.hello-world-0.1.0.supx` - a zip archive with the specific structure required by Superset ## Step 8: Deploy to Superset To deploy your extension, you need to enable extensions support and configure where Superset should load them from. **Configure Superset** Add the following to your `superset_config.py`: ```python # Enable extensions feature FEATURE_FLAGS = { "ENABLE_EXTENSIONS": True, } # Set the directory where extensions are stored EXTENSIONS_PATH = "/path/to/extensions/folder" ``` **Copy Extension Bundle** Copy your `.supx` file to the configured extensions path: ```bash cp my-org.hello-world-0.1.0.supx /path/to/extensions/folder/ ``` **Restart Superset** Restart your Superset instance to load the extension: ```bash # Restart your Superset server superset run ``` Superset will extract and validate the extension metadata, load the assets, register the extension with its capabilities, and make it available for use. ## Step 9: Test Your Extension 1. **Open SQL Lab** in Superset 2. Look for the **"Hello World"** panel in the panels dropdown or sidebar 3. Open the panel - it should display "Hello from the backend!" 4. Check that the message was fetched from your API endpoint ## Understanding the Flow Here's what happens when your extension loads: 1. **Superset starts**: Reads `manifest.json` from the `.supx` bundle and loads the backend entrypoint 2. **Backend registration**: `entrypoint.py` imports your API class, triggering the [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator to register it automatically 3. **Frontend loads**: When SQL Lab opens, Superset fetches the remote entry file 4. **Module Federation**: Webpack loads your extension module and resolves `@apache-superset/core` to `window.superset` 5. **Registration**: The module executes at load time, calling `views.registerView` to register your panel 6. **Rendering**: When the user opens your panel, React renders `` 7. **API call**: The component fetches data from `/extensions/my-org/hello-world/message` 8. **Backend response**: Your Flask API returns the hello world message 9. **Display**: The component shows the message to the user ## Next Steps Now that you have a working extension, explore: - **[Development](./development)** - Project structure, APIs, and development workflow - **[Contribution Types](./contribution-types)** - Other contribution points beyond panels - **[Deployment](./deployment)** - Packaging and deploying your extension - **[Security](./security)** - Security best practices for extensions For a complete real-world example, examine the query insights extension in the Superset codebase.