Quick Start¶
Extension Structure¶
my-extension/
├── spindle.json # required — extension manifest
├── src/
│ ├── backend.ts # runs in an isolated Bun worker
│ └── frontend.ts # runs in the browser
├── dist/ # compiled output
│ ├── backend.js
│ └── frontend.js
├── tsconfig.json
└── package.json
- Create a GitHub repo with this structure
- Add a
spindle.jsonmanifest - Write your backend and/or frontend modules
- Build to
dist/(or let Lumiverse auto-build fromsrc/) - Users install via the Extensions panel or
POST /api/v1/spindle/install
Backend vs Frontend¶
Extensions can have a backend module, a frontend module, or both.
- Backend (
dist/backend.js) — runs in an isolated Bun worker thread. Has access to thespindleglobal API. Cannot access the DOM or Lumiverse internals directly. - Frontend (
dist/frontend.js) — loaded in the browser via dynamic import. Receives a context object for DOM rendering, events, and backend communication. Most extension UI should render directly into host DOM roots withctx.dom.*orctx.ui.*. When you need isolated scriptable HTML, use the host-managedctx.dom.createSandboxFrame()orctx.messages.renderWidget()APIs instead of creating raw iframes.
If your dist/ folder doesn't exist but src/backend.ts and/or src/frontend.ts do, Lumiverse will auto-build them with bun build on install.