Manifest Reference
The manifest.json is the single source of truth for your application. It declares your entities, fields, permissions, and actions. Everything else — tables, CRUD API, constraints — is derived from it automatically.
Top-level fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
appId |
string |
Yes | Unique identifier. Becomes the PostgreSQL schema name. Immutable after first deploy. | |
name |
string |
Yes | Display name shown in the Studio. | |
type |
string |
No | "app" |
Application type: "app", "integration", or "agent". |
version |
string |
No | "0.0.1" |
Semantic version for deployment history. |
description |
string |
No | "" |
Short description shown in the Studio. |
dataContract |
array |
No | [] |
Array of entity definitions. |
permissions |
object |
No | Object with a permissions array of permission keys. |
|
actions |
array |
No | [] |
Array of RPC action declarations. |
configSchema |
object |
No | JSON Schema for the app's configuration structure. | |
webhooks |
string[] |
No | [] |
Webhook event names the app can receive. |
instructions |
string |
No | Usage instructions for AI agents interacting with this app. | |
trigger |
object |
No | Entity trigger for auto-invoking agents on data changes. |
appId must be lowercase snake_case (letters, digits, underscores). Hyphens are not allowed. It is immutable after first deploy and becomes the PostgreSQL schema name directly (e.g. appId: "crm" creates schema crm).dataContract[]
Each entry becomes a PostgreSQL table. System columns (id, created_at, updated_at) are added automatically. You can override id with a custom primary key, but created_at and updated_at are always managed by the system and cannot be overridden.
| Field | Type | Required | Description |
|---|---|---|---|
entityName |
string |
Yes | Table name. Used in API routes (/api/v1/apps/{appId}/collections/{entityName}). |
fields |
array |
Yes | Array of field definitions. |
identityKind |
string |
No | Federates records across apps (e.g. "contact"). Must be snake_case. |
identityKey |
string |
No | Field name used for identity lookup. Must reference an existing field. Required if identityKind is set. |
"dataContract": [
{
"entityName": "contacts",
"fields": [
{ "name": "first_name", "type": "text", "required": true },
{ "name": "last_name", "type": "text", "required": true },
{ "name": "email", "type": "text" },
{ "name": "phone", "type": "text" }
]
}
]
Identity Federation
Multiple apps can declare the same identityKind with different data. The SDK's useIdentity hook queries across all apps that share the same identity kind:
{
"entityName": "contacts",
"identityKind": "contact",
"identityKey": "email",
"fields": [...]
}
dataContract[].fields[]
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string |
Yes | Column name. created_at and updated_at are reserved (system-managed). |
|
type |
string |
Yes | One of the field types. | |
required |
boolean |
No | false |
Adds a NOT NULL constraint. |
defaultValue |
any |
No | Default on insert. Accepts JSON values: "draft", 0, false, {"key": "val"}. |
|
enumValues |
string[] |
No | Allowed values. Generates a CHECK constraint. |
|
references |
object |
No | For entity_link only. Generates a FOREIGN KEY. See references. |
|
isPrimaryKey |
boolean |
No | false |
Custom primary key. Rarely needed. |
Field types
Fields map directly to PostgreSQL types:
| Type | PostgreSQL | Description |
|---|---|---|
text |
TEXT |
Variable-length string. |
number |
DOUBLE PRECISION |
64-bit floating-point. |
boolean |
BOOLEAN |
True/false. |
date |
DATE |
Calendar date (YYYY-MM-DD). |
timestamp |
TIMESTAMPTZ |
Date and time in UTC. |
json |
JSONB |
Arbitrary JSON. Supports GIN indexing. |
file |
TEXT |
File reference from the upload endpoint. |
entity_link |
UUID REFERENCES |
Foreign key. Requires references. |
[text] |
TEXT[] |
Array of strings. |
[number] |
DOUBLE PRECISION[] |
Array of numbers. |
Relationships and constraints
Use entity_link to create foreign keys. The references object supports three reference types:
Local references
Reference an entity within the same app:
{
"name": "contact_id",
"type": "entity_link",
"references": { "entity": "contacts", "field": "id" }
}
Core references
Reference system entities using the core: prefix. Currently only core:users is supported:
{
"name": "assigned_to",
"type": "entity_link",
"references": { "entity": "core:users", "field": "id" }
}
This creates a foreign key to rootcx_system.users(id) with ON DELETE SET NULL.
crm:contacts) are not yet supported and will return a validation error.Constrained values
Use enumValues to restrict text field values:
{
"name": "stage",
"type": "text",
"enumValues": ["lead", "qualified", "proposal", "won", "lost"],
"defaultValue": "lead"
}
actions[]
Actions are RPC methods declared in the manifest. Declaring them makes them discoverable by the Studio, integrations, and AI agents.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Must match a key in serve(). |
name |
string |
Yes | Display name for Studio and AI agents. |
description |
string |
No | What the action does. |
inputSchema |
object |
No | JSON Schema for input parameters. |
outputSchema |
object |
No | JSON Schema for the return value. |
permissions
Add permission keys to control access. Keys follow the entity.action convention in the manifest. On deploy, they are automatically prefixed with app:<appId>: to form the full namespaced key.
"permissions": {
"permissions": [
{ "key": "contacts.create", "description": "Create contacts" },
{ "key": "contacts.read", "description": "View contacts" },
{ "key": "contacts.update", "description": "Edit contacts" },
{ "key": "contacts.delete", "description": "Delete contacts" }
]
}
These become app:sales_crm:contacts.create, app:sales_crm:contacts.read, etc. If you don't declare explicit permissions, the system auto-generates CRUD keys from your dataContract entities.
configSchema
An optional JSON Schema that defines the structure of app-level configuration:
"configSchema": {
"type": "object",
"properties": {
"defaultCurrency": { "type": "string", "default": "USD" },
"enableNotifications": { "type": "boolean", "default": true }
}
}
Triggers
For agent apps, define a trigger to auto-invoke the agent on entity changes:
"trigger": {
"app_id": "crm",
"entity": "contacts",
"on": ["INSERT", "UPDATE", "DELETE"]
}
When a matching database operation occurs in the target app, the agent is invoked via the hook system with the change details (entity, operation, record data, old record).
Schema sync
Every manifest install diffs your dataContract against the live PostgreSQL schema and applies the minimum DDL in a single transaction per table. Schema sync runs automatically on install and deploy — there are no manual migration files to manage.
| Change | DDL |
|---|---|
| New entity | CREATE TABLE |
| New field | ALTER TABLE ... ADD COLUMN |
| Field type changed | ALTER TABLE ... ALTER COLUMN ... TYPE with USING cast |
required changed |
SET / DROP NOT NULL |
defaultValue changed |
SET / DROP DEFAULT |
enumValues updated |
DROP CONSTRAINT + ADD CONSTRAINT CHECK |
| Field removed | DROP COLUMN ... CASCADE |
| Entity removed | DROP TABLE ... CASCADE |
Changes are applied in a safe order within a single transaction. Type conversions use appropriate USING clauses where possible.
id, created_at, and updated_at are protected.Full example manifest
{
"appId": "sales_crm",
"name": "Sales CRM",
"version": "1.0.0",
"description": "Manage contacts, companies, and deals with a sales pipeline.",
"dataContract": [
{
"entityName": "contacts",
"fields": [
{ "name": "first_name", "type": "text", "required": true },
{ "name": "last_name", "type": "text", "required": true },
{ "name": "email", "type": "text" },
{ "name": "phone", "type": "text" },
{ "name": "company_id", "type": "entity_link", "references": { "entity": "companies", "field": "id" } },
{ "name": "assigned_to", "type": "entity_link", "references": { "entity": "core:users", "field": "id" } }
]
},
{
"entityName": "companies",
"fields": [
{ "name": "name", "type": "text", "required": true },
{ "name": "industry", "type": "text" },
{ "name": "website", "type": "text" }
]
},
{
"entityName": "deals",
"fields": [
{ "name": "title", "type": "text", "required": true },
{ "name": "value", "type": "number" },
{ "name": "stage", "type": "text", "enumValues": ["lead", "qualified", "proposal", "won", "lost"], "defaultValue": "lead" },
{ "name": "contact_id", "type": "entity_link", "references": { "entity": "contacts", "field": "id" } },
{ "name": "company_id", "type": "entity_link", "references": { "entity": "companies", "field": "id" } },
{ "name": "notes", "type": "json" }
]
}
],
"actions": [
{
"id": "pipeline",
"name": "Pipeline Summary",
"description": "Returns deal counts per stage"
}
],
"permissions": {
"permissions": [
{ "key": "contacts.create", "description": "Create contacts" },
{ "key": "contacts.read", "description": "View contacts" },
{ "key": "contacts.update", "description": "Edit contacts" },
{ "key": "contacts.delete", "description": "Delete contacts" },
{ "key": "companies.read", "description": "View companies" },
{ "key": "deals.create", "description": "Create deals" },
{ "key": "deals.read", "description": "View deals" },
{ "key": "deals.update", "description": "Edit deals" },
{ "key": "deals.delete", "description": "Delete deals" }
]
}
}