Your first extension
We'll build a small but complete extension: a status-bar clock with a command to toggle 12/24-hour format and a keybinding for it. It uses three of ctx's registration methods — a realistic slice of the API in ~40 lines.
What you're building
An extension is a standalone package — your own folder, with its own package.json, that you build against @silo-code/sdk (installed from npm as a devDependency) and install into Silo from a folder. You don't need Silo's source to build one. This page writes the code; Publishing an extension covers the packaging contract in full. Installing from a URL / npm registry is on the roadmap.
1. Scaffold
Make a folder for your extension, install the SDK and React as devDependencies, and add src/index.tsx:
mkdir clock-extension && cd clock-extension
npm i -D @silo-code/sdk react @types/react// src/index.tsx
import { useEffect, useState } from "react";
import type { Extension } from "@silo-code/sdk";
export const extension: Extension = {
id: "acme.clock",
activate(ctx) {
// everything goes here
},
};An extension is just an Extension object. activate runs once; ctx is your ExtensionContext.
2. Add a command
A Command is a named action. We'll have it flip a flag held in module scope (a real extension might persist this — see step 5).
activate(ctx) {
let use24h = false;
ctx.registerCommand({
id: "clock.toggleFormat",
label: "Clock: Toggle 12/24-Hour",
run: () => { use24h = !use24h; },
});
}registerCommand returns a Disposable; the host also tracks it on ctx.subscriptions, so you don't have to clean it up manually.
3. Bind a key
A Keybinding points a shortcut at a command id:
ctx.registerKeybinding({
id: "clock.toggleFormat",
key: "cmd+shift+k",
command: "clock.toggleFormat",
});4. Render it in the status bar
A StatusItem is a React component placed at one end of the status bar. Clicking it runs our command via ctx.executeCommand:
function Clock() {
const [now, setNow] = useState(() => new Date());
useEffect(() => {
const t = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(t);
}, []);
const time = now.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
hour12: !use24h,
});
return (
<button onClick={() => ctx.executeCommand("clock.toggleFormat")}>
{time}
</button>
);
}
ctx.registerStatusItem({
id: "clock",
alignment: "right",
priority: 10,
component: Clock,
});Defining Clock inside activate lets it close over ctx and use24h. activate runs once, so the component identity stays stable.
5. Build & install
Add a silo key to your package.json so the host knows your extension's id and where its built bundle lives — the id must match extension.id from step 1:
{
"name": "clock-extension",
"displayName": "Clock",
"version": "0.1.0",
"type": "module",
"scripts": { "build": "node build.mjs" },
"silo": {
"id": "acme.clock", // must equal extension.id
"engine": "^0.1",
"main": "dist/index.js",
},
}Bundle to a single ESM file, leaving react, react/jsx-runtime, and @silo-code/sdk as externals — the host supplies its own instances at load time (see the build contract for why):
// build.mjs
import { build } from "esbuild";
await build({
entryPoints: ["src/index.tsx"],
outfile: "dist/index.js",
bundle: true,
format: "esm",
jsx: "automatic",
external: ["react", "react/jsx-runtime", "@silo-code/sdk"],
});Then npm run build and, in Silo, open Settings → Extensions → Install from folder… and pick your clock-extension folder. The clock appears at the right of the status bar; click it or press ⌘⇧K to toggle the format. Edits reload with Disable / Enable — no app restart. See Publishing an extension for the full packaging reference.
Going further
- Persist the format with the storage handed to side panels (
ExtensionStorage) or your own approach, instead of a module-scoped flag. - Open files from a command via
ctx.workspaces.openFile— the sanctioned entry point for editor tabs. - Add a settings page with
registerSettingsPage(theclock-extensionexample adds one). - Browse every type in the API Reference.