Skip to content
ZyncDocsGitHub Download Zync

Plugins and Extensions

Install, review, and build Zync plugins, panels, themes, icon themes, and editor providers.

Zync extensions are local packages. Install them from Settings > Plugins > Marketplace, inspect them under Installed, or load a local package from Developer. A restart may be required after an install or toggle because workers and editor providers are initialized during startup.

Type Runtime Typical use
Tool or background plugin Sandboxed Web Worker Commands, status-bar values, notifications, and background integration
Panel plugin Sandboxed iframe A custom HTML interface registered as a workspace panel
Color theme Host theme registry for trusted built-ins App shell and terminal colors
Icon theme Plugin asset resolver File-type and service icons
Editor provider Sandboxed editor iframe An alternative editor for local and remote files

The main.js entry is executed in a Web Worker. It has no DOM or module loader, so bundle dependencies into the package or use browser-compatible JavaScript. A panel registered by the worker is rendered separately in an iframe.

Declare the capabilities your package uses in manifest.json so users and marketplace reviewers can inspect the request:

manifest.json
{
"id": "com.example.plugin.deploy",
"name": "Deploy helper",
"version": "1.0.0",
"main": "main.js",
"permissions": ["terminal", "statusBar", "ui", "panel"]
}

The host bridge mediates privileged operations. Sending terminal input, opening a terminal with a command, or running an SSH command requires a user confirmation. File and SSH results are returned to the requesting runtime only. Keep permissions narrow and do not hide destructive operations behind a command with an unclear label.

Plugins do not receive raw Local Vault records, decrypted SSH credentials, provider API keys, or vault passphrases. A plugin can only use the APIs exposed by its bridge and the data a user explicitly makes available through an action.

Register work from the ready event:

main.js
zync.on('ready', () => {
zync.commands.register('example.ping', 'Example: Ping', async () => {
zync.ui.notify({ type: 'info', message: 'Pong' });
});
zync.statusBar.set('example', 'Ready');
});

The worker API includes:

  • zync.commands.register(id, title, handler) for command palette entries
  • zync.terminal.send(text) for terminal input after confirmation
  • zync.statusBar.set(id, text) and zync.statusBar.clear(id)
  • zync.ui.notify(options) and zync.ui.onNotifyAction(callback)
  • zync.fs.readFile, writeFile, ls, exists, and mkdir
  • zync.window.showQuickPick(items, options) and zync.window.create(options)
  • zync.plugins.list() for the loaded plugin descriptors
  • zync.theme.set(themeName) for an allowed theme choice
  • zync.logger.log(message) for app logs

Use Notifications for the plugin notification payload and action response contract.

Register a panel with HTML that can use the injected window.zync shim:

main.js
zync.on('ready', () => {
const html = `
<html>
<body>
<button id="uptime">Run uptime</button>
<pre id="output"></pre>
<script>
document.querySelector('#uptime').onclick = async () => {
const result = await window.zync.ssh.exec('uptime');
document.querySelector('#output').textContent = result;
};
</script>
</body>
</html>`;
zync.panel.register('example.panel', 'Example panel', html);
});

Panel HTML runs in an iframe with scripts and modal dialogs enabled, but it does not get access to the parent document. The parent validates the message source and generation before applying a request. Terminal input and SSH execution still require confirmation.

Keep panel content self-contained. Use textContent for command output and escape any user or host data before inserting it into HTML.

An editor provider is a plugin with type: "editor-provider". Its editor.entry points to the HTML entry file. Capabilities and selection metadata are declared in the manifest:

manifest.json
{
"id": "com.example.editor.my-editor",
"name": "My Editor",
"version": "1.0.0",
"type": "editor-provider",
"editor": {
"entry": "editor.html",
"displayName": "My Editor",
"priority": 100,
"defaultFor": [".md"],
"supports": ["search", "replace", "syntax-highlight", "save"],
"fileExtensions": [".md"],
"largeFileLimitMb": 20
}
}

