Plugin manifest reference
Complete field reference for manifest.json, validated by pluginManifestSchema in @bodegasuite/schemas.
Every PuntoSuite plugin ships a manifest.json at its root. The desktop loader reads it before
loading any code, and validates it with pluginManifestSchema from @bodegasuite/schemas
(source: packages/schemas/src/plugin-manifest.ts). If validation fails, the plugin is rejected at
discovery — it never runs.
Validation is strict for the fields below, but the schema is passthrough: unknown top-level keys are preserved rather than rejected, so you may add your own metadata. Only the documented fields are interpreted by the host.
Minimal example
The smallest manifest that loads — no data access, no Node built-ins:
{
"id": "com.acme.hello",
"name": "Hello",
"version": "0.1.0",
"entry": "dist/index.js",
"bodegasuite": { "version": ">=0.1.0" }
}
With permissions omitted it defaults to [], and with capabilities omitted a non-bundled plugin
gets no Node built-ins (deny-all) — it still receives the full mediated BodegaAPI.
Fuller example
A plugin that reads sales, makes outbound HTTPS calls to one host, and depends on another plugin:
{
"id": "com.acme.sales-sync",
"name": "Sales Sync",
"version": "1.4.0",
"description": "Mirrors completed sales to an external endpoint.",
"author": "Acme Co.",
"license": "MIT",
"entry": "dist/index.js",
"bodegasuite": { "version": ">=0.1.0", "apiVersion": "v1" },
"permissions": ["ipc:sales:read"],
"capabilities": ["net"],
"net": { "allow": ["api.acme.example"] },
"dependencies": { "com.acme.core": ">=1.0.0" },
"optionalDependencies": { "com.acme.theme": ">=0.2.0" }
}
Field reference
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | yes | Reverse-DNS, lowercase. See rules below. |
name | string | yes | Display name. Non-empty. |
version | string (semver) | yes | Your plugin's version. Non-empty. |
description | string | no | One-line summary. |
author | string | no | Author or org. |
license | string | no | SPDX identifier (e.g. MIT). |
entry | string (relative path) | yes | Path to the built ESM module. See rules below. |
bodegasuite.version | string (semver constraint) | yes | Host version range (e.g. ">=0.1.0"). |
bodegasuite.apiVersion | string | no | BodegaAPI surface, currently "v1". |
permissions | string[] | no | Gate BodegaAPI data surfaces. Defaults to []. |
capabilities | string[] | no | Gate Node built-ins. Omit = deny-all (non-bundled). |
net.allow | string[] | no | Host allowlist used with the net capability. |
dependencies | object | no | { "<pluginId>": "<semver>" }. Affects load order. |
optionalDependencies | object | no | Same shape; optional, affects load order. |
id
A stable reverse-DNS identifier (e.g. com.acme.sales-sync). The id doubles as a filesystem
directory name on install and as a config-dir segment, so it must be a single safe path segment.
- Lowercase letters, digits, and
.,-,_only. - Must start and end with a letter or digit —
.,-,_cannot lead or trail. - 1–214 characters.
- No path separators (
/,\), no:, no..traversal. - The
org.bodegasuite.*namespace is reserved for official plugins — the scaffolder rejects it.
Validation regex: ^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$.
version
A semver string. It is the version shops see, and the marketplace requires a higher semver to publish an update. Structurally it must be a non-empty string.
entry
A relative POSIX path to the built ESM file, typically dist/index.js. It is joined with the plugin
root and dynamically imported, so it is constrained to prevent escaping the plugin folder:
- Must be relative — no leading
/or\, no Windows drive letter (C:). - No
..traversal segments. - Non-empty.
bodegasuite
Host compatibility block.
version— a semver constraint describing which host versions you support (e.g.">=0.1.0"). Required.apiVersion— optional; the BodegaAPI surface you target. The current surface is"v1".
permissions
Tokens that gate BodegaAPI data surfaces (the db.* read façades). Unknown tokens fail
validation, so typos and over-broad requests are caught at discovery. Defaults to [].
| Token | Grants |
|---|---|
ipc:products:read | db.products.getById(id) / db.products.list(opts) |
ipc:sales:read | db.sales.getById(id) / db.sales.list(opts) |
These are the only permission tokens today. Both façades are read-only and auto-scoped to the
active business by the host. Calling a db.* method without its permission rejects. See
Hooks & API for the call surface and Security
for the trust model.
capabilities
Tokens that gate Node built-in modules — a different axis from permissions. For non-bundled
plugins the default is deny-all: omit the field and you get no Node built-ins (you still get the
full mediated BodegaAPI). Declare only what you truly need.
| Token | Unlocks |
|---|---|
net | Network egress (host-mediated) |
fs | Filesystem access |
child_process | Spawning child processes |
worker | Worker threads |
Note: the schema's enum is the source of truth (
net,fs,child_process,worker). For backward compatibility, whencapabilitiesis absent the legacy "no-restriction" mode is preserved; declaring the array switches the plugin to deny-everything-not-listed. New plugins should always declarecapabilitiesexplicitly.
net.allow
The outbound-host allowlist that pairs with the net capability: egress is restricted to these hosts
(exact or subdomain match), HTTPS-only, and SSRF-guarded by the host. An empty or absent list
means deny-all outbound.
Not yet on the v1 surface: there is no
BodegaAPI.net.fetch(or anyapi.net.*) method today — the mediated network API is still being finalized. Declaringnet+net.allowis forward-looking: it states your intent and is what review checks against, but a stable plugin-callable fetch is not part of v1 yet. Don't ship a plugin that depends on calling out to the network until it lands.
"capabilities": ["net"],
"net": { "allow": ["api.acme.example", "cdn.acme.example"] }
dependencies / optionalDependencies
Each is a record of { "<pluginId>": "<semverConstraint>" }. They influence load order so a
plugin loads after the plugins it relies on. Use optionalDependencies for soft ordering when the
dependency may be absent.
"dependencies": { "com.acme.core": ">=1.0.0" },
"optionalDependencies": { "com.acme.theme": ">=0.2.0" }