Skip to main content
Version: Next

Editor Contributions

Extensions can replace Superset's default text editors with custom implementations. This allows you to provide enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions.

Overview

Superset uses text editors in various places throughout the application:

LanguageLocations
sqlSQL Lab, Metric/Filter Popovers
jsonDashboard Properties, Annotation Modal, Theme Modal
cssDashboard Properties, CSS Template Modal
markdownDashboard Markdown component
yamlTemplate Params Editor

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:

{
"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.

Key EditorProps

interface EditorProps {
/** Controlled value */
value: string;
/** Content change handler */
onChange: (value: string) => void;
/** Language mode for syntax highlighting */
language: EditorLanguage;
/** Keyboard shortcuts to register */
hotkeys?: EditorHotkey[];
/** Callback when editor is ready with imperative handle */
onReady?: (handle: EditorHandle) => void;
/** Host-specific context (e.g., database info from SQL Lab) */
metadata?: Record<string, unknown>;
// ... additional props for styling, annotations, etc.
}

Key EditorHandle Methods

interface EditorHandle {
/** Focus the editor */
focus(): void;
/** Get the current editor content */
getValue(): string;
/** Get the current cursor position */
getCursorPosition(): Position;
/** Move the cursor to a specific position */
moveCursorToPosition(position: Position): void;
/** Set the selection range */
setSelection(selection: Range): void;
/** Scroll to a specific line */
scrollToLine(line: number): void;
// ... additional methods for text manipulation, annotations, etc.
}

Example Implementation

Here's an example of a Monaco-based SQL editor implementing the key interfaces shown above:

MonacoSQLEditor.tsx

import { forwardRef, useRef, useImperativeHandle, useEffect } from 'react';
import * as monaco from 'monaco-editor';
import type { editors } from '@apache-superset/core';

const MonacoSQLEditor = forwardRef<editors.EditorHandle, editors.EditorProps>(
(props, ref) => {
const { value, onChange, hotkeys, onReady } = props;
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);

// Implement EditorHandle interface
const handle: editors.EditorHandle = {
focus: () => editorRef.current?.focus(),
getValue: () => editorRef.current?.getValue() ?? '',
getCursorPosition: () => {
const pos = editorRef.current?.getPosition();
return { line: (pos?.lineNumber ?? 1) - 1, column: (pos?.column ?? 1) - 1 };
},
// ... implement remaining methods
};

useImperativeHandle(ref, () => handle, []);

useEffect(() => {
if (!containerRef.current) return;

const editor = monaco.editor.create(containerRef.current, { value, language: 'sql' });
editorRef.current = editor;

editor.onDidChangeModelContent(() => onChange(editor.getValue()));

// Register hotkeys
hotkeys?.forEach(hotkey => {
editor.addAction({
id: hotkey.name,
label: hotkey.name,
run: () => hotkey.exec(handle),
});
});

onReady?.(handle);
return () => editor.dispose();
}, []);

return <div ref={containerRef} style={{ height: '100%', width: '100%' }} />;
},
);

export default MonacoSQLEditor;

activate.ts

import { editors } from '@apache-superset/core';
import MonacoSQLEditor from './MonacoSQLEditor';

export function activate(context) {
// Register the Monaco editor for SQL
const disposable = editors.registerEditorProvider(
{
id: 'monaco-sql-editor.sql',
name: 'Monaco SQL Editor',
languages: ['sql'],
},
MonacoSQLEditor,
);

context.subscriptions.push(disposable);
}

Handling Hotkeys

Superset passes keyboard shortcuts via the hotkeys prop. Each hotkey includes an exec function that receives the EditorHandle:

interface EditorHotkey {
name: string;
key: string; // e.g., "Ctrl-Enter", "Alt-Shift-F"
description?: string;
exec: (handle: EditorHandle) => void;
}

Your editor must register these hotkeys with your editor framework and call exec(handle) when triggered.

Keywords

Superset passes static autocomplete suggestions via the keywords prop. These include table names, column names, and SQL functions based on the current database context:

interface EditorKeyword {
name: string;
value?: string; // Text to insert (defaults to name)
meta?: string; // Category like "table", "column", "function"
score?: number; // Sorting priority
}

Your editor should convert these to your framework's completion format and register them for autocomplete.

Completion Providers

For dynamic autocomplete (e.g., fetching suggestions as the user types), implement and register a CompletionProvider via the EditorHandle:

const provider: CompletionProvider = {
id: 'my-sql-completions',
triggerCharacters: ['.', ' '],
provideCompletions: async (content, position, context) => {
// Use context.metadata for database info
// Return array of CompletionItem
return [
{ label: 'SELECT', insertText: 'SELECT', kind: 'keyword' },
// ...
];
},
};

// Register during editor initialization
const disposable = handle.registerCompletionProvider(provider);

Next Steps