Foxel
Menu
On this page

Plugin Development Guide

Decide whether you need file mode, standalone mode, or backend extensions before filling out the manifest. Start with the complete example, then add features incrementally.

Environment and workflow

  • Use Bun and the Vite / TypeScript versions specified by your plugin project. Align development dependencies with Foxel's web/package.json when using shared components.
  • The current Foxel pyproject.toml requires Python >=3.14. Frontend-only plugins execute no Python at runtime; the example packer uses the Python standard library.
  • Prepare a Foxel development instance where you can sign in and install plugins, plus a test file you can read.
text
Write source → typecheck → build IIFE / CSS → package .foxpkg
             → install in Foxel → open a file or app → rebuild and reinstall

The host supplies FoxelRegister, shared dependencies, and file context. An ordinary vite dev page does not receive them automatically; a separate mock host is needed for standalone previews. Use a real Foxel installation for integration checks.

Plugin key

Use a reverse domain name such as com.example.jsonviewer or io.github.username.viewer. The host validates it with:

regex
^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$

Use at least two segments, each starting with a lowercase letter and containing only lowercase letters and digits. my-plugin, com.example.my-plugin, and com.example.MyPlugin are invalid. Matching directory and archive names are recommended; the manifest key determines identity. Keep it stable after release.

This configuration includes the basic metadata needed for both local installation and App Center uploads. Replace the sample author and website.

json
{
  "foxpkg": "1.0",
  "hostApiVersion": ">=1.0,<2.0",
  "key": "com.example.jsonviewer",
  "name": "JSON 查看器",
  "version": "1.0.0",
  "description": "在 Foxel 中格式化并查看 JSON 文件。",
  "i18n": {
    "zh": {
      "name": "JSON 查看器",
      "description": "在 Foxel 中格式化并查看 JSON 文件。"
    },
    "en": {
      "name": "JSON Viewer",
      "description": "Formats and previews JSON files in Foxel."
    }
  },
  "author": "Your Name",
  "website": "https://example.com",
  "license": "MIT",
  "frontend": {
    "entry": "frontend/index.js",
    "styles": [
      "frontend/player.css"
    ],
    "icon": "frontend/icon.svg",
    "publicDir": "frontend",
    "capabilities": [],
    "openApp": false,
    "supportedExts": [
      "json"
    ],
    "defaultBounds": {
      "width": 900,
      "height": 700
    },
    "useSystemWindow": true
  }
}

Top-level fields

FieldLocal installationMeaning
key, nameRequiredUnique identity and default display name
foxpkgDefaults to "1.0"Supported package format is 1.0; declare it explicitly
hostApiVersionDefaults to ">=1.0,<2.0"Python packaging specifier; npm syntax such as ^1.0.0 is invalid
versionDefaults to "1.0.0"Prefer an explicit major.minor.patch
description, author, websiteOptionalRequired and nonempty for App Center uploads
i18nOptionalname / description by zh and en; App Center requires i18n.en.name and i18n.en.description
github, licenseOptionalSource URL and license
frontend, backendOptionalDeclare the extensions you implement
dependenciesOptionalPython and package requirements; validated, never installed automatically

App Center also requires explicit foxpkg: "1.0" and version, with strictly three numeric version segments. repository is extra metadata understood by App Center (a URL or an object containing url), rather than a current typed Foxel manifest field. Prefer github for source links. See build and package for publishing requirements.

frontend fields

Archive paths are relative to the plugin root. Use /, no leading slash, and no .. segments.

FieldDefault / behavior
entryExplicit script path for frontend plugins, such as frontend/index.js
stylesCSS path array; the host inserts <link> elements before loading the entry. Emitted CSS is not loaded automatically
publicDirDefaults to frontend; a public subdirectory, never . or the plugin root
assetsExtra public file paths, including resources outside the public directory
iconPrefer a bundled frontend/icon.svg; relative paths must exist
capabilitiesDefaults to []; only monaco, artplayer, react-markdown are supported
supportedExtsPrefer lowercase extensions without dots, such as ["json", "txt"]
openApptrue exposes a standalone app entry and requires mountApp
defaultBoundsDefault window size, such as { "width": 900, "height": 700 }
defaultMaximizedWhether to maximize initially
useSystemWindowUsually true for the system window frame; with false, provide your own window UI

Bundle every declared entry, stylesheet, asset, and local icon. Files in the public directory can be served by the resource API, so keep it limited to public resources. Python files, backend entries, hidden paths, and manifest.json are excluded. See frontend development.

backend and dependencies

json
{
  "backend": {
    "routes": [
      {
        "module": "backend/routes/api.py",
        "prefix": "/api/com.example.jsonviewer",
        "tags": ["JSON Viewer"]
      }
    ],
    "processors": [
      {
        "module": "backend/processors/format_json.py",
        "type": "com.example.jsonviewer.format",
        "name": "JSON Formatter"
      }
    ]
  },
  "dependencies": {
    "python": ">=3.14",
    "packages": ["httpx>=0.28,<1"]
  }
}

module is a Python file path inside the archive, not an import path such as backend.routes.api. Route files export router. Processor files export the fixed name PROCESSOR_FACTORY; a manifest factory field does not override it, and type is required.

Add dependencies only when needed. Foxel checks packages and versions already installed in its environment; it does not run pip. URL and extras dependencies are currently unsupported by automatic validation.

Adapting an existing plugin

Start with the official Markdown plugin, also available locally under plugins/cc.foxel.markdown. Update its manifest identity, extensions, capabilities, and metadata; update the package.json name and version; and update pack.py file lists and output name. Preserve the lockfile and inspect the actual build scripts. Remove capabilities your plugin does not use.

Use web/src/plugins/sdk/v1.ts and externals.ts as references for a local src/contract.ts containing the types you need. Those are source paths, not import paths for a published npm SDK.

Next steps