Getting started · build it with Claude Code, or by hand

One typed model → idiomatic code, in your language.

Scaffold once, then tell Claude Code what you want"build the REST API for tasks," "add a priority field and update everything." Because meta init installs the MetaObjects skills, it knows the model is the source of truth: it edits the model and regenerates the code — it never hand-writes the boilerplate. Every command and output below is real verified, run against @metaobjectsdev/cli.

Set up the project and the agent context

Add the TypeScript CLI and scaffold the workspace. meta init also teaches your coding agent how to use MetaObjects:

npm i @metaobjectsdev/cli
npx meta init

That writes a small, legible tree — your models go in metaobjects/:

your-project/
├── metaobjects/                 # ← your models live here (.yaml or .json)
│   └── meta.common.json         #   starter file (shared abstracts)
├── metaobjects.config.ts        # ← targets: dialect + what to generate
├── CLAUDE.md                    # wired to auto-load the context below
├── .metaobjects/                # tool state + agent docs (AGENTS.md, CLAUDE.md)
└── .claude/skills/              # five skills your agent now has:
    ├── metaobjects-authoring/   #   write & edit models
    ├── metaobjects-codegen/     #   run codegen
    ├── metaobjects-runtime-ui/  #   wire up runtime + UI
    ├── metaobjects-prompts/     #   typed LLM prompts
    └── metaobjects-verify/      #   drift checks

This is the part that feels like magic. You describe outcomes; Claude Code knows the MetaObjects way — change the model, regenerate — because the codegen skill it loaded says so ("pattern-derivable-from-metadata = regenerate"). You never ask it to hand-write a route or a table.

Author the model

Model a Task — a title (required, max 120), a status of open / active / done, and a due date.

→ Claude writes metaobjects/meta.tasks.yaml using the metaobjects-authoring skill. You review a few readable lines.

Build the API — later, on demand

Now expose a REST API for tasks.

→ It knows REST routes are derived from the model, so it runs meta gen. The entity, typed data-access, and /tasks endpoints are generated — not one route hand-written.

Change the model — the API follows

Add a priority field, and make each Task belong to a Project. Then update everything.

→ It edits the model and re-runs meta gen. The schema, validation, data-access, and API all regenerate to match — and your hand-written logic in *.extra.ts is preserved. If anything no longer fits, it breaks at compile time, pointing at the exact line.

The rest of this page is what's happening underneath — useful if you'd rather drive it by hand, or just want to see it.

The model is language-neutral — the same metaobjects/ drives every port. You pick the toolchain for the language you ship. This guide uses TypeScript; swap the row for yours:

LanguageInstallGenerate
TypeScript (this guide)npm i @metaobjectsdev/climeta gen
C# / .NETdotnet add package MetaObjectsdotnet meta gen
Java / Kotlincom.metaobjects (Maven Central)mvn metaobjects:generate
Pythonpip install metaobjectsmetaobjects gen

For TypeScript, metaobjects.config.ts chooses what you generate — the SQL dialect and which generators run:

// metaobjects.config.ts
export default defineConfig({
  outDir:  "src/generated",
  dialect: "sqlite",        // sqlite | postgres | d1
  generators: [
    entityFile(),               // Drizzle table + Zod schemas + typed constants
    queriesFile(),              // typed data-access (findById, list, create…)
    routesFile(),               // REST endpoints (Fastify)
    // formFile(),              // opt-in: React form components
    barrel(),
  ],
});

Whether the agent writes it or you do, a model is just a small file in metaobjects/. Here's the one from above — metaobjects/meta.tasks.yaml:

# metaobjects/meta.tasks.yaml — the source of truth (.json works too)
metadata:
  package: app
  children:
    - object.entity:
        name: Task
        children:
          - source.rdb:       { table: tasks }
          - field.long:       { name: id }
          - field.string:     { name: title, required: true, maxLength: 120 }
          - field.enum:       { name: status, values: [open, active, done] }
          - field.timestamp:  { name: createdAt, required: true }
          - identity.primary: { fields: [id], generation: increment }
$ npx meta gen
meta gen — sqlite, src/generated

  NEW        src/generated/Task.ts
  NEW        src/generated/Task.queries.ts
  NEW        src/generated/Task.routes.ts
  NEW        src/generated/index.ts

  4 written

That's a real run. You get three things per entity (plus a barrel):

// src/generated/Task.ts  (excerpt — real output)
export const tasks = sqliteTable("tasks", {
  id:        integer("id").primaryKey({ autoIncrement: true }),
  title:     text("title").notNull(),
  status:    text("status", { enum: ["open", "active", "done"] }),
  createdAt: text("created_at").notNull(),
}, (t) => [ check("chk_tasks_status", sql`status IN ('open','active','done')`) ]);

export const TaskInsertSchema = z.object({
  title:  z.string().min(1).max(120),
  status: z.enum(["open", "active", "done"]).optional(),
  createdAt: z.string(),
});

Import the generated code into your app. It runs with no MetaObjects dependency — Drizzle, Zod, and Fastify only:

import Fastify from "fastify";
import { taskRoutes } from "./generated/Task.routes";

const app = Fastify();
app.register(taskRoutes);   // GET/POST/PATCH/DELETE /tasks, validated
await app.listen({ port: 3000 });

The custom business logic that makes the app yours? You write it — against the generated, typed code. Each file points at a *.extra.ts seam for additional queries, auth, and handlers that codegen preserves on regen. Change the model, re-run meta gen, and anything that no longer fits breaks at compile time — pointing you at the exact line.

As the model evolves, meta verify fails the build the moment generated code, schema, or prompt templates drift from it:

npx meta verify              # --templates (default): prompt-template drift
npx meta verify --codegen    # generated code vs the model
npx meta verify --db         # live database schema vs the model

Using a different assistant than Claude Code? Point it at metaobjects.dev/llms.txt for version-pinned context.

The monorepo is the source of truth — full quickstarts, the spec, and the cross-language conformance fixtures live there. This page is the on-ramp.