File size: 16,958 Bytes
93dd899
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
---
title: Quick Start
sidebar_position: 2
---

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
-->

# 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**: `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:

```
hello-world/
β”œβ”€β”€ extension.json           # Extension metadata and configuration
β”œβ”€β”€ backend/                 # Backend Python code
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   └── 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/my_org/hello_world/entrypoint.py`. We'll create an API endpoint.

**Create `backend/src/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`](https://github.com/apache/superset/blob/master/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/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`](https://github.com/apache/superset/blob/master/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.md#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<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/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 (
      <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`**

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',
  () => <HelloWorldPanel />,
);
```

**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`](https://github.com/apache/superset/blob/master/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 `<HelloWorldPanel />`
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.md)** - Project structure, APIs, and development workflow
- **[Contribution Types](./contribution-types.md)** - Other contribution points beyond panels
- **[Deployment](./deployment.md)** - Packaging and deploying your extension
- **[Security](./security.md)** - Security best practices for extensions

For a complete real-world example, examine the query insights extension in the Superset codebase.