<!-- GENERATED FILE. Do not edit by hand.
     Source: src/lib/apps/manifest.ts
     Regenerate: bun x tsx scripts/generate-app-manifest-docs.ts
     Guarded by: tests/unit/apps-manifest-docs.test.ts -->

# The app manifest, field by field

The manifest is `cartbase.app.json` at the root of the app folder. The
platform validates it on `cartbase app validate` and `cartbase app deploy`;
a refused manifest answers one row per problem, the path and what to change.
Unknown keys are refused by name. This page is generated from the schema
the platform validates with, so it cannot drift. The JSON Schema for
editors is [app-manifest.schema.json](app-manifest.schema.json), also at
https://docs.cartbase.ai/app-manifest.schema.json.

## Top level

| Field | Required | What it is |
|---|---|---|
| `key` | yes | The app's slug: 2 to 40 characters, lowercase letters, digits and hyphens, starting with a letter. The URL segment under Apps and the prefix of every record type. Reserved: `reviews`, `documents`, `nra-audit`, `subscriptions`, `feeds`, `search`, `search-discovery`, `design-system`, `new`, `install`, `validate`, `settings`. |
| `name` | yes | The name shown in Apps, up to 60 characters. |
| `description` | yes | One line saying what the app does, up to 140 characters. |
| `version` | yes | Three numbers, `1.0.0`. Every deploy is kept under its version. |
| `icon` | no | One of `apps`, `pin`, `pages`, `star`, `tag`, `chart`, `users`, `cart`, `invoice`, `dollar`, `rss`, `search`, `van`, `warehouse`, `bell`, `swatches`, `discount`, `home`, `cog`. Default `apps`. |
| `records` | no | The app's record types, up to 20. See below. |
| `settings` | no | What the merchant fills on the app's Settings screen, up to 40. See below. |
| `screens` | no | The screens under the app, up to 8. Omitted: one records screen per record type, then Settings when settings exist. |
| `storefront` | no | `{ files: [...], component? }`: the files the app adds to the storefront project, relative to its root, up to 200. Recorded so an uninstall removes exactly them. |
| `permissions` | no | Must be empty in this version: an app writes only its own records. |

## A record type

```jsonc
{
  "type": "location",          // snake_case, up to 40 characters, letters first
  "name": "Location",          // singular, what a record page is called
  "name_plural": "Locations",  // the list's title; default: name + "s"
  "visibility": "public",      // public | private; default public; fixed for life
  "fields": [ { "key": "city", "name": "City", "kind": "text", "required": true } ]
}
```

The platform stores the records as its own metaobjects, owned by the app:
the store type is `<key>.<type>`, so `store-locator.location`. A
`public` type answers at `GET /api/store/metaobjects/<key>.<type>` with the
publishable key (active records only); a `private` type never leaves the
admin. Visibility cannot change after the first deploy: declare a new type
and move the records.

### A field

| Field | Required | What it is |
|---|---|---|
| `key` | yes | snake_case: lowercase letters, digits, underscores. Unique in the type. |
| `name` | yes | The label the merchant reads. |
| `kind` | yes | One of `text`, `rich_text`, `number`, `boolean`, `date`, `file`, `color`, `reference_product`, `reference_collection`, `reference_page`, `reference_metaobject`. |
| `required` | no | Enforced when a record is or becomes active; drafts may be incomplete. |
| `validations` | no | `{ min?, max? }` (text: length, number: value), `pattern?` (text, a regex), `metaobject_definition_id` (required for `reference_metaobject`). |

Changing fields on a later deploy follows the platform's own rule: adding
is free; removing a field or changing its kind is refused while records
carry a value for it; making a field required is refused while an active
record lacks it. Clear or migrate the records first.

## A setting

```jsonc
{
  "key": "maps_key",           // snake_case, letters first
  "name": "Maps API key",
  "description": "From your maps provider's console",
  "kind": "secret",            // text | number | boolean | select | secret
  "required": false,
  "options": [ { "value": "light", "label": "Light" } ],   // select only
  "default": "light"           // not for secret; must match the kind
}
```

A `secret` is written on the app's Settings screen, stored encrypted by the
platform, never sent to a browser, and handed only to the app's own call at
`GET /api/apps/me` with the app's key.

## A screen

```jsonc
{ "kind": "records", "key": "locations", "label": "Locations", "record": "location" }
{ "kind": "settings", "key": "settings", "label": "Settings" }
```

A records screen names a declared record type. A settings screen needs at
least one declared setting. Keys are slugs and unique.
