Skip to main content

Snippets API

Overview

The Snippets API allows extensions to programmatically manage code snippets that appear in the editor's bottom bar. Snippets are small, reusable code templates that users can insert with a single tap. Each snippet is scoped to one or more file extensions (e.g., .js, .html, .dart) and supports cursor positioning and multi-stop tab navigation within the inserted text.

Extensions interact with snippets through three RPC methods: snippets.add, snippets.delete, and snippets.list. All snippet IDs created by an extension are automatically namespaced with the extension's ID to prevent collisions between extensions.

When a user opens a file, the editor's bottom bar displays character buttons and snippet buttons that match the file's extension. Snippets added by extensions appear alongside the editor's built-in defaults and any user-created snippets.

Snippet Model

Each snippet consists of the following fields:

FieldTypeRequiredDescription
idstringYesA unique identifier for the snippet. The system automatically prefixes this with <extensionId>_ to create a namespaced ID (e.g., an extension with ID react-snippets adding a snippet with ID useState produces the stored ID react-snippets_useState).
labelstringYesThe display text shown on the snippet button in the bottom bar. Keep this short -- typically 2-8 characters (e.g., "func", "class", "log").
templatestringYesThe code template to insert. Supports cursor markers and tab stop syntax (see Template Syntax below).
fileExtensionstringYesThe file extension this snippet applies to, including the leading dot (e.g., ".js", ".html", ".py").

Template Syntax

Snippet templates support two cursor positioning systems. The editor detects which system a template uses and handles insertion accordingly.

Use $1, $2, ..., $9 to define numbered tab stops. After insertion, the cursor is placed at $1. The user can press Tab to advance through $2, $3, and so on in order. Use $0 to define the final cursor position; if $0 is omitted, the end of the inserted text is used as the final stop.

function $1($2) {\n  $0\n}

After insertion, the cursor lands on $1 (where the function name goes). Pressing Tab moves to $2 (the parameters), then to $0 (the function body).

Legacy Cursor Marker

Use a single | character to mark where the cursor should be placed after insertion. This is simpler but only supports a single cursor position.

console.log(|);

After insertion, the cursor is placed between the parentheses.

No Marker

If a template contains neither tab stops nor a | marker, the text is inserted as-is and the cursor is placed at the end.

Special Characters

  • \n -- line break
  • \t -- tab / indentation

Examples

TemplateResult (cursor shown as )
console.log(|);console.log(▌);
<div>|</div><div>▌</div>
if ($1) {\n $0\n}if (▌) {

} (cursor at condition, Tab moves to body)
for (let $1 = 0; $1 < $2; $1++) {\n $0\n}Loop with tab stops for variable name, limit, and body
import |import ▌

RPC Methods

snippets.add

Adds a new snippet to the editor for a specific file extension. The snippet appears in the bottom bar whenever a file with the matching extension is open.

Parameters:

ParameterTypeRequiredDescription
fileExtensionstringYesThe file extension to associate the snippet with (e.g., ".js"). Must include the leading dot.
idstringYesA unique identifier for the snippet within your extension. Will be prefixed with <extensionId>_ automatically.
labelstringYesThe display text for the snippet button.
templatestringYesThe code template with optional cursor markers.

Returns: { id: string } -- the full namespaced snippet ID.

SDK usage:

import editor from "@srcnexus/ext-sdk";

const result = await editor.snippets.add({
fileExtension: '.js',
id: 'arrow',
label: 'arrow',
template: 'const $1 = ($2) => {\n $0\n};',
});

console.log(result.id); // "my-extension_arrow"

snippets.delete