The editor entry runs in an iframe with sandbox="allow-scripts" and an opaque origin. It communicates with Zync through the injected window.zyncEditor bridge:

  • emitReady() when the editor is initialized
  • emitChange({ content }) when the document changes
  • emitDirtyChange(dirty) for unsaved state
  • requestSave(content) to ask Zync to persist the document
  • requestClose() to close the editor
  • reportError(code, message, fatal) for recoverable or fatal errors

The host sends zync:editor:bootstrap and document messages back to the frame. Folding is provided by the editor itself. A provider should declare only capabilities it actually implements.

An icon theme uses type: "icon-theme" and an iconsPath directory containing its SVG assets:

manifest.json
{
"id": "com.example.icons.my-icons",
"name": "My Icons",
"version": "1.0.0",
"type": "icon-theme",
"iconsPath": "icons/"
}

The icon resolver maps file extensions and known service names to files under iconsPath. Keep filenames and mappings aligned with the assets shipped in the package. Zync falls back from the active icon theme to local cache, Lucide, and a generic icon when an asset is missing.

Color theme manifests use style, mode, preview_bg, and preview_accent:

manifest.json
{
"id": "com.example.theme.my-theme",
"name": "My Theme",
"version": "1.0.0",
"style": "theme.css",
"mode": "dark",
"preview_bg": "#10131a",
"preview_accent": "#7c9cff"
}

Theme CSS targets Zync’s theme variables under a theme selector. Use the built-in themes as references and test the terminal ANSI colors as well as the app shell. For the current built-in list and count, see Themes.

For security, only app-owned built-in theme CSS is injected into the host document. Third-party plugin styles remain sandboxed, and editor-provider styles stay inside the editor iframe. Do not design a third-party theme that depends on writing global host CSS unless the runtime explicitly adds support for it.

Put manifest.json at the root of the archive with its referenced files:

Terminal window
zip -r my-plugin.zip manifest.json main.js

For a panel or editor, include the HTML and any bundled assets too:

Terminal window
zip -r my-editor.zip manifest.json editor.html dist/

In Settings > Plugins > Developer, choose the local install option and select the package or plugin directory supported by your build. Disable or remove a package from Installed if it misbehaves, then restart when Zync requests it.

The official registry is maintained in zync-extensions. A marketplace entry needs at least id, name, version, and downloadUrl:

registry.json
{
"id": "com.example.plugin.deploy",
"name": "Deploy helper",
"version": "1.0.0",
"description": "Runs a reviewed deployment workflow.",
"author": "Example",
"type": "plugin",
"downloadUrl": "https://example.com/releases/deploy-1.0.0.zip"
}

Zync can also read a self-hosted HTTPS registry as a flat array or an object with plugins and themes arrays. Keep download URLs stable and publish checksums or release signatures through your distribution channel when possible.

  1. Fork the zync-extensions repository.
  2. Package the extension with manifest.json at the archive root and publish the archive at a stable HTTPS release URL.
  3. Add or update its entry in the registry with the current version, type, download URL, and descriptive metadata.
  4. Validate that a clean Zync installation can download, install, enable, and remove the package.
  5. Open a pull request with the registry change and the extension source or review link.

The in-app path is Settings > Plugins > Marketplace for published packages and Settings > Plugins > Developer for local development installs.

Worker plugins can request a toast, inbox record, or both through zync.ui.notify. Payloads are JSON only, so action entries contain IDs and labels rather than JavaScript functions:

main.js
await zync.ui.notify({
type: 'error',
message: 'Deploy failed',
persist: true,
channel: 'both',
actions: [{ id: 'retry', label: 'Retry' }],
});

Register zync.ui.onNotifyAction when an actionable notification needs a worker response. Zync namespaces plugin notification IDs by plugin, waits for a bounded response, and keeps the original notification visible when the action fails or times out. See Notifications for the end-user inbox and preference behavior.