Skip to content
OpenPets paw badgeOpenPets Docs
Esc
↑↓navigate↵open⌘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.

Publication and website discovery

The curated manifest remains the publication input. Publishing content and deploying website code are separate operations: an immutable snapshot contains the catalog, its search pages, and rich website metadata, while content-addressed assets are reused across publications. The active publication pointer changes only after the referenced objects and install ZIPs have been verified.

Desktop discovery still starts at the existing openpets.dev/pets/ endpoints. The v3 index pins its page and search URLs to a publication beneath /pets/catalog.v3/; image URLs remain beneath /pets/. Previously published revisions remain available so an app holding an older index cannot accidentally combine it with a newer publication’s pages. ZIP URLs, pet IDs, curated visibility, V1/V2 sprite behavior, and the base/full-package distinction are unchanged. Legacy unversioned descendants are pinned to the bootstrap snapshot.

The website reads /pets/site.json, a website-only descriptor that identifies one revision’s catalog and rich pet metadata. Pet details and outfit previews read that metadata at runtime rather than importing a build-time pet list.

Publicly shared Studio pets are a separate discovery source, displayed alongside curated pets on the website with a Studio mark. Studio owns readiness, sharing, moderation, creator profiles, hearts, search, and downloads. Hidden, private, or unfinished projects are excluded by its public API. Studio projects are not merged into desktop v3/v2 catalogs; their appearance on the website does not change desktop browsing, explicit catalog lookup, or installation semantics.

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 shared pet-format V3 client; catalog.ts façade
Pet catalog v3 pages …/pets/catalog.v3/page-NNN.json shared V3 client; desktop policy
Pet catalog v3 search …/pets/catalog.v3/search.json (+ search pages) shared V3 client; desktop policy
Pet catalog v2 (legacy/fallback) https://openpets.dev/pets/catalog.v2.json catalog-remote.ts transport/cache; catalog.ts fallback
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, and optional spriteVersionNumber: 2. Published entries may explicitly carry numeric 1 for V1 pets; the remote catalog boundary normalizes that marker to an omitted field internally. Omitted fields also retain V1 behavior. Only the exact numeric values 1 and 2 are accepted when a marker is supplied; strings, null, and other numbers are invalid.
  3. Use search pages for lightweight lookup: id, displayName, searchText, category, catalogPage, featured, original, and the optional spriteVersionNumber: 1 | 2 in published JSON. The remote boundary normalizes numeric 1 to omission, matching the internal optional-2 type.

Pet-specific rendering metadata such as gazeAnchor remains owned by the ZIP’s validated pet.json, not duplicated into catalog entries. Catalog omission of that optional field therefore does not affect installed V2 gaze behavior.

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 packages/pet-format/src/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.

The shared catalog-v3.ts owns HTTP requests, five-second deadlines, bounded streaming, final-URL checks, validation, V1-marker normalization, curated page hydration, and per-client V3 caches. Desktop catalog-remote.ts adapts V3 pets to its V2-compatible shape and caches V2. Successful V3 data stays cacheable; failed V3 index, search, or page requests can be retried, while successful search/index data remains coalesced and cached. Local V2/fixture validation does not accept an explicit V1 marker; omission is the internal V1 form. catalog.ts remains the product façade: it owns fixture fallback, V3-only curated visibility, virtual pagination/search composition, and explicit lookup semantics. When V3 is unavailable, the validated V2 catalog and bundled fixture expose all of their validated pets for browsing and lookup/install; their V2-compatible entries do not require V3 original/featured metadata. The V3 → V2 → fixture precedence and error behavior remain unchanged.

Control Center search stays on V3. A failed search load is distinct from a successful empty index: it is not cached as an empty result. The user can retry the failed load, and returning to the Pets route can attempt it again. Successful search data remains cached; failures do not trigger an automatic retry loop. The Pets route presents a search-load failure in its retry banner independently from action errors, which remain in the plain error banner. Retrying search clears only the search failure; it does not dismiss an unrelated pet action error.

The legacy V2 catalog may carry numeric spriteVersionNumber: 1 or 2 in published JSON. The remote boundary strips 1, retains 2, and V3-to-compat conversion preserves the normalized optional-2 representation. Local fixture/catalog validation remains strict and requires omission for V1.

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:

These files are publication inputs, not bundled website assets. The runtime serves validated R2 snapshots instead of exposing this directory directly.

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      or spritesheet.svg for a static vector atlas
    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). Website metadata is emitted into the immutable publication snapshot, not into JavaScript modules bundled with the website.

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.

Pet ZIP archive contract

The desktop installer accepts a pet ZIP only when its final non-directory file set is exactly:

pet.json
spritesheet.webp OR spritesheet.svg

The atlas filename must match pet.json.spritesheetPath; both assets together or a mismatched asset are rejected. SVG uses the same V1/V2 atlas/frame geometry, not SVG-native animation. The static, self-contained vector profile and bounded dimensions/complexity are specified in Pets. Catalog v3 spritesheet URLs may point to the canonical .svg atlas; thumbnails can remain raster images.

A pet entry’s optional wardrobeZip points at a second archive containing all declared outfits and activity strips, including for activity-only pets. The field name is historical: its presence means a full package exists, not that it contains outfits. Desktop records the installed package URL as its source identity and offers an extras update only when the catalog full-package URL differs. zip must stay the two-file base package (with wardrobe and activities stripped from its pet.json), because apps released before wardrobes reject outfits/. Entries may also carry minOpenPetsVersion (MAJOR.MINOR.PATCH); apps that know the field disable installs when they are older. Old apps ignore both fields. Activity publication requires an explicitly configured confirmed released supporting version; the full ZIP and SHA-256 are retained in web Git LFS for lossless repair rather than reconstructed from preview images. See Wardrobe.

Those files may be at the archive root or below exactly one top-level directory. Optional directory entries for that one directory are allowed. Paths must be safe, layouts cannot be mixed, and QA artifacts such as validation.json, contact sheets, and motion previews must stay outside the staged pet directory. The release verifier checks this contract from ZIP central-directory range reads, without downloading the spritesheet payload.

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 is missing or fails the pet ZIP archive contract. verify:catalog:remote validates every ZIP URL in the local v3 catalog, while verify:catalog:prod validates every ZIP referenced by the deployed v3 catalog and also reports local-vs-prod drift. Both checks use bounded central-directory range reads. 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. A catalog entry with spriteVersionNumber: 2 carries the same 8×11 layout metadata as an imported/local Codex V2 pet; entries without it remain V1-compatible.
  • 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.

Teams private catalog lane

Teams is an optional cloud service separate from the public catalog. The desktop uses OPENPETS_TEAMS_API_URL (default https://openpets-teams-api.tokozedg793.workers.dev) and downloads Team artifacts only through the authenticated configured API origin. Team Packs contain published pets/plugins, required/optional policy, organization configuration, immutable release references, and removals. Dedicated storage, strict package validation, and ZIP safety preserve public catalog restrictions. Updates are staged before activation and applied revision advances only after convergence. Offline leave/removal retains pending state and never claims remote deletion.

Was this page helpful?