DocsBuildBuild an Application

Build an Application

You describe what you want to your AI coding agent. It generates the code. You review it and deploy. But to review effectively, and to ask for the right things, you need to understand how a RootCX application is structured.

This page explains every piece of an internal tool and how it connects to the Core. Each section shows you the concept with an example, then links to the full reference.


Structure of an application

my_crm/
├── manifest.json         # Data model + permissions + config (required)
├── backend/
│   └── index.ts          # Custom logic (optional)
└── src/
    └── App.tsx           # User interface (optional)

The manifest is the only required piece. It tells the Core what data your tool manages, who can access it, what webhooks it receives, what crons it runs, and what actions it exposes. The Core reads it and creates everything: database tables, CRUD APIs, permission policies, webhook URLs, cron schedules.

The frontend is a React app that uses @rootcx/sdk to interact with the Core's Data API. It gets served at /apps/{appId}/.

The backend is TypeScript code that runs as an isolated Bun process supervised by the Core. It handles custom logic: RPC methods, background jobs, scheduled tasks, webhook handlers, and direct database access.

Your AI coding agent generates all three. You review and deploy with rootcx deploy.


The manifest

The manifest.json is the single source of truth for your application. Everything the Core does for you is derived from it.

Top-level fields

Field Required Description
appId Yes Unique identifier. Lowercase snake_case. Becomes the PostgreSQL schema name. Immutable after first deploy.
name Yes Display name shown in the dashboard.
version No Semantic version (default: "0.0.1").
description No Short description.
type No "app" (default), "integration", or "agent".
icon No Icon identifier.
dataContract No Array of entity definitions.
permissions No Permission key declarations.
actions No Named RPC operations discoverable by AI agents.
webhooks No Inbound webhook definitions.
crons No Declarative cron schedules.
public No Public access surface (no auth required).
configSchema No JSON Schema for app-level configuration.
instructions No Usage instructions surfaced to AI agents via list_integrations.
trigger No Auto-invoke an agent on entity changes in another app.

Full reference: Manifest Reference.


Data model

An entity is a PostgreSQL table. You declare them in the dataContract array:

{
  "appId": "sales_crm",
  "name": "Sales CRM",
  "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" } },
        { "name": "stage", "type": "text", "enum_values": ["lead", "qualified", "proposal", "won", "lost"], "default_value": "lead" }
      ]
    }
  ]
}

Every entity gets id, created_at, and updated_at automatically. When you deploy, the Core creates the PostgreSQL table and generates CRUD API endpoints.

Field types

Type What it stores
text Variable-length string
number 64-bit floating-point number
boolean True/false
date Calendar date (YYYY-MM-DD)
timestamp Date and time in UTC
json Arbitrary structured data
uuid UUID value
file Uploaded file reference (max 50 MB)
entity_link Foreign key to another entity
[text] Array of strings
[number] Array of numbers

Field options

Option Description
required NOT NULL constraint.
default_value Default on insert ("draft", 0, false, {"key": "val"}).
enum_values Restrict to specific values (generates a CHECK constraint).
is_primary_key Custom primary key.
on_delete For entity_link: "cascade", "restrict", or "set_null". Default depends on whether the field is required.

Relationships

Use entity_link to create foreign keys. Reference entities in the same app, or core:users for platform users:

{ "name": "contact_id", "type": "entity_link", "references": { "entity": "contacts", "field": "id" } },
{ "name": "assigned_to", "type": "entity_link", "references": { "entity": "core:users", "field": "id" } }

Identity federation

If multiple apps manage the same kind of entity (contacts in a CRM, contacts in support), federate them:

{
  "entityName": "contacts",
  "identityKind": "contact",
  "identityKey": "email",
  "fields": [...]
}

The SDK's useIdentity hook queries across all apps that share the same identityKind.

Full reference: Data API.


Permissions

Declare permission keys in the manifest to define what actions exist:

"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" }
  ]
}

On deploy, keys are prefixed with app:{appId}: (e.g., app:sales_crm:contacts.create). Assign them to roles, roles to users. The Core enforces on every request before any SQL runs.

If you do not declare permissions, the Core auto-generates CRUD keys from your entities.

Full reference: RBAC.


The backend

The backend runs as an isolated Bun process. The Core supervises it, restarts it on crash, routes RPC calls to it, and delivers jobs. Your AI coding agent writes all backend code. Here is what the backend can do:

