Appbricx
Docs
PricingStart free

Start here

OverviewGetting startedProjects & editorBring your own key

Build with your agent

Coding agents quickstartAccess tokensMCP serverApp contractCustom UI & brandingBefore-write rulesDeveloper CLIDeveloper HTTP API

End-to-end walkthroughs

Lead intakeMulti-user TodoMulti-tenant SaaSSlack on signup

AI & keys

AI keys — how resolution worksBYOK deep dive

Integrations

Integrations catalog

Full-stack runtime

Runtime overviewNamed queriesAuth & RLSAuto CRUD REST APIWorkflowsWebhooks & schedulesTopics & CDCData templatesSecrets & env

Ship & own

Publish & domainsExport & GitHubSelf-host & deploy

Developers

Developer guide

App contract

One JSON document describes the whole app. Appbricx normalizes it, reports what it changed, and builds the database, access policies, sign-in, workflows and UI from it. The contract stays in the project, so the next session reads it back with get_contract.

Full reference. The guide served by get_guide (MCP), appbricx docs (CLI) and GET /dev/v1/docsis generated from the platform's own source and is always current. This page is the overview.

Top-level keys

KeyRequiredWhat it holds
appYesname, tagline; optional brand_icon, theme, nav_style (sidebar | topbar | rail), density (comfortable | compact), direction, brand_color (a hex colour)
personasRecommendedThe roles: id, label, description, kind (staff | consumer)
entitiesYes (at least one)Tables, their fields, access, owner refs and rules
seedsNoRows per entity
pagesNoYour own pages. Omit it and every table gets a list + form page for its staff roles
workflowsNoUp to 10 server-side workflows (on data change, schedule or webhook)
componentsNoCustom React components with their source. See Custom components
authNoSign-in screen presentation, e.g. { "screen": "MyLogin" } for your own sign-in component
demo_loginsNoExtra demo sign-ins: [{ "persona", "row" }], up to 8

Personas (roles)

  • staff personas manage shared records. consumer personas act through pages (forms, funnels, composers).
  • The first consumer persona is the public sign-up. With no consumer persona the app is invite-only, and the first staff role gets a Team & access page to add people.
  • A person's own row lives in the entity named exactly like their persona id (the technician persona → the technician table).

Entities

Names are snake_case and singular. Every table already has id, created_at, updated_at, user_id, org_id and created_by. Never declare them.

KeyMeaning
nameTable name
label_fieldThe field that names a row in the UI
fields{ name, type, enum_values?, ref?, optional? }. Types: text, number, boolean, date, timestamp, enum, ref, email, url, image, attachment. A ref names another entity
uniqueField combinations that must be unique, e.g. [["slot", "date"]]
accessPer-persona overrides. read / update / delete: "own" | "all" | "none"; create: true | false
owner_refsRef fields that assign a row to a person. A row is "own" when any listed ref points at the viewer's row (transitively). [] = creator only
actor_refA ref that always names the writer's own persona row (e.g. sender_id). The server stamps it on every write by a non-staff role
rulesBefore-write checks, up to 6. See Before-write rules

Access is derived from personas and pages and enforced in Postgres with row-level security. Only add access where the default is wrong. verify then proves it with a role × table probe.

Seeds, pages and workflows

  • Seeds. Seed ids like "t1" are labels for refs between seed rows; the platform turns them into stable UUIDs. After the first apply, omit seeds to keep every row.
  • Pages. Each page has a name, route, title, personas and a layout tree of blocks and custom component nodes. "chrome": "none" makes it full screen. See get_guide sections pages and blocks.
  • Workflows. { id, title, on, when?, do[] }. Triggers: an entity event, a schedule or a webhook. Actions include notify, email, integration and (last resort) custom code. Omitting workflows keeps the current ones; a present list replaces them. Workflows run after a write; use rules to stop one.
  • Demo sign-ins. Every persona gets one login, linked to the first seed row of its table. Add demo_logins when a demo needs two people (chat, dating, a marketplace).

Example

A field-service app: dispatchers create and assign jobs, technicians see and update only their own. This is the example contract that ships with the Claude Code skill.

