Skip to content
OpenPets paw badgeOpenPets Docs
Esc
navigateopen⌘Jpreview
On this page

Catalogs

Reference the OpenPets pet and plugin catalogs, generated artifacts, ZIP hosting, sidecars, validation gates, and desktop fetch behavior.

Catalogs

The app discovers and installs pets and plugins from versioned JSON catalogs served at openpets.dev, with ZIPs hosted on R2 behind zip.openpets.dev. This doc describes those catalogs as contracts and how the desktop consumes them. It is intentionally scoped to the app-facing data of web/ - catalogs, ZIP hosting, pet/plugin metadata - and not the marketing site/frontend.

This doc is the consumer-and-contract view. Producer commands and release gates are covered in Testing and validation and the package scripts they reference; do not treat older website-local notes as authoritative when they disagree with the generated catalog.

Direction (read this first)

  • Pet catalog v3 is the source of truth. v2 exists only for old app versions and as a fallback. New work targets v3.
  • Plugin catalog v2 is active. v1 is kept as an empty compatibility shim for older desktop builds. (Verified URLs in apps/desktop/src/plugin-catalog.ts.)
  • This matches the forward-only direction in AGENTS.md: don’t optimize new behavior for legacy catalog versions.

Endpoints the app fetches

Data URL Owner in app
Pet catalog v3 (index) https://openpets.dev/pets/catalog.v3.json apps/desktop/src/catalog.ts
Pet catalog v3 pages …/pets/catalog.v3/page-NNN.json catalog.ts
Pet catalog v3 search …/pets/catalog.v3/search.json (+ search pages) catalog.ts
Pet catalog v2 (legacy/fallback) https://openpets.dev/pets/catalog.v2.json catalog.ts
Pet ZIPs https://zip.openpets.dev/pets/{slug}/{installId}.zip pet-installation.ts
Plugin catalog v2 (active) https://openpets.dev/plugins/catalog.v2.json plugin-catalog.ts
Plugin catalog v1 (empty compat) https://openpets.dev/plugins/catalog.v1.json plugin-catalog.ts
Plugin ZIPs https://zip.openpets.dev/plugins/{plugin-id}.zip plugin-package.ts

Pet catalog v3 contract

v3 is paginated to keep each runtime fetch small. The flow the app follows:

  1. Fetch the root index (catalog.v3.json): version: 3, generatedAt, total, pageSize, a search URL, filters (categories with counts, originalsCount, featuredCount), and a pages[] array of page URLs.
  2. Fetch pages on demand. Each page entry carries install + render data: id, displayName, description, thumbnail, spritesheet, zip, category, optional subcategory, featured, original.
  3. Use search pages for lightweight lookup: id, displayName, searchText, category, catalogPage, featured, original.

Only pets with a valid category (western or asian) appear in v3 - the generator drops the rest and logs a warning. To keep the app UI clean, the Control Center browsing/search indexes surface only “curated” (original or featured) pets. However, explicit lookup/installation by ID allows installing any valid v3 catalog pet. The validators on the app side live in catalog-validation.ts (validateCatalogV3Index, validateCatalogV3Page, validateCatalogV3SearchIndex, validateCatalogV3SearchPage, plus validateCatalogV2). Treat the generator output plus those validators as the contract, not any hand-written copy.

Fetch fallback chain

catalog.ts resolves the catalog V3 → V2 → bundled fixture. The fixture (catalog.v2.fixture.json) keeps the app usable offline / in tests. The fixture should never be the path real users hit online; it is a last-resort floor, not a shipping catalog.

Pet generated artifacts

The publishing scripts (web/scripts/) treat web/public/pets/manifest.json as the canonical generated state and derive everything else from it:

web/public/pets/
  manifest.json          canonical generated catalog state
  install.json           install metadata
  catalog.v2.json        legacy/fallback catalog
  catalog.v3.json        v3 index
  catalog.v3/
    page-000.json …       paginated pages
    search.json
    search-page-NNN.json
  {slug}/
    spritesheet.webp
    thumb.webp
    thumb.webp.meta.json

Regenerating artifacts from the manifest is generate:catalog-artifacts; verifying them is the verify:catalog* family (see Testing and validation). The web build also emits app/lib/pets.generated.js / pets.preview.js for the site - out of scope here.

Plugin catalog contract

The plugin catalog (plugin-catalog-validation.ts) is validated strictly: schema version, unique ids, semver + SHA fields, canonicalized permissions, and an optional minimum-OpenPets-version gate. Catalog cards may carry an iconDataUrl (base64 SVG) so the Plugins UI renders an icon without an extra fetch. Each entry’s downloadUrl must point at zip.openpets.dev/plugins/. Catalog v2 also carries publisherType: "official" | "community"; older catalogs without the field are treated as official by the desktop validator. Community entries are public catalog plugins but cannot be bundled/default-on.

Sidecars: plugin provenance and submissions (website-only)

To secure community-contributed plugins without changing the app-facing catalog.v2.json schema, the website serves sidecar metadata files:

File Purpose
https://openpets.dev/plugins/provenance.json Reviewed provenance for installable community plugins.
https://openpets.dev/plugins/submissions.json Pending external GitHub submissions shown on the website but not installable.

These files are only used by the website/CI environment for provenance display, validation, and automated owner-publishing policy. provenance.json is keyed by plugin ID and defines:

  • publisher: GitHub user/organization owner.
  • sourceUrl: Upstream GitHub repository.
  • sourceSubdirectory: Optional subdirectory under the repository root.
  • sourceCommit: The reviewed and approved commit SHA.
  • reviewedAt: ISO date/time of review.
  • updatePolicy: safe-auto (safe for automated release updates) or manual-review.

submissions.json is also keyed by plugin ID, but entries are candidates only: they must not appear in the installable catalog until promoted into plugins/community/, packaged, uploaded, and release-validated.

The desktop fetch (plugin-catalog.ts) is hardened: timeout, redirect rejection, response-size cap, and caching with refresh. Install/verification of the downloaded ZIP (SHA-256, host/path allowlist, entry restrictions, manifest ↔ catalog consistency) is plugin-package.ts. See Plugin platform.

ZIP hosting (R2)

Both pet and plugin ZIPs live on the R2 bucket (default openpets) backing zip.openpets.dev. The hard rule: never ship a catalog entry whose ZIP isn’t live on R2. The verification commands HEAD-check every ZIP for exactly this reason. Override the bucket with OPENPETS_R2_BUCKET; --skip-r2 is for local testing only.

How the app uses all this

  • Browsing: the Pets page in the Control Center pages through v3 and uses the search index for filtering.
  • Installing: see the install flow in Pets - catalog lookup (which allows installing any valid v3 catalog pet by ID, even if not original or featured) → ZIP download → validated extraction → state update → tray refresh. (Control Center UI surfaces only curated original/featured pets, but explicit install by ID allows any valid v3 pet).
  • Plugins: the Plugins page lists catalog v2 entries filtered by app version and install state; install downloads + verifies the plugin ZIP. See Plugin platform.

Was this page helpful?