--- title: "Guide: manifest" description: "smabar plugin reference: Guide: manifest" canonical: "https://smabar.com/docs/plugin-ref-guide-manifest/" lang: en --- # Guide: manifest > smabar plugin reference: Guide: manifest Canonical HTML version: https://smabar.com/docs/plugin-ref-guide-manifest/ · Language: en · Publisher: smabar (https://smabar.com) --- [Build my plugin](https://smabar.com/build-my-plugin.md) · [All references](https://smabar.com/docs/plugin-ref-index.md) · [Install the ZIP](https://smabar.com/docs/install-plugin-zip.md) Source: smabar 1.5.0, revision `a019f2563965edc37a5e8d84742ea1ff2fa6f824`. [Export metadata and checksums](https://smabar.com/plugin-reference/export.json). This is an offline reference. MCP calls quoted below describe the connected workflow; use the linked documents and create local files instead. Live state, installed plugins, available providers and the user's active theme are unknown. Never claim a live check. Read with [styling](https://smabar.com/docs/plugin-ref-styling.md) and [examples](https://smabar.com/docs/plugin-ref-examples.md). ## file ```json "smabar.json" ``` ## forwardCompatibility ```json "Unknown fields and invalid optional values are ignored instead of stopping the plugin, and one source=\"core\" warning names the supported fields or values. Essential identity, protocol, runtime and launch fields remain required. If an edited manifest cannot be parsed, smabar keeps the last running instance until you save a valid replacement." ``` ## validation ```json [ "id: non-empty, [a-z0-9-] only, identical to the folder name.", "protocolVersion: must be 1.", "tiles: at least one usable tile. Keep ids unique; if an id is repeated, the first tile is kept and later duplicates are ignored with one source=\"core\" warning.", "runtime \"python\": needs `entry` (a script file in the plugin folder).", "runtime \"exec\": needs a non-empty `command` array (program plus arguments).", "iconSvg: optional per-tile branding icon, at most 8192 UTF-8 bytes. The shell requires exactly one root and sanitizes it: scripts, event handlers, styles, classes and external URLs never render.", "accent / accent2 / accentFg: simple CSS color values, at most 128 chars, no ';', '{', '}' or url() other than data:/https:.", "settingsSchema: optional JSON Schema, read by the plugin through app.settings and rendered by the settings panel — what it can render, and the coverLayout convention: manifest.settingsSchema below." ] ``` ## pythonHeader ```json "uv prepares a python plugin's environment from its entry file (`uv sync --script`) and smabar then starts that environment's interpreter directly, so the entry file MUST start with a PEP 723 header:\n# /// script\n# requires-python = \">=3.12\"\n# dependencies = []\n# ///\nDeclare third-party packages in `dependencies`; uv resolves them on first start (which is why a first start can take a minute)." ``` ## tileFields ```json { "id": "Stable tile id. Referenced by every app.render call and by plugin_order_set as plugin::.", "name": "Human-readable name, shown in settings and menus.", "hasFlyout": "true when clicking the tile opens flyout content.", "tile": "\"card\" or \"flat\" — overrides the global appearance.tileChrome for this tile.", "iconSvg": "Optional custom branding icon as inline SVG code. It is set by writing smabar.json through plugin_write_file, works for bundled and community plugins, and the shell prepends it to your tile HTML independently of usePluginIcon. A usable SVG takes precedence if both are set. Render empty tile HTML for an intentional compact icon-only tile. Keep it to one root and at most 8 KiB; scripts, on* handlers, styles/classes and external URLs are stripped before rendering.", "usePluginIcon": "Optional boolean, false by default. Set true to prepend the plugin-folder app icon to this tile's cover HTML; a usable iconSvg takes precedence. A missing or invalid image adds no cover icon. Omit both usePluginIcon and iconSvg for no extra icon, such as a clock cover. Icons you render inside tile HTML remain your own layout choice. Settings cards and the sort list use the app icon independently of this flag. See folderIcon for files and examples.", "accent": "Optional branding color; overrides --sb-accent inside this plugin's tile, flyout and popup ONLY while the user's appearance.pluginAccent is plugin. The default theme keeps every plugin on the theme accent so headers and accent surfaces look alike — omit it unless the color is part of the brand, and design for the theme accent first.", "accent2": "Second branding color (--sb-accent-2); falls back to accent. Same gate as accent.", "accentFg": "Optional text color on accent surfaces (--sb-on-accent). You can leave it out: the shell derives a readable one from your accent, and overrides yours if it would be unreadable on it. Set it only to pick a specific brand color that already has enough contrast. Same gate as accent." } ``` ## modules ```json "A plugin may be several files: the process runs inside its own folder, so `import views` finds a sibling views.py — split markup (views.py), storage (model.py) and glue (plugin.py) the way the template does. Write every sibling with plugin_write_file BEFORE smabar.json: the manifest write is refused while the entry script is missing, and an imported sibling that is missing fails the start with \"ModuleNotFoundError: No module named 'views'\" as the reply's error." ``` ## settingsSchema ```json "optional JSON Schema; the settings UI and MCP render it, and the plugin reads the values through app.settings. The Settings › Plugins panel renders exactly: string (text), number/integer (number), boolean (switch), a string enum (choice grid) and an array of strings (one comma-separated field). Anything structured — an array of objects such as places with coordinates — shows only \"edit in the plugin\": your flyout is its editor (search + add/remove rows), persisted with app.set_settings. Convention for the tile's look: a string property named coverLayout whose enum lists the cover layout ids you implement (with a default) renders as a choice grid — branch your tile render on it in on_settings_changed. Layout ids and markup: ui_kit(sections=[\"coverLayouts\"])." ``` ## folderIcon ```json { "files": "Optional icon file directly beside smabar.json. No manifest field is needed for settings; covers opt in separately with usePluginIcon. The first usable file wins in this order: icon.png, icon.jpg, icon.jpeg, icon.ico, icon.webp (lowercase filenames).", "image": "Design at 128x128 pixels. The core decodes PNG, JPEG, ICO and WebP, fits the image proportionally into a centered 128x128 PNG, and preserves existing alpha; unused space is transparent. Opaque app icons are fully supported. JPEG itself has no alpha. Icons are static.", "limits": "Regular files only, no symlinks. Maximum source size: 524288 bytes (512 KiB); maximum decoded width and height: 2048 pixels each; decoder allocation budget: 32 MiB. Invalid or excessive images are skipped with the existing deduplicated core diagnostic and the next format is tried; the plugin remains usable.", "usage": "Plugin settings and the sort list use the normalized image automatically, after a usable per-tile iconSvg and before the generic settings symbol. Bar covers only use it when the tile sets usePluginIcon: true; iconSvg still takes precedence. Without either choice, the shell adds no cover icon. Folder changes trigger the existing plugin reload, including icon replacement or removal; deactivated plugins keep their settings icon without starting a process. The source file is never rewritten and no plugin-code asset access is granted to the webview.", "writing": "Store actual image bytes beside smabar.json using filesystem/image tools, or ship the image in the plugin bundle. The installed folder is plugin_list.dir. plugin_write_file writes UTF-8 text only; passing base64 text as icon.png does not create a PNG.", "coverExamples": { "none": { "id": "clock", "name": "Clock" }, "pluginImage": { "id": "status", "name": "Status", "usePluginIcon": true }, "customSvg": { "id": "custom", "name": "Custom", "iconSvg": "" } } } ```