{
  "app": { "name": "Field Desk", "tagline": "Jobs, sites and the people who fix them" },
  "personas": [
    { "id": "dispatcher", "label": "Dispatcher", "description": "creates and assigns jobs", "kind": "staff" },
    { "id": "technician", "label": "Technician", "description": "works the jobs assigned to them", "kind": "staff" }
  ],
  "entities": [
    {
      "name": "technician",
      "label_field": "name",
      "fields": [
        { "name": "name", "type": "text" },
        { "name": "email", "type": "email" },
        { "name": "phone", "type": "text", "optional": true }
      ]
    },
    {
      "name": "site",
      "label_field": "name",
      "fields": [
        { "name": "name", "type": "text" },
        { "name": "address", "type": "text" }
      ]
    },
    {
      "name": "job",
      "label_field": "title",
      "fields": [
        { "name": "title", "type": "text" },
        { "name": "site", "type": "ref", "ref": "site" },
        { "name": "technician", "type": "ref", "ref": "technician", "optional": true },
        { "name": "status", "type": "enum", "enum_values": ["open", "assigned", "done"] },
        { "name": "due", "type": "date", "optional": true }
      ],
      "owner_refs": ["technician"],
      "access": { "technician": { "read": "own", "update": "own", "delete": "none" } }
    }
  ],
  "seeds": {
    "technician": [
      { "id": "t1", "name": "Asha Rao", "email": "asha@example.com" },
      { "id": "t2", "name": "Ben Ortiz", "email": "ben@example.com" }
    ],
    "site": [
      { "id": "s1", "name": "Harbor Mall", "address": "1 Quay St" },
      { "id": "s2", "name": "Northside Clinic", "address": "40 Elm Ave" }
    ],
    "job": [
      { "id": "j1", "title": "Boiler pressure drop", "site": "s1", "technician": "t1", "status": "assigned", "due": "2026-10-10" },
      { "id": "j2", "title": "Replace door closer", "site": "s2", "status": "open" }
    ]
  },
  "workflows": [
    {
      "id": "job-assigned",
      "title": "Tell the technician about a new assignment",
      "on": { "entity": "job", "event": "updated" },
      "when": { "field": "status", "equals": "assigned" },
      "do": [ { "action": "notify", "to": "technician", "role": "technician", "title": "New job: {{title}}", "body": "Due {{due}}" } ]
    },
    {
      "id": "overdue-digest",
      "title": "Morning digest of new jobs",
      "on": { "schedule": "daily 08:00", "entity": "job" },
      "do": [ { "action": "notify", "role": "dispatcher", "title": "{{count}} new job(s) since yesterday", "body": "{{items}}" } ]
    }
  ]
}

No pages: each table gets a management page for its staff roles. "to": "technician"sends the notification to the person the job's technician ref points at, not to the whole role.

The check report

check_contract (and every apply and patch) returns a report:

FieldMeaning
okTrue when there are no errors
errorsWhy the contract can't be built. Must be empty to apply
fixesWhat the normalizer matched, filled or dropped. Make each one explicit in the contract so your intent is clear
warningsBuilt fine but worth a look (e.g. pages nobody can reach)
summaryapp, roles, entities (fields, seed row counts), pages (name, route, personas), workflows(id, trigger, actions). Null when the contract doesn't parse

An apply with errors is refused with 422, code: "CONTRACT_INVALID" and the report. Nothing is half-built.

Patch ops

After the first apply, send small typed edits with patch_contract instead of resending the whole contract. Each op is validated, then the whole contract is re-normalized and rebuilt. dry_run reports without building. Up to 50 ops per call.

OpFieldsNotes
add_entityname, fields[], title?, personas?Also adds its management page
add_fieldentity, fieldAdded fields are optional
update_fieldentity, name, enum_values?, optional?Enum options can be added, not removed while in use
remove_fieldentity, nameLeaves the app; the column and its data stay
set_workflowworkflowAdd or replace by id (kebab-case)
remove_workflowid
add_pagepage (name, route, title, …)The route must be new
set_pagepage, title?, icon?, personas?, layout?, form?, chrome?Route and name stay fixed
remove_pagepage
set_componentcomponentAdd or replace by name; new source is gated on apply
remove_componentnamePages that placed it drop the node
edit_componentname, edits[{ find, replace }]Each find must occur exactly once
set_entityentity, access?, owner_refs?, actor_ref?, label_field?, rules?A present key replaces; null removes
set_apptheme?, direction?, nav_style?, density?, brand_color?, auth?brand_color: "" removes it; auth: null removes the auth section
set_demo_loginslogins[{ persona, row }]Replaces the extra demo sign-ins (max 8)

Example

[
  { "op": "add_field", "entity": "job", "field": { "name": "priority", "type": "enum", "enum_values": ["low", "normal", "urgent"] } },
  { "op": "update_field", "entity": "job", "name": "status", "enum_values": ["open", "assigned", "done", "cancelled"] },
  { "op": "set_app", "brand_color": "#0F766E" }
]

Send it as edits to patch_contract, as the body { "edits": [...] } to POST /dev/v1/projects/:id/patch, or save it as a file and run appbricx patch ops.json.

Next

  • Custom components & branding
  • Before-write rules
  • MCP server reference
Appbricx

Full-stack AI app builder for teams. Hosted cloud or private deploy into your account — multi-tenant, sandboxed, credit-metered.

Product

For coding agentsHow it worksDemoCapabilitiesPricingPrivate cloudBYOKFAQ

Resources

DocumentationBlogFor freelancersFor agenciesSupport

Company

ContactPrivate cloud / agencyPrivacyTerms

© 2026 Appbricx. All rights reserved.

TermsPrivacyCookiesAcceptable Use