The vendo-templates repo
Structure and layout of runvendo/vendo-templates — where every catalog manifest lives.
Catalog manifests live in a dedicated repo: runvendo/vendo-templates. It is the single source of truth for what the deploy worker provisions. Manifests are not stored in the Vendo monorepo, and they are not stored in your tool's own repo.
Repo layout
vendo-templates/
├── README.md
├── .github/workflows/
│ ├── sync.yml
│ └── upstream-tracker.yml
└── <slug>/
├── 1.0.0.json
├── 1.0.1.json
└── 1.1.0.jsonOne directory per tool, named exactly the same as the tool's apps_catalog.slug. One JSON file per version, named {semver}.json. That's the entire convention.
How the sync action works
On every push to main, .github/workflows/sync.yml runs two jobs in sequence: a validate job (also runs on PR) followed by a sync job (push-to-main + manual workflow_dispatch only).
- Validate.
npm run validateparses every{slug}/{version}.jsonagainst the local Zod schema invendo-templates/src/schema.ts. The Zod schema is intentionally stricter than the deploy worker'smanifest.schema.json— for example, everyintegrations[]entry must declareprofileandcardinality. A submission that passes the deploy-worker schema can still fail Zod here. - Sync to R2.
npm run sync:r2uploads each{slug}/{version}.jsonto thevendo-templatesR2 bucket at the same path. The deploy worker fetchestemplates/{slug}/{version}.jsonat deploy time. - Sync to Supabase.
npm run sync:supabasejoins each template againstapps_catalog.slug, finds the matchingtool_releasesrow withstatus='active', and — only if the active release'sversionmatches the template'sversion— UPDATEs the row'stemplate_version,wizard_inputs,requires,wizard_layout, andversioncolumns from the manifest JSON. Templates whose version doesn't match the active release are skipped (with a log line). Seesrc/sync-supabase.ts.
R2 is content-addressed by {slug}/{version}. Once a version is uploaded, do not edit the file in place — every change is a new version. Existing deployments reference the manifest version that was active when they deployed.
Editing a published 1.0.0.json in place is a footgun. Existing deployments snapshot the manifest into deployments.manifest at deploy time, so they keep running. But anything that re-fetches the manifest (e.g. retries) will pick up the silent change, and sync:supabase will rewrite tool_releases columns next push. Always cut a new version.
Order of operations matters: sync:supabase only updates an active release whose version matches the template. Adding {slug}/1.0.5.json does nothing in the DB until the matching release migration lands. The recommended flow is to merge the monorepo release migration first (or at the same time) so the active release pins to the new version before sync runs.
Required workflow secrets
The sync action needs five secrets configured on the vendo-templates repo. These are already wired by Vendo for the main repo; you only deal with them if you fork:
R2_ACCESS_KEY_IDR2_SECRET_ACCESS_KEYCLOUDFLARE_ACCOUNT_IDSUPABASE_URLSUPABASE_SERVICE_ROLE_KEY
What's NOT in the templates repo
- Catalog entry. The row in
apps_cataloglives in a DB seed migration in the Vendo monorepo, not here. - Release row.
tool_releasesis also driven by monorepo migrations. - Adapter code. Per-provider proxy adapters live in
packages/integrations/<slug>/in the monorepo. - Source code. Your tool's container image and source code live wherever the image is built — usually a separate repo entirely. The manifest only references the image tag.
Naming and slug discipline
The tool's slug is load-bearing in five places: the directory name here, apps_catalog.slug, deployments.tool_slug, the proxy adapter registry (if the tool defines one), and the deployment URL. Pick it once and never rename it. If you absolutely must rename, expect to write a data migration touching all five surfaces.
A minimal first PR
For a brand-new tool:
vendo-templates/
└── my-tool/
└── 1.0.0.jsonThat single file is enough to open a PR. The matching catalog entry and release row come from migrations in the Vendo monorepo — see Submission for the full checklist.
Next: Manifest format — what goes inside {version}.json.