Plugin API Reference
Import types from @eidos.space/plugin-sdk using import type. Eidos supplies the context when it calls mount or activate.
Declare a view
Section titled “Declare a view”File templates and dashboards
Section titled “File templates and dashboards”In Lite plugin API 3.2, fileTemplates adds entries to New File. Each entry
declares id, title, extension, view, and static UTF-8 content (up to
256 KiB). The referenced document View must have a matching file/open
placement. The host creates the file without overwriting and opens that View.
To compose existing content, a file View with workspace.files.read can use
the optional ctx.capabilities.ui.resources interface:
const resources = ctx.capabilities.ui.resourcesif (!resources) throw new Error("Resource composition is unavailable")const views = await resources.listViews("./downloads.eidos")const view = views.find((item) => item.kind !== "file")if (!view) throw new Error("No saved views")const panel = await resources.mount(container, { kind: "eidos-view", path: "./downloads.eidos", tableId: view.tableId, viewId: view.viewId,})ctx.subscriptions.add(panel)Use { kind: "file", path: "./notes.md" } for text, Markdown or image previews.
For ordinary files such as GPX, listViews(path) returns matching enabled plugin
Views and the built-in preview as { kind: "file", editor, name, type }.
Pass the chosen editor with the file source to retain the selected View.
Paths resolve from the dashboard file and remain inside its Space. Panels are
read-only, limited to 12, and cannot contain nested dashboards. panel.refresh()
reloads the reference. Embedded plugin Views receive
ctx.presentation?.mode === "embedded" and can simplify their toolbar.
The host manages rendering and grants; the dashboard never receives child
plugin handles. Use ordinary rectangular containers without transforms or
overlapping controls. For a floating menu, use a native HTML popover; Lite
clips resource surfaces beneath the most recently opened popover without resizing
their content. This capability is currently available in Lite.
Set pointer-events: none on resource containers during layout editing to let
drag and resize gestures cross the previews. Restore it to interact with charts.
Each entry in views requires id, title, entry and kind. kind is page or file; access is read or write and defaults to read.
File views declare their data interfaces in capabilities: document for text editing, or a combination of eidos/schema, eidos/table and eidos/config for file structure, the current table and plugin configuration. For direct file reads or media playback, use fs and omit capabilities. Text and Eidos interfaces apply to different file formats and cannot be combined.
Choose a placement: navigation and plugin/settings open a page; file/open opens a file by extension; table/view opens a file view with eidos/table within a table.
In Lite, the sidebar footer shows Settings, Plugins, and up to three standalone pages from enabled plugins. Pages appear in their default order until the user changes it. With more than three pages, the … menu lists all pages and supports pinning and drag reordering, including keyboard operation through the drag handle. Up to three pinned pages are guaranteed a visible slot; remaining slots follow the saved order. Pins and ordering are saved locally per Space. Narrow sidebars display icons with name tooltips. Pages dedicated to plugin/settings or sidebar/explorer are excluded unless they also declare a navigation placement. Showing an entry does not load its page contents.
requires.pluginApi declares the required API; these examples use "3.0.0". apiVersion is 1, while the plugin’s own version identifies its releases.
Add an optional description to the manifest to explain what your plugin does.
Use a nonempty line of plain text, up to 1024 UTF-16 code units. Lite shows this
summary in plugin lists and details, including for locally installed packages.
If it is absent, Lite uses the plugin’s marketplace description when available.
"description": "Keep Markdown filenames and existing leading H1 headings in sync."Current file and page
Section titled “Current file and page”Check binding.kind before accessing the binding. A page supplies route; a file supplies file metadata and can describe a table location.
if (ctx.binding.kind === "page") { root.textContent = ctx.binding.route} else { root.textContent = ctx.binding.file.name ?? "File"}FileRef members such as path, name and size depend on the environment. Its id identifies the file within the current context; use stable identifiers from your application data for persistent references.
Read and edit text
Section titled “Read and edit text”Declare capabilities: ["document"] and use ctx.capabilities.document. Writing also requires access: "write".
| Method | Purpose |
|---|---|
read() |
Read a snapshot including text, version and dirty |
observe(listener, onError?) |
Get an initial snapshot and subscribe to changes |
edit({ text, expectedVersion }) |
Update unsaved content; returns applied or stale |
save() |
Save the file; returns saved or conflict |
undo() / redo() |
Undo or redo an edit and return a snapshot |
This function body converts the current text to uppercase and saves it:
const document = ctx.capabilities.documentif (!document) throw new Error("Document capability required")const current = await document.read()const result = await document.edit({ text: current.text.toUpperCase(), expectedVersion: current.version,})if (result.status === "stale") { await ctx.capabilities.ui.notify( "The text changed. Review it before editing again." ) return}const saved = await document.save()if (saved.status === "conflict") { await ctx.capabilities.ui.notify( "The file changed on disk. Your draft is still available." )}Use the version from the read snapshot as expectedVersion. On stale, preserve user input and let the user review newer content. On conflict, preserve the draft and explain the disk change. An editor handling continuous input should serialize edit calls so several requests do not use the same old version.
Read files and play media
Section titled “Read files and play media”Lite’s ctx.capabilities.fs provides readText/writeText, readBinary/writeBinary, list/stat, delete/rename, getUrl and watch. Text reads are limited to 2 MiB and binary I/O to 16 MiB. For larger media, play the streaming URL returned by getUrl.
const fs = ctx.capabilities.fsif (!fs) throw new Error("Filesystem access is required")const video = document.createElement("video")video.controls = truevideo.src = await fs.getUrl()root.append(video)File-view access is confined to the bound file and its allowed directory scope. Access throughout a Space requires workspace.files. Writes also require write access on the contribution. Direct writes, deletes and renames return BUSY for paths with an existing working copy; use the document interface for text editing, saving and conflict handling.
File properties in Lite
Section titled “File properties in Lite”Lite API 3.1 exposes complete native namespace properties through ctx.capabilities.filemeta, backed by sqlite-fs-meta. Declare the namespaces you need; writes also require access: "write" on the View or Action. A page needs workspace file read permission to select files across the Space.
{ "workspace": { "files": { "read": true }, "filemeta": { "namespaces": ["space.eidos.meta"], "write": true } }}const filemeta = ctx.capabilities.filemeta!const metadata = await filemeta.read("notes/example.md", "space.eidos.meta")await filemeta.patch("notes/example.md", "space.eidos.meta", { set: { rating: 4, tags: ["review"], custom: { checked: true } }, remove: ["obsolete"],})Reads return the whole JSON object, including keys without columns in files.eidos; an absent namespace returns {}. Patches preserve other keys. Null is a value; remove deletes keys. Paths are relative to the Space, and without workspace file read permission only the bound file is available. Property permission does not allow changing file contents. Existing corrupt JSON, symbolic links and non-files fail instead of being overwritten. Envelopes are bounded to 1 MiB and the native filesystem may impose smaller limits. These are immediate filesystem operations; serialize writers when several processes modify the same namespace. No Linux attr command-line package is needed.
Replace the sidebar explorer in Lite
Section titled “Replace the sidebar explorer in Lite”Declare a page View at sidebar/explorer, requiring plugin API 3.1.0:
{ "requires": { "pluginApi": "3.1.0" }, "workspace": { "files": { "read": true } }, "views": [ { "id": "tree", "title": "My explorer", "kind": "page", "entry": "./src/tree.ts" } ], "placements": [{ "location": "sidebar/explorer", "view": "tree" }]}After installing and enabling the plugin, open Plugins → Choose file explorer and select it. The choice is saved per Space. A replacement owns the complete explorer area, including its heading, breadcrumbs, search, sorting and file-action toolbar. Lite does not place the built-in Space heading above the plugin. Window navigation and application controls remain in the host shell, outside this contribution. Return to the built-in explorer through Plugin Manager; if a plugin fails, Lite restores the built-in heading and tree with retry and recovery actions.
At this placement ctx.capabilities.explorer.read() returns rootDirectory, activePath and sort. Root null means the Space; activePath null means no open file. explorer.watch(listener) subscribes to changes without an initial callback and returns a disposable subscription. It provides display state, while file permissions remain independently checked. Use fs.list(rootDirectory ?? "", { recursive: false, includeDirectories: true }) for each directory level, fs.watch for refreshes and ui.openFile(relativePath) when a user selects a file. Listings include empty directories; FileStat.modifiedAtMs supports time sorting. Default list options preserve recursive file-only indexing. Eidos opens files in the main area using the configured file View. The plugin owns navigation, selection styling and granted file actions within the sidebar sandbox. Global Space text search temporarily overlays the explorer and leaves the plugin mounted, preserving its browsing state. Ordinary navigation pages do not receive explorer context.
Work with Eidos files
Section titled “Work with Eidos files”ctx.capabilities.eidos contains the schema, table and config interfaces you declare. For example, a table view that reads file structure and saves plugin configuration can declare:
{ "id": "board", "title": "Board", "kind": "file", "entry": "./src/board.ts", "capabilities": ["eidos/schema", "eidos/table", "eidos/config"], "access": "write"}Add a table/view placement for this view. Each declaration provides its corresponding interface; declaration order does not affect behavior.
Browse file structure
Section titled “Browse file structure”Declare eidos/schema. Use ctx.capabilities.eidos.schema.listTables() to list tables in the file and readTable(tableId) to read fields. A file browser opens through a file/open placement matching .eidos; a table view can also read the structure of its containing file.
Query the current table
Section titled “Query the current table”Declare eidos/table and open through table/view. ctx.capabilities.eidos.table provides readContext for current schema and view information, readRows for records, aggregate for summaries, setViewConfig to replace the current view’s plugin configuration, openRecord for record navigation and watch for invalidation notifications. Read schema first and query using field IDs; these operations always target the bound table.
Save plugin configuration
Section titled “Save plugin configuration”Declare eidos/config. Use read, write and watch on ctx.capabilities.eidos.config to access your plugin’s configuration stored in the file. A file browser can select tables within that file; a table view uses its bound table ID.
const { table, config } = ctx.capabilities.eidos ?? {}if (!table || !config) throw new Error("Table and config capabilities required")const current = await config.read(table.tableId)await config.write(table.tableId, { value: { ...current.value, compact: true }, expectedVersion: current.version,})Writes require access: "write" and the version from the read result. If the version is stale, reread and reconcile the changes before writing. config.watch(tableId, listener) returns a subscription without an initial value; read initially and reread on notification. Notifications may include unrelated changes and stop after disposal.
Lite supports all three interfaces. CLI Serve provides eidos/table; declare only supported capabilities and verify with check --target cli when targeting Serve.
Settings, storage and notifications
Section titled “Settings, storage and notifications”Lite views read settings with capabilities.settings.get(key); ordinary actions can also set/reset them. capabilities.storage supplies list/read/write/delete for private binary objects addressed by key. Declare the quota in manifest.storage.maxBytes. Private storage remains on the device.
capabilities.ui.notify(message) shows a notification. In environments supporting navigation, ui.openFile(relativePath) opens a file and ui.navigate(viewId, route?) opens a page. Check optional methods before calling them.
Views can use the optional capabilities.ui.exportFile({ name, mimeType, data })
to export generated files without filesystem write permission. Supply a filename
without a directory, a media type such as image/png, and a Uint8Array of at most
16 MiB. Lite opens a save dialog and returns { status: "saved" } or
{ status: "cancelled" }. CLI Serve returns { status: "download-started" } after
initiating the browser download. Check whether the method exists and show export
failures to the user. Mobile and action contexts do not currently provide it.
capabilities.network.read(src/content/docs/plugins/api.mdx) makes bounded HTTPS requests to origins declared in browser.networkOrigins. For credentialed requests, use isConfigured(connectionId) and request({ connection, body }) on ctx.capabilities.connections with a declared connection. Connections are available to pages, file views and ordinary actions. Users configure details and secrets in Eidos; action requests are cancelled when the invocation ends.
Register commands and formatters
Section titled “Register commands and formatters”Run after local file changes
Section titled “Run after local file changes”Declare hooks and require plugin API 3.3.0:
{ "requires": { "pluginApi": "3.3.0" }, "extension": "./extension.ts", "hooks": [ { "id": "saved", "title": "After saving Markdown", "event": "document.saved", "extensions": [".md", ".markdown"], "access": "write" } ]}import type { Activate } from "@eidos.space/plugin-sdk"const activate: Activate = (ctx) => { ctx.subscriptions.add( ctx.capabilities.hooks.register( "saved", async ({ event, settings, signal }) => { signal.throwIfAborted() // Return { text } or { name } for this document, or nothing. } ) )}export default activateEvents provide the Space-relative path, operationId, source, and
document: { text, version }. document.saved includes previousText;
file.renamed includes previousPath. Hooks run only after successful local
changes. Opening, enabling, cloud/LAN updates and hook-generated changes do not
trigger them. A filename plan retains the extension and folder; existing files
and newer document versions are protected. A failed hook leaves the save or
rename in place. Multiple plugins run by ID, then declaration order, stopping
at the first effective write plan.
Use portable JavaScript: hook handlers have no filesystem, database, network, DOM or Node APIs. They receive the plugin’s resolved settings and return plans for the current document only. Add registration disposables to subscriptions. Lite supports editable text files; Android and iOS support Markdown. CLI Serve does not support hooks.
In activate, call capabilities.actions.register(id, handler) for a declared action. Its handler receives a context and performs operations through capabilities.
Register formatters with capabilities.formatters.register(id, provider). provider.format receives { text, path, signal } and returns { text }; Eidos applies an undoable edit.
Process table records in batches
Section titled “Process table records in batches”Register a table action with capabilities.actions.registerTableProvider(id, provider). getItems receives capabilities.eidos.table, read-only capabilities.eidos.config and signal and returns menu items. run also receives capabilities.target, task and connections.
target.count is the number of records in this run. Read them with target.readRows and pass each returned readToken to target.update. Before writing, submit a real output sample to task.declareOutputs and await validation; this establishes the fields the run may update. Report progress with task.report and check signal between batches. Cancellation retains completed changes.
Release resources and handle errors
Section titled “Release resources and handle errors”Add subscriptions to ctx.subscriptions, cancel asynchronous work with ctx.signal, and release interface resources in the dispose object returned from mount.
Common errors include PERMISSION_DENIED (insufficient access), INSTANCE_CLOSED (closed context), STALE_REVISION (changed data), BUSY (a managed resource), TOO_LARGE (a size limit) and TIMEOUT. Explain the failure to the user and preserve unsaved input.
See the SDK types for full signatures and the plugin specification for behavioral requirements.