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:
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.
superset-extensions init
The CLI will prompt you for information:
Extension ID (unique identifier, alphanumeric only): hello_world
Extension name (human-readable display name): Hello World
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
This creates a complete project structure:
hello_world/
├── extension.json # Extension metadata and configuration
├── backend/ # Backend Python code
│ ├── src/
│ │ └── hello_world/
│ │ ├── __init__.py
│ │ └── 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 basic metadata. Update it to register your panel in SQL Lab:
{
"id": "hello_world",
"name": "Hello World",
"version": "0.1.0",
"license": "Apache-2.0",
"frontend": {
"contributions": {
"views": {
"sqllab.panels": [
{
"id": "hello_world.main",
"name": "Hello World"
}
]
}
},
"moduleFederation": {
"exposes": ["./index"]
}
},
"backend": {
"entryPoints": ["hello_world.entrypoint"],
"files": ["backend/src/hello_world/**/*.py"]
},
"permissions": ["can_read"]
}
Key fields:
frontend.contributions.views.sqllab.panels: Registers your panel in SQL Labbackend.entryPoints: Python modules to load eagerly when extension starts
Step 4: Create Backend API
The CLI generated a basic backend/src/hello_world/entrypoint.py. We'll create an API endpoint.
Create backend/src/hello_world/api.py
from flask import Response
from flask_appbuilder.api import expose, protect, safe
from superset_core.api.rest_api import RestApi
class HelloWorldAPI(RestApi):
resource_name = "hello_world"
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:
- Extends
RestApifromsuperset_core.api.types.rest_api - Uses Flask-AppBuilder decorators (
@expose,@protect,@safe) - Returns responses using
self.response(status_code, result=data) - The endpoint will be accessible at
/extensions/hello_world/message - 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/hello_world/entrypoint.py
Replace the generated print statement with API registration:
from superset_core.api import rest_api
from .api import HelloWorldAPI
rest_api.add_extension_api(HelloWorldAPI)
This registers your API with Superset when the extension loads.
Step 5: Create Frontend Component
The CLI generated boilerplate files. The webpack config and package.json are already properly configured with Module Federation.
Create frontend/src/HelloWorldPanel.tsx
Create a new file for the component implementation:
import React, { useEffect, useState } from 'react';
import { authentication } from '@apache-superset/core';
const HelloWorldPanel: React.FC = () => {
const [message, setMessage] = useState<string>('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string>('');
useEffect(() => {
const fetchMessage = async () => {
try {
const csrfToken = await authentication.getCSRFToken();
const response = await fetch('/extensions/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 (
<div style={{ padding: '20px', textAlign: 'center' }}>
<p>Loading...</p>
</div>
);
}
if (error) {
return (
<div style={{ padding: '20px', color: 'red' }}>
<strong>Error:</strong> {error}
</div>
);
}
return (
<div style={{ padding: '20px' }}>
<h3>Hello World Extension</h3>
<div
style={{
padding: '16px',
backgroundColor: '#f6ffed',
border: '1px solid #b7eb8f',
borderRadius: '4px',
marginBottom: '16px',
}}
>
<strong>{message}</strong>
</div>
<p>This message was fetched from the backend API! 🎉</p>
</div>
);
};
export default HelloWorldPanel;
Update frontend/src/index.tsx
Replace the generated code with the extension entry point:
import React from 'react';
import { core } from '@apache-superset/core';
import HelloWorldPanel from './HelloWorldPanel';
export const activate = (context: core.ExtensionContext) => {
context.disposables.push(
core.registerViewProvider('hello_world.main', () => <HelloWorldPanel />),
);
};
export const deactivate = () => {};
Key patterns:
activatefunction is called when the extension loadscore.registerViewProviderregisters the component with IDhello_world.main(matchingextension.json)authentication.getCSRFToken()retrieves the CSRF token for API calls- Fetch calls to
/extensions/{extension_id}/{endpoint}reach your backend API context.disposables.push()ensures proper cleanup
Step 6: Install Dependencies
Install the frontend dependencies:
cd frontend
npm install
cd ..
Step 7: Package the Extension
Create a .supx bundle for deployment:
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 referencesfrontend/dist/- Built frontend assets (remoteEntry.js, chunks)backend/- Python source files
- Packages everything into
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:
# Enable extensions feature
FEATURE_FLAGS = {
"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:
cp hello_world-0.1.0.supx /path/to/extensions/folder/
Restart Superset
Restart your Superset instance to load the extension:
# 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
- Open SQL Lab in Superset
- Look for the "Hello World" panel in the panels dropdown or sidebar
- Open the panel - it should display "Hello from the backend!"
- Check that the message was fetched from your API endpoint
Understanding the Flow
Here's what happens when your extension loads:
- Superset starts: Reads
extension.jsonand loads backend entrypoint - Backend registration:
entrypoint.pyregisters your API viarest_api.add_extension_api() - Frontend loads: When SQL Lab opens, Superset fetches the remote entry file
- Module Federation: Webpack loads your extension code and resolves
@apache-superset/coretowindow.superset - Activation:
activate()is called, registering your view provider - Rendering: When the user opens your panel, React renders
<HelloWorldPanel /> - API call: Component fetches data from
/extensions/hello_world/message - Backend response: Your Flask API returns the hello world message
- Display: Component shows the message to the user
Next Steps
Now that you have a working extension, explore:
- Development - Project structure, APIs, and development workflow
- Contribution Types - Other contribution points beyond panels
- Deployment - Packaging and deploying your extension
- Security - Security best practices for extensions
For a complete real-world example, examine the query insights extension in the Superset codebase.