Capability Description
RPC methods Custom endpoints your frontend or other apps can call. 30-second timeout.
Background jobs Durable queue. Jobs survive restarts, retried automatically.
Scheduled jobs Recurring crons (every minute, every 10 seconds, daily at 9 AM).
Webhook handlers Receive HTTP callbacks from external services (Stripe, GitHub, etc.).
Direct database access PostgreSQL connection string available at startup.
Secrets Injected as environment variables from the encrypted vault.
Collection operations Read/write entities directly via IPC (no HTTP round-trip).
File storage Upload files via IPC nonce URLs.
Custom events Emit named events consumed by other parts of the system.

The backend receives caller context on every RPC call: userId, email, and authToken (the JWT bearer token you can use for Core API calls from your handler).

Full reference: Backend Development.


Webhooks

Declare webhooks in the manifest to receive HTTP callbacks from external services:

{
  "webhooks": [
    { "name": "stripe", "method": "onStripePayment" },
    { "name": "github", "method": "onGithubPush" }
  ]
}

On deploy, the Core generates a unique 64-character token per webhook. External services POST to /api/v1/hooks/{token}. The Core routes to your backend with headers, parsed body, and raw body (base64, for signature verification).

Tokens are stable across re-deploys. Removing a webhook from the manifest deletes it.

Full reference: Webhooks.


Scheduled jobs (crons)

Declare crons in the manifest for recurring work:

{
  "crons": [
    {
      "name": "daily-check",
      "schedule": "0 9 * * *",
      "method": "onDailyCheck",
      "payload": { "task": "check_replies" },
      "overlapPolicy": "skip"
    }
  ]
}

Schedule formats: standard 5-field cron (0 9 * * *), seconds interval (10 seconds), or $ for last day of month. All times GMT unless you set timezone.

Crons can also be created at runtime via the API or the SDK's useCrons hook.

Full reference: Scheduled Jobs.


Actions

Actions make your backend's RPC methods discoverable by AI agents and integrations:

"actions": [
  {
    "id": "pipeline",
    "name": "Pipeline Summary",
    "description": "Returns deal counts per stage",
    "inputSchema": { "type": "object", "properties": { "period": { "type": "string" } } },
    "outputSchema": { "type": "object", "properties": { "counts": { "type": "object" } } }
  }
]

AI agents discover actions via the list_actions tool and call them via call_action, if they have permission app:{appId}:action:{actionId}.


Public access

By default, all endpoints require authentication. To expose specific RPCs or collections without auth (public forms, status pages, embeds):

{
  "public": {
    "rpcs": [
      { "name": "submitForm", "scope": [] },
      { "name": "checkStatus", "scope": ["orderId"] }
    ],
    "collections": ["public_announcements"]
  }
}

File uploads

Add a file field to any entity. Max 50 MB per file. Supported formats: CSV, JSON, XML, TXT, PDF, PNG, JPG, JPEG, GIF, WebP, SVG, XLSX, XLS, DOC, DOCX, ZIP, GZ, Parquet.

Upload endpoint: POST /api/v1/apps/{appId}/upload (multipart/form-data).


The frontend

The frontend uses @rootcx/sdk (React hooks) to interact with the Core. Served at /apps/{appId}/.

Hook Purpose
useAuth() Login, registration, logout, OIDC SSO, current user
useAppCollection(appId, entity, query?) List, filter, create, update, delete records
useAppRecord(appId, entity, id) Single record CRUD
usePermissions() Check RBAC permissions client-side
useCrons(appId) Manage scheduled jobs
useIntegration(id) Connect and call integration actions
useIdentity(kind, query?) Federated query across apps
useCoreCollection(entity) Read-only access to platform data (users)
Component Purpose
AuthGate Pre-built login/registration UI with OIDC support
Authorized Render children conditionally based on RBAC permissions
PermissionsProvider Share permissions fetch across multiple components

Full reference: React SDK.


Evolving your application

Tell your AI coding agent what to change. It updates the manifest. You deploy again. The Core diffs the manifest against the live database and applies only what changed. No migration files to manage.

The Core drops columns removed from the manifest. Only id, created_at, and updated_at are protected.
appId is immutable. It becomes the PostgreSQL schema name. Changing it after first deploy requires manual data migration.

Full reference: Manifest Reference - Schema Sync.


Deploy

rootcx deploy

The CLI handles dependency install, frontend build, manifest sync, backend deployment, and static asset publishing. Idempotent: deploy as many times as you want.


Next steps