Manifest reference
Every add-on is a folder with a manifest.json at its root. The manifest says what the add-on is, which Budvo versions it works with, what it may ask for and where its files are.
{ "$schema": "https://budvo.nulldaily.com/schemas/addon-manifest-v1.json", "manifest": 1, "id": "pixel-pets", "name": "Pixel Pets", "version": "1.2.0", "kind": "feature", "runtime": "app@1", "publisher": "NullDaily", "summary": "Raise a pixel pet. Feeding it feeds your debt payoff.", "compat": { "budvo": ">=0.4.0 <1.0.0", "sdk": "^1.0.0" }, "entry": "app/index.html", "icon": "icon.png", "permissions": ["summary:read", "jar:pledge"], "pledge": { "daily_max_cents": 500, "reason": "Food, toys and treats for your pet go into your jar." }, "network": [], "ui": { "nav": { "label": "Pets", "icon": "paw-print" } }}Fields
| Field | Required | Meaning |
|---|---|---|
manifest | yes | Manifest format version. Always 1 for now. |
id | yes | Lowercase letters, digits and dashes, 3 to 40 characters, like pixel-pets. Unique across the marketplace, and never changes once published. |
name | yes | What people see, up to 40 characters. |
version | yes | The add-on's version, in semver: 1.2.0. |
kind | yes | theme, feature or connector, for the catalog. It must match the runtime (below). |
runtime | yes | theme@1, app@1, or (later) connector@1. |
publisher | yes | You, or your company. |
summary | no | One sentence for the catalog, up to 200 characters. |
compat.budvo | yes | A semver range of Budvo server versions the add-on supports. |
compat.sdk | app@1 | A semver range of the SDK and bridge protocol it was built for. |
entry | yes | theme@1: the tokens file. app@1: the HTML page loaded in the sandbox. A path inside the folder. |
icon | no | A PNG or SVG in the folder, around 256 by 256. |
permissions | yes | What the add-on may ask the bridge for. Use [] for none. |
pledge | with jar:pledge | daily_max_cents (the most it may pledge per day) and reason (a sentence shown to the owner). |
network | no | Hostnames the add-on may fetch through net.fetch. Empty or missing means none. |
league | with league:share | shares: what the add-on shares with a league, up to 120 characters, shown to the owner and each person. |
ui.nav | no | Adds a page to the dashboard's Add-ons menu: a label (up to 24 characters) and a Lucide icon name. |
Runtimes and kinds go together:
| Runtime | Kind |
|---|---|
theme@1 | theme |
app@1 | feature |
connector@1 | connector |
Catalog fields
The marketplace also reads these when you publish. Budvo servers ignore them.
| Field | Meaning |
|---|---|
description | The long description, in markdown. Without it, the folder's README.md is used. |
tags | Up to 8 short lowercase words, like ["game", "pixel-art"]. |
notes | Release notes for this version, in markdown. |
price_cents, billing, currency | For paid add-ons: billing is free, one_time or subscription. Everything is free for now. |
Permissions
| Permission | Bridge methods | Needs |
|---|---|---|
summary:read | summary.read | |
debts:read | debts.read | |
goals:read | goals.read | |
jar:read | jar.read | |
jar:pledge | jar.pledge | a pledge object |
storage | storage.get, storage.set | |
net | net.fetch | at least one host in network |
league:share | league.create, league.join, league.list, league.publish, league.read, league.invite, league.leave, league.delete | a league object |
context.get, ui.toast and ui.resize need no permission. Ask only for what you use: owners see the list before turning your add-on on, and reviewers ask about anything that looks unneeded. theme@1 add-ons can't have permissions.
Pledges
"pledge": { "daily_max_cents": 500, "reason": "Food, toys and treats for your pet go into your jar."}daily_max_centsis the most the add-on may pledge in a day. The owner can set a lower limit, never a higher one. Keep it small: a few dollars is plenty for a game.reasonis shown to the owner next to the limit. Say plainly what earns a pledge.- Amounts are integer cents in the household's currency.
A pledge only adds to the jar. Above the day's limit the rest is dropped and the bridge answers with capped: true; see jar.pledge.
Network
"permissions": ["net"],"network": ["api.elections.kalshi.com"]Hostnames only: no https://, port, path or wildcard. List each subdomain you use. Requests are GET only, and responses must be JSON or text up to 1 MB. With the net permission you need at least one host; without it, leave network empty.
Leagues
"permissions": ["storage", "league:share"],"league": { "shares": "display name, picks and points" }Friends leagues are kept on the Budvo website and only take league data: a display name, a random member id per league and pick entries in a fixed shape (no amounts, stakes or jar). Say plainly what you share in shares; the owner reads it next to the "Share leagues" switch, and each person sees it before agreeing. See pc.league.
Navigation
"ui": { "nav": { "label": "Pets", "icon": "paw-print" } }Adds a page to the dashboard's Add-ons menu that shows your entry. The icon is any Lucide name in kebab case.
Compat and versions
Your add-on's version
- Use semver. Bump the patch for fixes (
1.2.1), the minor for new things (1.3.0) and the major when something changes in a way people would notice, like new permissions or a bigger pledge limit. - A published version never changes. To ship anything, even a typo fix, bump
versionand publish again.
compat.budvo
A semver range of the Budvo server versions your add-on works with. It's required, and it's checked when an add-on is installed, every time the server starts, and before the server updates itself: the update screen warns about add-ons the new version would switch off. A server outside the range won't load the add-on and says why.
| Range | Means |
|---|---|
>=0.4.0 <1.0.0 | 0.4.0 up to, not including, 1.0.0. The usual choice. |
^0.4.0 | 0.4.x only, since versions before 1.0 treat each minor as a major. |
>=0.4.0 | 0.4.0 and anything later. Risky: a future major could break it. |
Development builds count as their base version: a server running 0.4.0-dev matches >=0.4.0.
compat.sdk
For app@1 add-ons: the range of SDK and bridge protocol versions you built against, usually ^1.0.0. Servers publish the SDK version they speak in /api/status (for example "sdk": "1.0.0").
- Breaking changes to the bridge bump the SDK major, to
2.0.0. - Servers keep speaking every major they support, so an add-on built for 1.x keeps working after 2.0 arrives.
- An add-on whose
compat.sdkisn't satisfied isn't loaded.
More examples
A theme:
{ "manifest": 1, "id": "theme-midnight", "name": "Midnight", "version": "1.0.1", "kind": "theme", "runtime": "theme@1", "publisher": "NullDaily", "compat": { "budvo": ">=0.2.0 <1.0.0" }, "entry": "theme.json", "permissions": [], "summary": "Deep navy surfaces with an indigo accent.", "tags": ["dark", "blue"]}An app@1 add-on with network access and storage:
{ "manifest": 1, "id": "forecast-pledges", "name": "Forecast Pledges", "version": "1.0.0", "kind": "feature", "runtime": "app@1", "publisher": "NullDaily", "compat": { "budvo": ">=0.4.0 <1.0.0", "sdk": "^1.0.0" }, "entry": "app/index.html", "icon": "icon.svg", "permissions": ["net", "jar:pledge", "storage"], "pledge": { "daily_max_cents": 1000, "reason": "When a call settles, $1 goes into your jar if you called it and $3 if you didn't." }, "network": ["api.elections.kalshi.com"], "ui": { "nav": { "label": "Forecasts", "icon": "sparkles" } }}An add-on with no permissions at all, which can still show pages and toasts:
{ "manifest": 1, "id": "breathing-break", "name": "Breathing Break", "version": "0.1.0", "kind": "feature", "runtime": "app@1", "publisher": "You", "compat": { "budvo": ">=0.4.0 <1.0.0", "sdk": "^1.0.0" }, "entry": "index.html", "permissions": []}Check it
npx budvo-addon validate my-addonThe validator checks the manifest against the schema, the semver ranges, permission names and what they need, that the entry and icon exist, theme tokens and colors, files the sandbox would block, and the 5 MB size limit. It exits with an error code when something's wrong, so it fits in CI; --strict fails on warnings too.