Removes a snippet previously added by this extension. Only snippets owned by the calling extension can be deleted (the system automatically applies the extension's namespace prefix).

Parameters:

ParameterTypeRequiredDescription
fileExtensionstringYesThe file extension the snippet is associated with.
idstringYesThe snippet ID (without the extension namespace prefix).

Returns: void

SDK usage:

import editor from "@srcnexus/ext-sdk";

await editor.snippets.delete({
fileExtension: '.js',
id: 'arrow',
});

snippets.list

Lists all snippets, optionally filtered by file extension. Returns snippets from all sources: built-in defaults, user-created snippets, and extension-registered snippets.

Parameters:

ParameterTypeRequiredDescription
fileExtensionstringNoIf provided, only snippets for this file extension are returned. If omitted, all snippets are returned grouped by file extension.

Returns:

When fileExtension is provided:

[
{ id: "js_console_log", label: "log", template: "console.log(|);" },
{ id: "js_arrow_function", label: "arrow", template: "const | = () => {\n \n};" },
{ id: "my-extension_useState", label: "useState", template: "const [$1, set$1] = useState($2);$0" }
]

When fileExtension is omitted, the result is an object keyed by file extension:

{
".js": [
{ id: "js_console_log", label: "log", template: "console.log(|);" },
...
],
".html": [
{ id: "html_div", label: "div", template: "<div>|</div>" },
...
]
}

SDK usage:

import editor from "@srcnexus/ext-sdk";

// List snippets for a specific extension
const jsSnippets = await editor.snippets.list({ fileExtension: '.js' });

// List all snippets grouped by file extension
const allSnippets = await editor.snippets.list();

Editor Integration

Bottom Bar

Snippets appear as tappable buttons in the editor's bottom bar, alongside quick-access character buttons. The bottom bar is visible whenever a text file is open and adapts to the current file's extension.

  • Tap a snippet button to insert the template at the current cursor position.
  • Long-press a snippet button to preview the full template in a dialog before inserting.
  • Character buttons support press-and-hold for rapid repeat insertion; snippet buttons do not repeat on hold.

Snippet buttons are visually distinguished from character buttons: they use the primary container color with a rounded pill shape, while character buttons use the surface color with a more compact style.

Tab Stop Navigation

When a snippet with tab stop syntax ($1, $2, etc.) is inserted, the editor enters a tab stop session. The cursor is placed at $1, and pressing Tab advances through subsequent stops in order. The session ends when the cursor reaches the final stop ($0, or end-of-text if $0 is absent). Tab stop positions are automatically adjusted as the user types between stops so they remain accurate even after the document changes.

Snippet Ordering and Visibility

The editor merges snippets from three sources in this priority:

  1. Built-in defaults -- language-specific snippets provided by the editor (e.g., log, func, if for JavaScript).
  2. User customizations -- snippets added or modified by the user through the Settings > Snippet Manager UI.
  3. Extension snippets -- snippets added programmatically through the Snippets API.

User and extension snippets with the same ID as a default snippet override the default. Users can also hide specific snippets and reorder them through the Snippet Manager.

Supported File Extensions with Built-in Snippets

The editor ships with default snippets for these file types:

  • .html, .htm -- HTML tags (div, span, p, a, img, input, button)
  • .css, .scss, .sass, .less -- CSS properties (flex, grid, pos, margin, padding, border, bg)
  • .js, .jsx -- JavaScript constructs (log, func, arrow, if, for, forEach, const, let)
  • .ts, .tsx -- TypeScript constructs (all JS snippets plus interface, type)
  • .dart -- Dart constructs (class, final, const, void, future, print, if)
  • .py -- Python constructs (def, class, if, for, print, import)
  • .json -- JSON structures ({}, [], str)

Extensions can add snippets for any file extension, including those not listed above.

JavaScript Examples

Adding Language Snippets on Activation

A common pattern is to register snippets when the extension loads, providing language-specific templates for the user:

import editor from "@srcnexus/ext-sdk";

editor.onLoad(async () => {
// Add React-specific snippets for JSX files
await editor.snippets.add({
fileExtension: '.jsx',
id: 'component',
label: 'comp',
template: 'function $1() {\n return (\n <div>\n $0\n </div>\n );\n}',
});

await editor.snippets.add({
fileExtension: '.jsx',
id: 'useState',
label: 'state',
template: 'const [$1, set$1] = useState($2);$0',
});

await editor.snippets.add({
fileExtension: '.jsx',
id: 'useEffect',
label: 'effect',
template: 'useEffect(() => {\n $0\n}, [$1]);',
});
});

Conditionally Adding Snippets Based on Project Content

An extension can inspect the project and add relevant snippets:

import editor from "@srcnexus/ext-sdk";

editor.onLoad(async () => {
try {
const activeFile = await editor.workspace.getActiveFile();
if (!activeFile) return;

// Add Express.js snippets for JavaScript files
await editor.snippets.add({
fileExtension: '.js',
id: 'express_route',
label: 'route',
template: "app.$1('/$2', (req, res) => {\n $0\n});",
});

await editor.snippets.add({
fileExtension: '.js',
id: 'express_middleware',
label: 'mw',
template: 'function $1(req, res, next) {\n $0\n next();\n}',
});
} catch (err) {
console.error('Failed to add snippets:', err);
}
});

Listing and Filtering Snippets

An extension can query existing snippets and act on the results:

import editor from "@srcnexus/ext-sdk";

async function showSnippetCount() {
const jsSnippets = await editor.snippets.list({ fileExtension: '.js' });
await editor.window.showToast(`${jsSnippets.length} JavaScript snippets available`);
}

async function listAllSnippetLanguages() {
const all = await editor.snippets.list();
const languages = Object.keys(all);
await editor.window.showToast(`Snippets available for: ${languages.join(', ')}`);
}

Cleaning Up Snippets

Extensions do not need to manually remove their snippets when deactivated. The editor automatically removes all snippets namespaced to an extension when it is uninstalled or disabled. However, an extension can remove specific snippets at any time:

import editor from "@srcnexus/ext-sdk";

// Remove a specific snippet
await editor.snippets.delete({
fileExtension: '.jsx',
id: 'useState',
});

A Complete Snippet Provider Extension

Here is a full example of an extension that provides Go language snippets:

import editor from "@srcnexus/ext-sdk";

const goSnippets = [
{ id: 'func', label: 'func', template: 'func $1($2) $3 {\n\t$0\n}' },
{ id: 'iferr', label: 'iferr', template: 'if err != nil {\n\t$0\n}' },
{ id: 'struct', label: 'struct', template: 'type $1 struct {\n\t$0\n}' },
{ id: 'interface', label: 'iface', template: 'type $1 interface {\n\t$0\n}' },
{ id: 'goroutine', label: 'go', template: 'go func() {\n\t$0\n}()' },
{ id: 'select', label: 'select', template: 'select {\ncase $1:\n\t$0\n}' },
{ id: 'test', label: 'test', template: 'func Test$1(t *testing.T) {\n\t$0\n}' },
];

editor.onLoad(async () => {
for (const s of goSnippets) {
await editor.snippets.add({
fileExtension: '.go',
id: s.id,
label: s.label,
template: s.template,
});
}

await editor.window.showToast(`Registered ${goSnippets.length} Go snippets`);
});

Namespace and Ownership

All snippet IDs are automatically namespaced. When an extension with ID my-ext calls snippets.add with id: "func", the stored snippet ID becomes my-ext_func. This namespacing has several implications:

  • No collisions -- two extensions can both define a snippet with id: "func" without conflict, since the stored IDs will be ext-a_func and ext-b_func.
  • Scoped deletion -- snippets.delete automatically prepends the calling extension's ID, so an extension can only delete its own snippets.
  • Transparent listing -- snippets.list returns the full namespaced IDs, which is useful for debugging but does not affect the user-facing label.

When an extension is uninstalled or disabled, the editor automatically removes all snippets whose ID starts with <extensionId>_.