docs(extensions): fix extension developer documentation and CLI scaffolding (#38472)

This commit is contained in:
Michael S. Molina
2026-03-06 13:10:41 -03:00
committed by GitHub
parent 5c4bf0f6ea
commit 296bd7e56b
17 changed files with 273 additions and 291 deletions

View File

@@ -33,13 +33,15 @@ The extension architecture is built on six core principles that guide all techni
### 1. Lean Core
Superset's core should remain minimal, with many features delegated to extensions. Built-in features use the same APIs and extension mechanisms available to external developers. This approach:
- Reduces maintenance burden and complexity
- Encourages modularity
- Allows the community to innovate independently of the main codebase
### 2. Explicit Contribution Points
All extension points are clearly defined and documented. Extension authors know exactly where and how they can interact with the host system. Backend contributions are declared in `extension.json`. Frontend contributions are registered directly in code at module load time, giving the host clear visibility into what each extension provides:
All extension points are clearly defined and documented. Extension authors know exactly where and how they can interact with the host system. Both backend and frontend contributions are registered directly in code — backend contributions via classes decorated with `@api` (and other decorators) imported from the auto-discovered entrypoint, frontend contributions via calls like `views.registerView` and `commands.registerCommand` executed at module load time in `index.tsx`. This gives the host clear visibility into what each extension provides:
- Manage the extension lifecycle
- Provide a consistent user experience
- Validate extension compatibility
@@ -47,6 +49,7 @@ All extension points are clearly defined and documented. Extension authors know
### 3. Versioned and Stable APIs
Public interfaces for extensions follow semantic versioning, allowing for:
- Safe evolution of the platform
- Backward compatibility
- Clear upgrade paths for extension authors
@@ -54,6 +57,7 @@ Public interfaces for extensions follow semantic versioning, allowing for:
### 4. Lazy Loading and Activation
Extensions are loaded and activated only when needed, which:
- Minimizes performance overhead
- Reduces resource consumption
- Improves startup time
@@ -61,6 +65,7 @@ Extensions are loaded and activated only when needed, which:
### 5. Composability and Reuse
The architecture encourages reusing extension points and patterns across different modules, promoting:
- Consistency across extensions
- Reduced duplication
- Shared best practices
@@ -80,6 +85,7 @@ Two core packages provide the foundation for extension development:
**Frontend: `@apache-superset/core`**
This package provides essential building blocks for frontend extensions and the host application:
- Shared UI components
- Utility functions
- APIs and hooks
@@ -90,6 +96,7 @@ By centralizing these resources, both extensions and built-in features use the s
**Backend: `apache-superset-core`**
This package exposes key classes and APIs for backend extensions:
- Database connectors
- API extensions
- Security manager customization
@@ -102,6 +109,7 @@ It includes dependencies on critical libraries like Flask-AppBuilder and SQLAlch
**`apache-superset-extensions-cli`**
The CLI provides comprehensive commands for extension development:
- Project scaffolding
- Code generation
- Building and bundling
@@ -114,6 +122,7 @@ By standardizing these processes, the CLI ensures extensions are built consisten
The Superset host application serves as the runtime environment for extensions:
**Extension Management**
- Exposes `/api/v1/extensions` endpoint for registration and management
- Provides a dedicated UI for managing extensions
- Stores extension metadata in the `extensions` database table
@@ -121,6 +130,7 @@ The Superset host application serves as the runtime environment for extensions:
**Extension Storage**
The extensions table contains:
- Extension name, version, and author
- Metadata and configuration
- Built frontend and/or backend code
@@ -132,6 +142,7 @@ The following diagram illustrates how these components work together:
<img width="955" height="586" alt="Extension System Architecture" src="https://github.com/user-attachments/assets/cc2a41df-55a4-48c8-b056-35f7a1e567c6" />
The diagram shows:
1. **Extension projects** depend on core packages for development
2. **Core packages** provide APIs and type definitions
3. **The host application** implements the APIs and manages extensions
@@ -151,23 +162,25 @@ The architecture leverages Webpack's Module Federation to enable dynamic loading
Extensions configure Webpack to expose their entry points:
``` typescript
new ModuleFederationPlugin({
name: 'my_extension',
filename: 'remoteEntry.[contenthash].js',
exposes: {
'./index': './src/index.tsx',
},
externalsType: 'window',
externals: {
'@apache-superset/core': 'superset',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
'antd-v5': { singleton: true }
}
})
```javascript
externalsType: 'window',
externals: {
'@apache-superset/core': 'superset',
},
plugins: [
new ModuleFederationPlugin({
name: 'my_extension',
filename: 'remoteEntry.[contenthash].js',
exposes: {
'./index': './src/index.tsx',
},
shared: {
react: { singleton: true, import: false },
'react-dom': { singleton: true, import: false },
antd: { singleton: true, import: false },
},
}),
]
```
This configuration does several important things:
@@ -195,24 +208,12 @@ Here's what happens at runtime:
On the Superset side, the APIs are mapped to `window.superset` during application bootstrap:
``` typescript
```typescript
import * as supersetCore from '@apache-superset/core';
import {
authentication,
core,
commands,
extensions,
sqlLab,
} from 'src/extensions';
export default function setupExtensionsAPI() {
window.superset = {
...supersetCore,
authentication,
core,
commands,
extensions,
sqlLab,
};
}
```

View File

@@ -28,7 +28,7 @@ To facilitate the development of extensions, we define a set of well-defined con
## Frontend
Frontend contribution types allow extensions to extend Superset's user interface with new views, commands, and menu items. Frontend contributions are registered directly in code from your extension's `index.tsx` entry point — they do not need to be declared in `extension.json`.
Frontend contribution types allow extensions to extend Superset's user interface with new views, commands, and menu items. Frontend contributions are registered directly in code from your extension's `index.tsx` entry point.
### Views
@@ -68,25 +68,28 @@ commands.registerCommand(
### Menus
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item specifies the target area, the command to execute, and its placement (primary, secondary, or context). Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor).
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item specifies the view and command to execute, the target area, and the location (`primary`, `secondary`, or `context`). Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor).
```typescript
import { menus } from '@apache-superset/core';
menus.addMenuItem('sqllab.editor', {
placement: 'primary',
command: 'my-extension.copy-query',
});
menus.registerMenuItem(
{ view: 'sqllab.editor', command: 'my-extension.copy-query' },
'sqllab.editor',
'primary',
);
menus.addMenuItem('sqllab.editor', {
placement: 'secondary',
command: 'my-extension.prettify',
});
menus.registerMenuItem(
{ view: 'sqllab.editor', command: 'my-extension.prettify' },
'sqllab.editor',
'secondary',
);
menus.addMenuItem('sqllab.editor', {
placement: 'context',
command: 'my-extension.clear',
});
menus.registerMenuItem(
{ view: 'sqllab.editor', command: 'my-extension.clear' },
'sqllab.editor',
'context',
);
```
### Editors
@@ -111,24 +114,31 @@ See [Editors Extension Point](./extension-points/editors) for implementation det
## Backend
Backend contribution types allow extensions to extend Superset's server-side capabilities with new API endpoints, MCP tools, and MCP prompts.
Backend contribution types allow extensions to extend Superset's server-side capabilities. Backend contributions are registered at startup via classes and functions imported from the auto-discovered `entrypoint.py` file.
### REST API Endpoints
Extensions can register custom REST API endpoints under the `/extensions/` namespace. This dedicated namespace prevents conflicts with built-in endpoints and provides a clear separation between core and extension functionality.
```python
from superset_core.rest_api.api import RestApi, api
from flask_appbuilder.api import expose, protect
from flask import Response
from flask_appbuilder.api import expose, permission_name, protect, safe
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
@api(
id="my_extension_api",
name="My Extension API",
description="Custom API endpoints for my extension"
description="Custom API endpoints for my extension",
)
class MyExtensionAPI(RestApi):
openapi_spec_tag = "My Extension"
class_permission_name = "my_extension_api"
@expose("/hello", methods=("GET",))
@protect()
@safe
@permission_name("read")
def hello(self) -> Response:
return self.response(200, result={"message": "Hello from extension!"})
@@ -136,7 +146,7 @@ class MyExtensionAPI(RestApi):
from .api import MyExtensionAPI
```
**Note**: The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects context and generates appropriate paths:
**Note**: The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects context and generates appropriate paths:
- **Extension context**: `/extensions/{publisher}/{name}/` with ID prefixed as `extensions.{publisher}.{name}.{id}`
- **Host context**: `/api/v1/` with original ID
@@ -152,16 +162,65 @@ You can also specify a `resource_name` parameter to add an additional path segme
@api(
id="analytics_api",
name="Analytics API",
resource_name="analytics" # Adds /analytics to the path
resource_name="analytics", # Adds /analytics to the path
)
class AnalyticsAPI(RestApi):
@expose("/insights", methods=("GET",))
def insights(self):
@protect()
@safe
@permission_name("read")
def insights(self) -> Response:
# This endpoint will be available at:
# /extensions/my-org/dataset-tools/analytics/insights
return self.response(200, result={"insights": []})
```
### MCP Tools and Prompts
### MCP Tools
Extensions can contribute Model Context Protocol (MCP) tools and prompts that AI agents can discover and use. See [MCP Integration](./mcp) for detailed documentation.
Extensions can register Python functions as MCP tools that AI agents can discover and call. Tools provide executable functionality such as data processing, custom analytics, or integration with external services. Each tool specifies a unique name and an optional description that helps AI agents decide when to use it.
```python
from superset_core.mcp.decorators import tool
@tool(
name="my-extension.get_summary",
description="Get a summary of recent query activity",
tags=["analytics", "queries"],
)
def get_summary() -> dict:
"""Returns a summary of recent query activity."""
return {"status": "success", "result": {"queries_today": 42}}
```
See [MCP Integration](./mcp) for implementation details.
### MCP Prompts
Extensions can register MCP prompts that provide interactive guidance and context to AI agents. Prompts help agents understand domain-specific workflows, best practices, or troubleshooting steps for your extension's use cases.
```python
from superset_core.mcp.decorators import prompt
from fastmcp import Context
@prompt(
"my-extension.analysis_guide",
title="Analysis Guide",
description="Step-by-step guidance for data analysis workflows",
)
async def analysis_guide(ctx: Context) -> str:
"""Provides guidance for data analysis workflows."""
return """
# Data Analysis Guide
Follow these steps for effective analysis:
1. **Explore your data** - Review available datasets and schema
2. **Build your query** - Use SQL Lab to craft and test queries
3. **Visualize results** - Choose the right chart type for your data
What would you like to analyze today?
"""
```
See [MCP Integration](./mcp) for implementation details.

View File

@@ -38,12 +38,14 @@ superset-extensions build: Builds extension assets.
superset-extensions bundle: Packages the extension into a .supx file.
superset-extensions dev: Automatically rebuilds the extension as files change.
superset-extensions validate: Validates the extension structure and metadata.
```
When creating a new extension with `superset-extensions init`, the CLI generates a standardized folder structure:
```
dataset-references/
my-org.dataset-references/
├── extension.json
├── frontend/
│ ├── src/
@@ -53,8 +55,10 @@ dataset-references/
├── backend/
│ ├── src/
│ │ └── superset_extensions/
│ │ └── dataset_references/
├── tests/
│ │ └── my_org/
│ └── dataset_references/
│ │ ├── api.py
│ │ └── entrypoint.py
│ ├── pyproject.toml
│ └── requirements.txt
├── dist/
@@ -65,19 +69,20 @@ dataset-references/
│ │ └── 900.038b20cdff6d49cfa8d9.js
│ └── backend
│ └── superset_extensions/
│ └── dataset_references/
── __init__.py
│ ├── api.py
│ └── entrypoint.py
├── dataset-references-1.0.0.supx
│ └── my_org/
── dataset_references/
├── api.py
└── entrypoint.py
├── my-org.dataset-references-1.0.0.supx
└── README.md
```
**Note**: The extension ID (`dataset-references`) serves as the basis for all technical names:
- Directory name: `dataset-references` (kebab-case)
- Backend Python package: `dataset_references` (snake_case)
- Frontend package name: `dataset-references` (kebab-case)
- Module Federation name: `datasetReferences` (camelCase)
**Note**: With publisher `my-org` and name `dataset-references`, the technical names are:
- Directory name: `my-org.dataset-references` (kebab-case)
- Backend Python namespace: `superset_extensions.my_org.dataset_references`
- Backend distribution package: `my_org-dataset_references`
- Frontend package name: `@my-org/dataset-references` (scoped)
- Module Federation name: `myOrg_datasetReferences` (camelCase)
The `extension.json` file serves as the declared metadata for the extension, containing the extension's name, version, author, description, and a list of capabilities. This file is essential for the host application to understand how to load and manage the extension.
@@ -203,7 +208,8 @@ Extension endpoints are registered under a dedicated `/extensions` namespace to
```python
from superset_core.common.models import Database, get_session
from superset_core.common.daos import DatabaseDAO
from superset_core.rest_api.api import RestApi, api
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
from flask_appbuilder.api import expose, protect
@api(
@@ -244,7 +250,7 @@ class DatasetReferencesAPI(RestApi):
### Automatic Context Detection
The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects whether it's being used in host or extension code:
The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects whether it's being used in host or extension code:
- **Extension APIs**: Registered under `/extensions/{publisher}/{name}/` with IDs prefixed as `extensions.{publisher}.{name}.{id}`
- **Host APIs**: Registered under `/api/v1/` with original IDs
@@ -262,7 +268,7 @@ LOCAL_EXTENSIONS = [
]
```
This instructs Superset to load and serve extensions directly from disk, so you can iterate quickly. Running `superset-extensions dev` watches for file changes and rebuilds assets automatically, while the Webpack development server (started separately with `npm run dev-server`) serves updated files as soon as they're modified. This enables immediate feedback for React components, styles, and other frontend code. Changes to backend files are also detected automatically and immediately synced, ensuring that both frontend and backend updates are reflected in your development environment.
This instructs Superset to load and serve extensions directly from disk, so you can iterate quickly. Running `superset-extensions dev` watches for file changes and rebuilds assets automatically, while the Webpack development server (started separately with `npm run start`) serves updated files as soon as they're modified. This enables immediate feedback for React components, styles, and other frontend code. Changes to backend files are also detected automatically and immediately synced, ensuring that both frontend and backend updates are reflected in your development environment.
Example output when running in development mode:

View File

@@ -37,32 +37,12 @@ Superset uses text editors in various places throughout the application:
| `css` | Dashboard Properties, CSS Template Modal |
| `markdown` | Dashboard Markdown component |
| `yaml` | Template Params Editor |
| `javascript` | Custom JavaScript editor contexts |
| `python` | Custom Python editor contexts |
| `text` | Plain text editor contexts |
By registering an editor provider for a language, your extension replaces the default Ace editor in **all** locations that use that language.
## Manifest Configuration
Declare editor contributions in your `extension.json` manifest:
```json
{
"name": "monaco-editor",
"version": "1.0.0",
"frontend": {
"contributions": {
"editors": [
{
"id": "monaco-editor.sql",
"name": "Monaco SQL Editor",
"languages": ["sql"],
"description": "Monaco-based SQL editor with IntelliSense"
}
]
}
}
}
```
## Implementing an Editor
Your editor component must implement the `EditorProps` interface and expose an `EditorHandle` via `forwardRef`. For the complete interface definitions, see `@apache-superset/core/api/editors.ts`.
@@ -165,21 +145,22 @@ const MonacoSQLEditor = forwardRef<editors.EditorHandle, editors.EditorProps>(
export default MonacoSQLEditor;
```
### activate.ts
### index.tsx
Register the editor at module load time from your extension's entry point:
```typescript
import { editors } from '@apache-superset/core';
import MonacoSQLEditor from './MonacoSQLEditor';
export function activate(context) {
// Register the Monaco editor for SQL using the contribution ID from extension.json
const disposable = editors.registerEditorProvider(
'monaco-sql-editor.sql',
MonacoSQLEditor,
);
context.subscriptions.push(disposable);
}
editors.registerEditor(
{
id: 'my-extension.monaco-sql',
name: 'Monaco SQL Editor',
languages: ['sql'],
},
MonacoSQLEditor,
);
```
## Handling Hotkeys

View File

@@ -86,132 +86,73 @@ Extensions can replace the default SQL editor with custom implementations (Monac
This example adds a "Data Profiler" panel to SQL Lab:
```json
{
"name": "data_profiler",
"version": "1.0.0",
"frontend": {
"contributions": {
"views": {
"sqllab": {
"panels": [
{
"id": "data_profiler.main",
"name": "Data Profiler"
}
]
}
}
}
}
}
```
```typescript
import { core } from '@apache-superset/core';
import React from 'react';
import { views } from '@apache-superset/core';
import DataProfilerPanel from './DataProfilerPanel';
export function activate(context) {
// Register the panel view with the ID declared in extension.json
const disposable = core.registerView('data_profiler.main', <DataProfilerPanel />);
context.subscriptions.push(disposable);
}
views.registerView(
{ id: 'my-extension.data-profiler', name: 'Data Profiler' },
'sqllab.panels',
() => <DataProfilerPanel />,
);
```
### Adding Actions to the Editor
This example adds primary, secondary, and context actions to the editor:
```json
{
"name": "query_tools",
"version": "1.0.0",
"frontend": {
"contributions": {
"commands": [
{
"command": "query_tools.format",
"title": "Format Query",
"icon": "FormatPainterOutlined"
},
{
"command": "query_tools.explain",
"title": "Explain Query"
},
{
"command": "query_tools.copy_as_cte",
"title": "Copy as CTE"
}
],
"menus": {
"sqllab": {
"editor": {
"primary": [
{
"view": "builtin.editor",
"command": "query_tools.format"
}
],
"secondary": [
{
"view": "builtin.editor",
"command": "query_tools.explain"
}
],
"context": [
{
"view": "builtin.editor",
"command": "query_tools.copy_as_cte"
}
]
}
}
}
}
}
}
```
```typescript
import { commands, sqlLab } from '@apache-superset/core';
import { commands, menus, sqlLab } from '@apache-superset/core';
export function activate(context) {
// Register the commands declared in extension.json
const formatCommand = commands.registerCommand(
'query_tools.format',
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Format the SQL query
}
},
);
commands.registerCommand(
{ id: 'my-extension.format', title: 'Format Query', icon: 'FormatPainterOutlined' },
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Format the SQL query
}
},
);
const explainCommand = commands.registerCommand(
'query_tools.explain',
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Show query explanation
}
},
);
commands.registerCommand(
{ id: 'my-extension.explain', title: 'Explain Query' },
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Show query explanation
}
},
);
const copyAsCteCommand = commands.registerCommand(
'query_tools.copy_as_cte',
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Copy selected text as CTE
}
},
);
commands.registerCommand(
{ id: 'my-extension.copy-as-cte', title: 'Copy as CTE' },
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Copy selected text as CTE
}
},
);
context.subscriptions.push(formatCommand, explainCommand, copyAsCteCommand);
}
menus.registerMenuItem(
{ view: 'builtin.editor', command: 'my-extension.format' },
'sqllab.editor',
'primary',
);
menus.registerMenuItem(
{ view: 'builtin.editor', command: 'my-extension.explain' },
'sqllab.editor',
'secondary',
);
menus.registerMenuItem(
{ view: 'builtin.editor', command: 'my-extension.copy-as-cte' },
'sqllab.editor',
'context',
);
```
## Next Steps

View File

@@ -64,6 +64,7 @@ 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)
@@ -80,9 +81,7 @@ my-org.hello-world/
│ ├── src/
│ │ └── superset_extensions/
│ │ └── my_org/
│ │ ├── __init__.py
│ │ └── hello_world/
│ │ ├── __init__.py
│ │ └── entrypoint.py # Backend registration
│ └── pyproject.toml
└── frontend/ # Frontend TypeScript/React code
@@ -95,7 +94,7 @@ my-org.hello-world/
## Step 3: Configure Extension Metadata
The generated `extension.json` contains the extension's metadata. It is used to identify the extension and declare its backend entry points. Frontend contributions are registered directly in code (see Step 5).
The generated `extension.json` contains the extension's metadata.
```json
{
@@ -104,10 +103,6 @@ The generated `extension.json` contains the extension's metadata. It is used to
"displayName": "Hello World",
"version": "0.1.0",
"license": "Apache-2.0",
"backend": {
"entryPoints": ["superset_extensions.my_org.hello_world.entrypoint"],
"files": ["backend/src/superset_extensions/my_org/hello_world/**/*.py"]
},
"permissions": ["can_read"]
}
```
@@ -117,8 +112,7 @@ The generated `extension.json` contains the extension's metadata. It is used to
- `publisher`: Organizational namespace for the extension
- `name`: Technical identifier (kebab-case)
- `displayName`: Human-readable name shown to users
- `backend.entryPoints`: Python modules to load eagerly when the extension starts
- `backend.files`: Glob patterns for Python source files to include in the bundle
- `permissions`: List of permissions the extension requires
## Step 4: Create Backend API
@@ -129,7 +123,8 @@ The CLI generated a basic `backend/src/superset_extensions/my_org/hello_world/en
```python
from flask import Response
from flask_appbuilder.api import expose, protect, safe
from superset_core.rest_api.api import RestApi, api
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
@api(
@@ -174,7 +169,7 @@ class HelloWorldAPI(RestApi):
**Key points:**
- Uses [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator with automatic context detection
- 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)`
@@ -187,12 +182,10 @@ 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
print("Hello World extension loaded successfully!")
from .api import HelloWorldAPI # noqa: F401
```
The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects extension context and registers your API with proper namespacing.
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
@@ -236,52 +229,53 @@ The webpack configuration requires specific settings for Module Federation. Key
**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.json");
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";
const isProd = argv.mode === 'production';
return {
entry: isProd ? {} : "./src/index.tsx",
mode: isProd ? "production" : "development",
entry: isProd ? {} : './src/index.tsx',
mode: isProd ? 'production' : 'development',
devServer: {
port: 3001,
port: 3000,
headers: {
"Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Origin': '*',
},
},
output: {
filename: isProd ? undefined : "[name].[contenthash].js",
chunkFilename: "[name].[contenthash].js",
clean: true,
path: path.resolve(__dirname, "dist"),
publicPath: `/api/v1/extensions/my-org/hello-world/`,
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"],
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
// Map @apache-superset/core imports to window.superset at runtime
externalsType: "window",
externalsType: 'window',
externals: {
"@apache-superset/core": "superset",
'@apache-superset/core': 'superset',
},
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "myOrg_helloWorld",
filename: "remoteEntry.[contenthash].js",
name: 'myOrg_helloWorld',
filename: 'remoteEntry.[contenthash].js',
exposes: {
"./index": "./src/index.tsx",
'./index': './src/index.tsx',
},
shared: {
react: {
@@ -289,9 +283,14 @@ module.exports = (env, argv) => {
requiredVersion: packageConfig.peerDependencies.react,
import: false, // Use host's React, don't bundle
},
"react-dom": {
'react-dom': {
singleton: true,
requiredVersion: packageConfig.peerDependencies["react-dom"],
requiredVersion: packageConfig.peerDependencies['react-dom'],
import: false,
},
antd: {
singleton: true,
requiredVersion: packageConfig.peerDependencies['antd'],
import: false,
},
},
@@ -306,8 +305,9 @@ module.exports = (env, argv) => {
```json
{
"compilerOptions": {
"baseUrl": ".",
"moduleResolution": "node",
"target": "es5",
"module": "esnext",
"moduleResolution": "node10",
"jsx": "react",
"strict": true,
"esModuleInterop": true,
@@ -332,16 +332,16 @@ const HelloWorldPanel: React.FC = () => {
const [error, setError] = useState<string>('');
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!,
},
});
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}`);
@@ -496,8 +496,8 @@ Superset will extract and validate the extension metadata, load the assets, regi
Here's what happens when your extension loads:
1. **Superset starts**: Reads `extension.json` and loads the backend entrypoint
2. **Backend registration**: `entrypoint.py` imports your API class, triggering the [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator to register it automatically
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

View File

@@ -30,15 +30,15 @@ This page serves as a registry of community-created Superset extensions. These e
| Name | Description | Author | Preview |
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api_explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api-explorer.png" target="_blank"><img src="/img/extensions/api-explorer.png" alt="Extensions API Explorer" width="120" /></a> |
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api-explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api-explorer.png" target="_blank"><img src="/img/extensions/api-explorer.png" alt="Extensions API Explorer" width="120" /></a> |
| [SQL Query Flow Visualizer](https://github.com/msyavuz/superset-sql-visualizer) | A SQL Lab panel that transforms SQL queries into interactive flow diagrams, helping developers and analysts understand query execution paths and data relationships. | Mehmet Salih Yavuz | <a href="/img/extensions/sql-flow-visualizer.png" target="_blank"><img src="/img/extensions/sql-flow-visualizer.png" alt="SQL Flow Visualizer" width="120" /></a> |
| [SQL Lab Export to Google Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab_gsheets) | A Superset extension that allows users to export SQL Lab query results directly to Google Sheets. | Michael S. Molina | <a href="/img/extensions/gsheets-export.png" target="_blank"><img src="/img/extensions/gsheets-export.png" alt="SQL Lab Export to Google Sheets" width="120" /></a> |
| [SQL Lab Export to Google Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab-gsheets) | A Superset extension that allows users to export SQL Lab query results directly to Google Sheets. | Michael S. Molina | <a href="/img/extensions/gsheets-export.png" target="_blank"><img src="/img/extensions/gsheets-export.png" alt="SQL Lab Export to Google Sheets" width="120" /></a> |
| [SQL Lab Export to Parquet](https://github.com/rusackas/superset-extensions/tree/main/sqllab_parquet) | Export SQL Lab query results directly to Apache Parquet format with Snappy compression. | Evan Rusackas | <a href="/img/extensions/parquet-export.png" target="_blank"><img src="/img/extensions/parquet-export.png" alt="SQL Lab Export to Parquet" width="120" /></a> |
| [SQL Lab Query Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query_comparison) | A SQL Lab extension that enables side-by-side comparison of query results across different tabs, with GitHub-style diff visualization showing added/removed rows and columns. | Michael S. Molina | <a href="/img/extensions/query-comparison.png" target="_blank"><img src="/img/extensions/query-comparison.png" alt="Query Comparison" width="120" /></a> |
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result_stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
| [SQL Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/sql_snippets) | A SQL Lab extension that provides reusable SQL code snippets, enabling quick insertion of commonly used code blocks such as license headers, author information, and frequently used SQL patterns. | Michael S. Molina | <a href="/img/extensions/sql-snippets.png" target="_blank"><img src="/img/extensions/sql-snippets.png" alt="SQL Snippets" width="120" /></a> |
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query_estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors_bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
| [SQL Lab Query Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query-comparison) | A SQL Lab extension that enables side-by-side comparison of query results across different tabs, with GitHub-style diff visualization showing added/removed rows and columns. | Michael S. Molina | <a href="/img/extensions/query-comparison.png" target="_blank"><img src="/img/extensions/query-comparison.png" alt="Query Comparison" width="120" /></a> |
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result-stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
| [Editor Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/editor-snippets) | A SQL Lab extension for managing and inserting reusable code snippets into the editor, with server-side persistence per user. | Michael S. Molina | <a href="/img/extensions/editor-snippets.png" target="_blank"><img src="/img/extensions/editor-snippets.png" alt="Editor Snippets" width="120" /></a> |
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query-estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors-bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
## How to Add Your Extension