Manifest schema

This page is the exhaustive reference for the manifest object every package default-exports from manifest.ts. For a task-oriented walkthrough, see Manifest; for the narrative around individual fields, see the other pages under Anatomy.

Fields

FieldTypeRequired?Description
namestringyesHuman-readable name shown in navigation and the package registry.
slugstringyesURL segment and collection-name prefix. Must match the last segment of the npm package name.
versionstringyesThe version the in-app package registry shows. Keep in sync with package.json; the release tooling bumps both.
descriptionstringyesOne-sentence summary shown in the package registry.
repository.urlstringnoWhere the package lives, for “Report an issue” links. repository.issueTemplate optionally names an issue template.
routes.directorystringnoFolder of app screens, re-exported under tinycld/app/a/(app)/<slug>/ (served at /a/<slug>/). Convention: 'screens'.
publicRoutes.directorystringnoFolder of public screens, re-exported under tinycld/app/p/<slug>/<path>. Convention: 'public-screens'.
nav.labelstringif nav setText for the rail entry.
nav.iconstringif nav setLucide icon name.
nav.ordernumbernoSort priority; lower comes first.
nav.shortcutstringnoSingle letter: t then this letter jumps to the package. Must be unique across installed packages; the generator rejects duplicates.
migrations.directorystringnoFolder of PocketBase migration JS files. Convention: 'pb-migrations'.
hooks.directorystringnoFolder of PocketBase JS hooks. Convention: 'pb-hooks'.
collections.registerstringnoSubpath (no extension) to the module exporting registerCollections.
collections.typesstringnoSubpath (no extension) to the module exporting {PascalSlug}Schema.
sidebar.componentstringnoSubpath to a component rendered in the secondary sidebar when this package is active. Omit sidebar entirely and the workspace renders no sidebar container — the package’s screens get the full viewport width next to the nav rail (@tinycld/calc ships this way).
provider.componentstringnoSubpath to a provider component wrapping the package’s routes.
settingsArray<{slug, component, label}>noPersonal Settings panel contributions. Each entry is a link + component pair.
settings[].slugstringif settings setURL segment under /a/settings/. Must be unique across installed packages.
settings[].componentstringif settings setSubpath to the panel component.
settings[].labelstringif settings setLink text in the settings sidebar.
systemSettingsArray<{slug, component, label}>noDeployment-wide settings panels (a mail provider’s credentials, a webhook secret). Same shape as settings, but rendered in the owner’s system settings and stored once per deployment, not per user.
slotsstring[]noNames of sidebar slots this package exposes for other packages to contribute into. Each name must be unique within the manifest; the generator errors on duplicates. See Sidebar slots.
sidebarContributionsArray<{target, slot, component, order?}>noUI contributions this package injects into another package’s sidebar slot.
sidebarContributions[].targetstringif setSlug of the host package whose slot is being targeted. Tolerated (warning, not error) when the host isn’t installed.
sidebarContributions[].slotstringif setName of the slot to render into. Must match a slot declared in the target’s slots array; the generator errors otherwise.
sidebarContributions[].componentstringif setSubpath to the React component. Same resolution as settings[].component. Must default-export.
sidebarContributions[].ordernumbernoSort priority among contributions to the same slot. Default 0; ties broken alphabetically by contributor slug.
eventSourcesArray<{target, id, label, module, color?, order?}>noRead-only feeds this package contributes to another package’s grid (Boards’ due dates on the Calendar). target is the host slug — it must declare eventSourceHost: true or generation fails; an absent host leaves the source silently inactive. id is [a-z0-9-], unique per target. module is a subpath to a module exporting useEventSource. See Event sources.
eventSourceHostbooleannoDeclares that this package’s grid accepts eventSources contributions.
seed.scriptstringnoSubpath to a module default-exporting an async seed function.
tests.directorystringnoFolder of Playwright specs. Convention: 'tests'. Vitest globs tests automatically.
server.packagestringnoSubdirectory containing a Go module. Convention: 'server'.
server.modulestringif server setGo module path declared in that subdirectory’s go.mod. Namespace as tinycld.org/packages/<slug>.
server.mailListenersbooleannoThis package serves mail protocols. A self-hosted deployment binds its own ports; under a hosting supervisor the pre-bound sockets are injected and the package discovers them through coreserver.GetTenantContext.
payloads.packagestringnoDirectory of a Go package declaring the HTTP payload contract (e.g. 'server/api'). The generator emits typed TypeScript for it at lib/generated/<slug>-api.ts.
carddavobjectnoDeclarative CardDAV config: the contacts collection, its list filter, owner and UID fields, soft-delete field, and the vCard field map. Core serves the protocol; the package contributes only the config, so a deployment that links no feature Go still gets CardDAV.
caldavobjectnoDeclarative CalDAV config: the calendar and event collections and their field maps, plus defaults for required select fields a minimal client payload omits.
webdavobjectnoDeclarative WebDAV config: the mount prefix, the items collection and its field map, and an optional trash block so a DAV DELETE stamps a per-user soft delete instead of destroying the record.
quotaArray<{collection, sizeField, ownerField?}>noStorage-bearing collections. Core enforces the per-user and deployment ceilings as record hooks, so no write path can skip them. A source with no ownerField counts toward the deployment ceiling only.
help.directorystringnoFolder of <id>.md help topics. Convention: 'help'.
cli.packagestringnoSubdirectory containing a Go module exposing Register(root *cobra.Command, c *client.Client). Convention: 'cli'. See tinycld CLI reference.
cli.modulestringif cli setGo module path declared in that subdirectory’s go.mod. Namespace as tinycld.org/packages/<slug>/cli. There is deliberately no command list (Cobra owns the tree) and no scope list (the package’s Go server registers its OAuth scopes with oauth.RegisterPackage).
search.adapterstringnoSubpath to a module exporting useSearchActions, which decides what happens when a result from this package is selected in the palette. Rows come from core’s federated /api/search, fed by the Go server’s search.RegisterSources. Omit to stay out of the palette.
search.labelstringnoChip and group label in the palette. Defaults to nav.label.
automation.definitionsstringnoSubpath to a TS module default-exporting an AutomationDefinitions object — pure data, typed against the package’s schema. Declares the triggers and actions users can build rules on. See Automation.
build.scriptstringnoSubpath to a TS module the generator runs before emitting generated files. Use for artifacts the bundler can’t produce on its own (e.g. an embedded webview bundle). dev keeps it alive in watch mode.
dependenciesstring[]noSlugs of other packages this one expects. Advisory only, plus seed ordering; not enforced.
peerVersionsRecord<string, string>noEnforced semver ranges keyed by slug or @tinycld/core (e.g. { '@tinycld/core': '>=0.0.6 <0.1.0' }). The version-compatibility solver refuses any staged change in Settings → Packages that would leave a declared range unsatisfied, and the server checks again before applying. Every shipping package sets it.

