Build an app

From a folder to an app running in a store's admin, in six steps. You need the cartbase CLI signed in to the store (cartbase login).

An app is a manifest, cartbase.app.json, and whatever storefront code the project needs under apps/<key>/. The app ships no admin code:

  • its records become record types the platform stores, validates and lists in the admin under the app, and serves to the storefront when public;
  • its settings become the app's Settings screen, committing as the merchant leaves each field; a secret is stored encrypted;
  • its screens appear under the app in the sidebar and on the Apps page;
  • its storefront files are recorded so an uninstall removes exactly them.

The reference chapters: app-manifest.md for every field, app-runtime-api.md for the app's own API, app-cli.md for the commands, apps.md for the storefront's reads.

1. Start the folder

# doc-noexec
cartbase app create store-locator --name "Store locator" --description "Partner shops on a map"

Inside a storefront project this writes apps/store-locator/; anywhere else ./store-locator/. Three files: cartbase.app.json (the app), CLAUDE.md (the brief a coding agent reads) and README.md.

2. Describe the app

Edit cartbase.app.json. Every field is in app-manifest.md. A complete example:

{
  "key": "store-locator",
  "name": "Store locator",
  "description": "Partner shops on a map",
  "version": "1.0.0",
  "icon": "pin",
  "records": [
    {
      "type": "location",
      "name": "Location",
      "name_plural": "Locations",
      "visibility": "public",
      "fields": [
        { "key": "name", "name": "Name", "kind": "text", "required": true },
        { "key": "address", "name": "Address", "kind": "text", "required": true },
        { "key": "city", "name": "City", "kind": "text", "required": true },
        { "key": "lat", "name": "Latitude", "kind": "number", "required": true, "validations": { "min": -90, "max": 90 } },
        { "key": "lng", "name": "Longitude", "kind": "number", "required": true, "validations": { "min": -180, "max": 180 } },
        { "key": "phone", "name": "Phone", "kind": "text" },
        { "key": "hours", "name": "Opening hours", "kind": "rich_text" }
      ]
    }
  ],
  "settings": [
    { "key": "maps_key", "name": "Maps API key", "kind": "secret", "description": "From your maps provider's console" },
    { "key": "default_zoom", "name": "Default zoom", "kind": "number", "default": 7 }
  ],
  "storefront": {
    "files": ["apps/store-locator/storefront/StoreLocator.tsx"],
    "component": "apps/store-locator/storefront/StoreLocator.tsx"
  }
}

3. Validate

# doc-noexec
cartbase app validate apps/store-locator

A refused manifest prints one line per problem, the path first, in words that say what to change. Fix and run again.

4. Deploy

# doc-noexec
cartbase app deploy apps/store-locator

The app appears in the store's admin under Apps with its screens: a Locations list, a record page per location, a Settings screen with the two settings. The first deploy prints the app's key once and writes it to the project's .env.local as CARTBASE_APP_STORE_LOCATOR_KEY. Deploy again after every manifest change; the version history is kept.

5. Write the storefront half

The storefront reads the app's public records with the publishable key, through the package:

import { listAppRecords } from "@cartbase/storefront/api/apps"

const { metaobjects: locations } = await listAppRecords(client, "store-locator", "location", { limit: 100 })

Each record carries fields: [{ key, kind, value }]. Server code that needs a setting or a secret calls the app's own API with the key from the environment; see app-runtime-api.md. Ship the storefront with cartbase deploy from the project root, as always.

6. Share

# doc-noexec
cartbase app share apps/store-locator

The folder becomes a public repository under the Cartbase organisation with a README written from the manifest and the MIT licence. Anyone installs it into their own store as a fork:

# doc-noexec
cartbase app install Cartbase/app-store-locator
cartbase deploy

Rules

  • Records and settings are the platform's. Render what it answers; never keep a second copy.
  • A secret never reaches the browser. Read it in server code through the runtime API; the storefront's client bundle carries the publishable key and nothing else.
  • The manifest is the app. A change is a new deploy. Removing a record type or a field that still holds data is refused; clear the data first.
  • Uninstall deletes. The merchant is shown what goes and offered the export; the click deletes the app and everything it held, at once.