The public, versioned description of every Inkvoice plugin. Two things read it:
- The Inkvoice app (self-hosted and Cloud) fetches it to populate the Plugins settings tab, including plugins your install cannot run and ones that do not exist yet.
- The inkvoice.app website renders
/pluginsand one page per plugin from the same file.
Published artifact:
https://pigontech.github.io/inkvoice-plugins/catalog.v1.json
Schemas (generated from the Zod source, published next to the artifact):
https://pigontech.github.io/inkvoice-plugins/schema/catalog.v1.schema.json and
https://pigontech.github.io/inkvoice-plugins/schema/plugin.schema.json
This repo contains no plugin code. Inkvoice plugins are compiled into the app; this is metadata only.
- Create
plugins/<id>/plugin.yaml. Copy an existing one as a starting point.<id>must match theidinside the file. - Keep the
# yaml-language-server: $schema=../../schema/plugin.schema.jsoncomment on line one. It gives you completion and inline errors in any editor with the YAML extension installed. - Run
bun install, thenbun run validateandbun test. CI runs both, so a greenvalidateon its own is not enough. - Open a PR. CI validates on every PR and publishes on merge to
main.
| Field | Notes |
|---|---|
id |
Lowercase kebab-case, immutable once published, must match the folder name. Cannot be catalog. |
category |
One of billing, compliance, productivity, integrations, reporting. |
status |
available needs at least one version. planned must have no versions and no source. |
availability |
oss ships in the self-hosted app, cloud only in Inkvoice Cloud, both in both. |
requires_feature |
The plan feature that gates this plugin in Inkvoice Cloud, or null. Must match the plugin's feature value in code. |
icon |
A lucide-react export name available in both consumer versions. bun run validate checks this. |
versions |
Newest first. version and min_app are strict MAJOR.MINOR.PATCH. |
bun run validate checks the schema first, then eight rules that span more than
one field or more than one entry. A failure prints the rule number, so this list
is what [rule 4] refers to.
- A
plannedentry has noversionsand nosource. It describes something that does not exist yet. - An
availableentry has at least one version. It has shipped. versionsis newest first and strictly descending. No duplicates.- Read oldest to newest,
min_appnever decreases. iconis alucide-reactexport present in both consumer versions.- Every
screenshots[].fileexists in the repo. - Ids are unique across the catalog.
- Reserved ids are rejected.
catalogis reserved because the app mounts its own routes under that segment.
A plugin's version is its own, not the app's. When you bump the version a
plugin declares in code, add a matching entry here in the same release, or the
app will report an update that does not exist.
min_app is the lowest Inkvoice version that release runs on. It may stay flat
or rise across releases, never fall.
bun install
bun run validate # schema plus the eight cross-field rules
bun test # unit tests
bun run build # writes dist/ (catalog.v1.json, index.html, schema/, screenshots)
bun run gen:schema # regenerates schema/*.json from the Zod sourceschema/*.json is generated. Edit src/schema.ts and re-run gen:schema; CI
fails if the committed files are stale.
Both files describe the contract, from opposite ends. plugin.schema.json is
strict: it validates hand-authored input, so an unknown key is a typo and gets
rejected. catalog.v1.schema.json permits unknown properties, so a consumer
using it as a validation gate keeps accepting the catalog when an additive v1
change introduces a field that consumer has not heard of yet.
Screenshot URLs in the artifact are absolute. They point at the Pages
deployment by default; set SCREENSHOT_BASE_URL to build them against another
host, so a consumer that mirrors the images onto its own domain does not send
readers to github.io. An empty value counts as unset.
SCREENSHOT_BASE_URL=https://inkvoice.app/plugins bun run buildThe filename carries the version. Additive changes (a new optional field, a new
enum value) stay on v1, and consumers ignore fields they do not know. A
breaking change publishes catalog.v2.json alongside v1, and v1 keeps
building for at least one full OSS release cycle, because self-hosted installs
upgrade on their own schedule.
MIT, see LICENSE. Contributions are accepted under the same terms.