Guide

How to build an extension

An extension is a small folder of code that runs inside Empire Code Editor and talks to it through the nova API. This guide walks through everything — the files, the API, full examples, packaging and publishing. When you are ready, open the Dev Area to upload it.

1. Folder structure

An extension is a folder. The only required file is extension.js — the code that runs. You can add a manifest and other files next to it.

my-extension/
  extension.js          ← required: the code
  nova-extension.json   ← optional: name, version, contributions
  themes/
    my-theme.json       ← optional: files your code imports

You do not have to write the manifest yourself — the Dev Area fills in the name, description and icon for you. Add a manifest only when you want to declare contributions (like a theme) without code.

2. Your first extension

extension.js exports an activate function. The editor calls it with the nova API when the extension is turned on, and deactivate when it is turned off. Both CommonJS and ES-module syntax work.

CommonJS:

module.exports = {
  activate(nova) {
    nova.commands.register('hello.world', 'Hello: Say Hi', () => {
      nova.window.showMessage('Hello from my extension!');
    });
  },
  deactivate() {},
};

ES module (same thing):

export function activate(nova) {
  nova.commands.register('hello.world', 'Hello: Say Hi', () => {
    nova.window.showMessage('Hello from my extension!');
  });
}
export function deactivate() {}

Anything the extension registers through nova is undone automatically when it is disabled.

3. The nova API

Everything your extension can do is on the nova object passed to activate.

Commands

nova.commands.register(id, title, fn)   // adds a command (Ctrl+Shift+P)
nova.commands.execute(id, ...args)      // runs another command

Status bar

const item = nova.statusBar.add({ text: 'Ready', tooltip: 'My item', command: 'hello.world' });
item.setText('Busy…'); item.show(true); item.dispose();

Editor

nova.editor.getText()            // whole file
nova.editor.getSelectionText()   // current selection
nova.editor.getLanguage()        // 'lua', 'python', …
nova.editor.getFile()            // path of the active file
nova.editor.insertText(text)     // insert at the cursor
nova.editor.replaceAll(text)     // replace the whole file
nova.editor.onDidChangeContent(fn)
nova.editor.onDidChangeCursor(fn)
nova.editor.onDidSave(fn)

Languages

nova.languages.registerCompletions('lua', [{ label: 'print', insertText: 'print($1)' }]);
nova.languages.registerSnippets('lua', { 'for loop': 'for i=1,10 do\\n\\t$0\\nend' });
nova.languages.registerFormatter('lua', (text) => text.trimEnd());
nova.languages.registerLinter('lua', (text) => [{ line: 1, col: 1, endCol: 5, message: 'Example', severity: 'warning' }]);

Window & output

nova.window.showMessage(msg, 'info' | 'warning' | 'error');
await nova.window.showInput('Your name?');
await nova.window.showQuickPick(['One', 'Two']);
nova.output.show('My Channel', 'some logs');

Theme & settings

nova.theme.set('nova-light');        // switch to a built-in theme
nova.theme.toggle(true);             // true = light, false = dark, nothing = flip
nova.theme.register(themeObject, { apply: true });  // add & apply your own theme
nova.theme.list();                   // [{ id, label }]
nova.settings.get('fontSize');
nova.settings.update({ fontSize: 15 });

Git, AI & Discord

await nova.git.blame(file, line);
await nova.git.diff();
await nova.ai.complete('Explain this code', 'You are helpful');
nova.discord.setActivity(clientId, { details: 'Coding', state: 'in my project' });

4. Full examples

A light theme

Create themes/my-light.json (the editor's theme format), import it, and apply it. import of a JSON file works.

// extension.js
import theme from './themes/my-light.json';
export function activate(nova) {
  nova.theme.register(theme, { apply: true });
}
export function deactivate() {}
// themes/my-light.json
{
  "id": "my-light",
  "label": "My Light",
  "type": "light",
  "ui": {
    "--bg": "#f3f4f8", "--bg-sidebar": "#f3f4f8", "--bg-panel": "#ffffff",
    "--fg": "#2a2f3a", "--fg-muted": "#6a7383", "--border": "#dcdfe7", "--accent": "#2f6bff"
  },
  "editor": {
    "base": "vs",
    "inherit": true,
    "rules": [
      { "token": "comment", "foreground": "8a93a6", "fontStyle": "italic" },
      { "token": "keyword", "foreground": "2453c9" },
      { "token": "string", "foreground": "1f7a4d" }
    ],
    "colors": { "editor.background": "#ffffff", "editor.foreground": "#2a2f3a" }
  }
}

A VS Code theme (with colors and tokenColors) also works — the editor converts it automatically.

A trailing-whitespace cleaner

module.exports = {
  activate(nova) {
    nova.commands.register('clean.trim', 'Clean: Trim trailing spaces', () => {
      nova.editor.replaceAll(nova.editor.getText().replace(/[ \\t]+$/gm, ''));
      nova.window.showMessage('Trimmed trailing whitespace.');
    });
  },
  deactivate() {},
};

5. Package & publish

  1. Put extension.js (and any files it needs) in a folder.
  2. Zip the folder. extension.js can be at the root or inside one wrapping folder — both work.
  3. Open the Dev Area, sign in, click New extension, fill in the name, description and icon, and upload the .zip.
  4. It appears in the editor under Extensions as not installed — click Install there to turn it on.
  5. Need a change? Click Edit code on the card to open extension.js in the editor, or upload a new .zip from Edit.