All the *.directory and *.component / *.script fields are paths relative to the package root. directory values point at a folder; component, script, module, and adapter values are subpaths resolved through the package’s exports map, without the file extension.

TypeScript interface

The source-of-truth interface, with full doc comments, lives in tinycld/core/lib/packages/types.ts; tinycld/scripts/load-manifest.ts carries a copy for the generator. Abbreviated:

interface PackageManifest {
    name: string
    slug: string
    version: string
    description: string
    repository?: { url: string; issueTemplate?: string }
    routes?: { directory: string }
    publicRoutes?: { directory: string }
    nav?: { label: string; icon: string; order?: number; shortcut?: string }
    migrations?: { directory: string }
    hooks?: { directory: string }
    collections?: { register: string; types: string }
    sidebar?: { component: string }
    provider?: { component: string }
    settings?: { slug: string; component: string; label: string }[]
    systemSettings?: { slug: string; component: string; label: string }[]
    slots?: string[]
    sidebarContributions?: { target: string; slot: string; component: string; order?: number }[]
    eventSources?: { target: string; id: string; label: string; module: string; color?: string; order?: number }[]
    eventSourceHost?: boolean
    seed?: { script: string }
    tests?: { directory: string }
    server?: { package: string; module: string; mailListeners?: boolean }
    payloads?: { package: string }
    carddav?: CardDavConfig
    caldav?: CalDavConfig
    webdav?: WebDavConfig
    quota?: { collection: string; sizeField: string; ownerField?: string }[]
    help?: { directory: string }
    cli?: { package: string; module: string }
    search?: { adapter: string; label?: string }
    automation?: { definitions: string }
    build?: { script: string }
    dependencies?: string[]
    peerVersions?: Record<string, string>
}

The three protocol config shapes (CardDavConfig, CalDavConfig, WebDavConfig) are field maps from the protocol’s vocabulary onto the package’s own collections; copy the block from contacts/manifest.ts, calendar/manifest.ts, or drive/manifest.ts and rename the fields.

Annotated example

const manifest = {
    name: 'Example',
    slug: 'example',
    version: '0.1.0',
    description: 'An example package',
    repository: { url: 'https://github.com/acme/example' },

    routes: { directory: 'screens' },
    publicRoutes: { directory: 'public-screens' },

    nav: {
        label: 'Example',
        icon: 'box',
        order: 20,
        shortcut: 'e',
    },

    migrations: { directory: 'pb-migrations' },
    hooks: { directory: 'pb-hooks' },

    collections: {
        register: 'collections',
        types: 'types',
    },

    settings: [
        { slug: 'example', component: 'settings/example', label: 'Example settings' },
    ],

    sidebar: { component: 'sidebar' },
    provider: { component: 'provider' },

    seed: { script: 'seed' },
    tests: { directory: 'tests' },
    help: { directory: 'help' },

    server: { package: 'server', module: 'tinycld.org/packages/example' },
    cli: { package: 'cli', module: 'tinycld.org/packages/example/cli' },

    search: { adapter: 'search-adapter' },
    automation: { definitions: 'automation' },
    quota: [{ collection: 'example_files', sizeField: 'size', ownerField: 'owner' }],

    build: { script: 'build' },

    peerVersions: { '@tinycld/core': '>=0.0.6 <0.1.0' },
    // dependencies: ['other-package-slug'],
}

export default manifest