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.
1 · Install & scaffold
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
2 · Then just tell it what you want
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.
3 · Pick your language
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:
| Language | Install | Generate |
|---|---|---|
| TypeScript (this guide) | npm i @metaobjectsdev/cli | meta gen |
| C# / .NET | dotnet add package MetaObjects | dotnet meta gen |
| Java / Kotlin | com.metaobjects (Maven Central) | mvn metaobjects:generate |
| Python | pip install metaobjects | metaobjects 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(), ], });
4 · Author a model
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 }
5 · Generate
$ 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):
Task.ts— the entity: a Drizzle table, Zod insert/update schemas, a typedTaskconstants object (fields, labels, validation rules), and filter/sort allowlists.Task.queries.ts— typed data-access:findTaskById(db, id),listTasks(db, opts), create/update/delete.Task.routes.ts— REST:mountCrudRoutes()wires the 5 standard endpoints.
// 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(), });
6 · Use it — and add your own logic
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.
7 · Keep it honest
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.
Go deeper
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.
