ObjectStackObjectStack

Anatomy of an ObjectStack App

A tour of the metadata an AI agent writes for you — objects, actions, views, apps — and how to read it so you can verify the result in the Console.

Anatomy of an ObjectStack App

An ObjectStack app is its metadata: a few hundred lines of typed definitions for objects, actions, views, apps, automation, and permissions. In the AI-first workflow, an agent writes these files for you — you rarely type them by hand.

But you do need to read them. In AI development the human's job shifts from writing code to verifying what the agent produced: is the object modelled right, is that predicate scoped correctly, does the app match what you asked for? This page is a tour of the pieces so you can review an agent's work with confidence — and catch the things the validation gate can't.

Want to build something, not just read about the pieces? Follow Build with Claude Code — it runs the whole loop (describe → agent authors → validate → verify) on a live app. New here? The Example Apps are working code you can run today.

What you build

Everything an agent writes falls into one of a few areas — the same way the work divides when you build an app. This is the map to hold in your head when you read a project:

AreaThe metadata you author (src/…)What ObjectStack derives from it
DataObjects, fields, relationships, validation (src/objects)Database schema, CRUD APIs, type safety
AutomationFlows, workflows, triggers, approvals (src/flows)Event handlers, approval chains, scheduled jobs
InterfaceViews, apps, dashboards, actions (src/views, src/apps)The Console UI, navigation, responsive layout
AccessRoles, permissions, sharing, row-level securityMiddleware, RLS policies, field masking
AIAgents, tools, RAG, MCP exposure (src/agents)Chat, search indexes, an MCP server

The through-line: you author intent once as metadata, and the runtime derives the database, the API, the UI, and the AI tool surface from it. That's why an agent can build across all of these in one coherent change — and why reading the metadata tells you everything about how the app behaves.

Reading an object

The object is the heart of every app. Here's a representative one, in the same ObjectSchema.create + Field.* form the scaffolder and the agent use:

src/objects/ticket.object.ts
import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Ticket = ObjectSchema.create({
  name: 'support_desk_ticket',        // ← snake_case, namespace-prefixed
  label: 'Ticket',
  pluralLabel: 'Tickets',
  icon: 'life-buoy',
  description: 'A customer support request.',

  fields: {
    subject: Field.text({ label: 'Subject', required: true, searchable: true, maxLength: 200 }),
    description: Field.textarea({ label: 'Description' }),

    status: Field.select({
      label: 'Status',
      required: true,
      options: [
        { label: 'Open', value: 'open', color: '#3B82F6', default: true },
        { label: 'Pending', value: 'pending', color: '#F59E0B' },
        { label: 'Resolved', value: 'resolved', color: '#10B981' },
        { label: 'Closed', value: 'closed', color: '#6B7280' },
      ],
    }),
  },

  sharingModel: 'private',      // org-wide default (OWD) — required by the security gate
  enable: { apiEnabled: true, searchable: true },
});

What to check when you read one:

  • Names are snake_case and namespace-prefixed (support_desk_ticket) — the scaffolder derives the prefix from your project name.
  • Field types are Field.* builders (text, textarea, select, date, lookup, …), not raw strings. A select's options carry the exact labels, values, and colors you'll see in the UI — verify these match your intent.
  • required, default, searchable encode behavior the UI and API inherit automatically.
  • sharingModel is the org-wide default for records the user doesn't own — an explicit, authored security decision (private unless you have a reason otherwise). The validation gate rejects custom objects that omit it.

This one definition powers the REST API, the Console UI, and the MCP tools exposed to AI agents. Define it once, and ObjectStack derives the rest — see Data Modeling for the full field catalog and relationships.

Reading the other pieces

The remaining domains follow the same shape — a typed factory call per file. When reviewing an agent's work, these are the two things most worth a close read:

  • Actions (defineAction) — buttons and bulk operations. Their visible / disabled predicates are CEL, record-scoped: record.status, never a bare status. A bare reference silently hides the action on every record — the single most common AI mistake, which is exactly why os validate rejects it. See it in action in Build with Claude Code → the gate.
  • Views & Apps (defineView, App.create) — the list/form lenses and the navigation. Reading these tells you what the user will actually see and click.

For the full authoring surface of each, load the matching skill or read UI Metadata and Automation.

Reading a project's layout

Metadata is grouped by domain under src/. This map lets you navigate any ObjectStack project — including one an agent just generated:

support-desk/
├── objectstack.config.ts     # defineStack() — the single entry point, wires it all
├── src/
│   ├── objects/              # Data models (required)
│   ├── actions/              # Buttons, bulk operations
│   ├── views/                # List / form / kanban lenses
│   ├── apps/                 # Navigation shells
│   ├── flows/                # Automation logic
│   ├── dashboards/           # Analytics
│   └── agents/               # AI agents
└── test/                     # Tests

Each folder has an index.ts barrel that re-exports its metadata; those barrels are imported into objectstack.config.ts. There is no filename-suffix magic — metadata is wired in through those explicit imports, so objectstack.config.ts is the one place that tells you what's actually in the app.

How you verify

Reading the metadata is half of verification; running the app is the other half. Two commands, both of which the agent runs for you but you can run yourself:

npx os validate     # the gate — schema + CEL predicates + widget bindings (no artifact)
npx os dev --ui     # boot the app, then open http://localhost:3000/_console/

os validate proves the metadata is well-formed; the Console at /_console/ proves it does what you meant — create a record, watch an action show/hide, confirm the nav and filters. That visual check is the human's half of every build loop. See Validating Metadata for what the gate catches, and Build with Claude Code for the full describe → author → validate → verify cycle.

Go deeper

Next steps

On this page