---
title: "Creating Templates"
description: "How to create and publish your own agent-native app templates."
---

# Creating Templates

<!-- i18n-copy-ignore: Agent-Native is a proper name; punctuation is locale-invariant. -->

Templates are complete, cloneable agent-native apps that solve a real workflow. The first-party templates are built with the same framework surface you use: React routes for the UI, PostgreSQL data through Drizzle, actions for operations, workspace resources for agent behavior, and polling sync so the agent and UI stay aligned.

Use `@agent-native/core` for the runtime contract: actions, server plugins,
database helpers, app state, routing helpers, and agent chat. Use
`@agent-native/toolkit` for reusable app-building UI and helpers such as shared
shadcn-style primitives, toast/mobile hooks, date pickers, and simple app shell
slots. Keep template-specific variants local when they encode product behavior
or visual identity.

Keep the three layers in mind when deciding where a capability belongs:

- **Core - the framework:** the required runtime and data contract every app can use.
- **Toolkit - optional reusable pieces:** shared UI and product systems that apps can adopt, compose, or fork.
- **Templates - optional apps:** complete domain products built on Core, commonly using Toolkit, that people can fork and use as starting points.

Toolkit and Templates are optional. A template is not the framework itself, and
Toolkit does not replace Core.

A good template:

- Solves one workflow end-to-end, with useful seed data or an empty-state flow.
- Stores durable state in PostgreSQL/PGlite, not JSON files.
- Defines app operations as `defineAction()` actions.
- Exposes navigation and selection through application state.
- Ships a clear `AGENTS.md` plus focused skills for non-obvious workflows.
- Registers onboarding steps for required providers and secrets.
- Works as a standalone app and as part of a multi-app workspace.

## Start from Chat {#start-from-chat}

Use the Chat template when you want a minimal app with the framework wiring already in place:

```bash
npx --yes @agent-native/core@latest create my-template --template chat --standalone
```

For a workspace with multiple apps, run the picker and include Chat with any domain templates you want:

```bash
npx --yes @agent-native/core@latest create my-platform
```

Chat gives you auth, durable chat threads, PostgreSQL-backed resources, tools, application state, actions, and polling sync. You add the domain model and product UI.

If you are not building a reusable UI template yet, use [Automation-First Apps](/docs/pure-agent-apps): define actions, run them without a browser, and add UI later when the workflow needs a durable surface.

## Project Structure {#project-structure}

Every template follows the same broad layout:

<FileTree
  id="doc-block-1pqnp29"
  title="Template project layout"
  entries={[
    {
      path: "app/",
      note: "React frontend",
    },
    {
      path: "app/root.tsx",
      note: "HTML shell and providers",
    },
    {
      path: "app/routes/",
      note: "React Router file routes",
    },
    {
      path: "app/components/",
      note: "Template UI",
    },
    {
      path: "app/hooks/",
      note: "UI state and data hooks",
    },
    {
      path: "actions/",
      note: "defineAction operations — the single source of truth",
    },
    {
      path: "server/db/schema.ts",
      note: "Drizzle schema",
    },
    {
      path: "server/plugins/db.ts",
      note: "additive migrations",
    },
    {
      path: "server/plugins/",
      note: "startup integrations",
    },
    {
      path: "server/routes/api/",
      note: "custom routes only when actions are not enough",
    },
    {
      path: "shared/types.ts",
      note: "shared client/server types",
    },
    {
      path: ".agents/skills/",
      note: "<skill>/SKILL.md — agent guidance for complex workflows",
    },
    {
      path: "AGENTS.md",
      note: "template-specific agent instructions",
    },
    {
      path: "package.json",
    },
    {
      path: "react-router.config.ts",
    },
    {
      path: "vite.config.ts",
    },
  ]}
/>

Do not add a `data/` directory for application state. Durable app data belongs in PostgreSQL/PGlite, and the UI reads it through actions or typed server handlers.

The four areas of every template wire together through one shared action surface and one PostgreSQL/PGlite database — the agent and the UI are equal partners over the same operations:

<Diagram id="doc-block-1rbf8wn" title={"How a template's four areas connect"} summary={"The UI and the agent both reach PostgreSQL/PGlite through the same actions; application state and polling sync keep them aligned."}>

```html
<div class="diagram-tmpl">
  <div class="diagram-col">
    <div class="diagram-node">
      React UI<br /><small class="diagram-muted">app/routes · components</small>
    </div>
    <div class="diagram-node">
      Agent<br /><small class="diagram-muted">AGENTS.md · skills</small>
    </div>
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-panel center">
    <span class="diagram-pill accent">Actions</span
    ><small class="diagram-muted">defineAction()</small>
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-box" data-rough>
    PostgreSQL via Drizzle<br /><small class="diagram-muted"
      >additive schema</small
    >
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&#8635;</div>
  <div class="diagram-pill ok">Polling sync</div>
</div>
```

```css
.diagram-tmpl {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.diagram-tmpl .diagram-col {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.diagram-tmpl .diagram-arrow {
  font-size: 22px;
  line-height: 1;
}
.diagram-tmpl .center {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
```

</Diagram>

## Model Data In PostgreSQL {#data-models}

Define domain tables with the framework Drizzle helpers for PostgreSQL compatibility. Use local PGlite during development and hosted Postgres in deployed environments:

```ts filename="server/db/schema.ts"
import {
  table,
  text,
  integer,
  now,
  ownableColumns,
  createSharesTable,
} from "@agent-native/core/db/schema";

export const projects = table("projects", {
  id: text("id").primaryKey(),
  title: text("title").notNull(),
  status: text("status", {
    enum: ["draft", "active", "archived"],
  })
    .notNull()
    .default("draft"),
  sortOrder: integer("sort_order").notNull().default(0),
  ...ownableColumns(),
  createdAt: text("created_at").notNull().default(now()),
  updatedAt: text("updated_at").notNull().default(now()),
});

export const projectShares = createSharesTable("project_shares");
```

Schema changes must be additive. For new app-owned tables and columns, use
`drizzle-kit generate` to create reviewed SQL under
`server/db/migrations/<id_name>.sql`, then load it through
`runDrizzleMigrations()` from the release migration command on Node.js
deployments. Keep generated SQL in entries embedded in the deployment bundle
and pass them to `runMigrations()`. Keep using `runMigrations()` for existing
handwritten history, data backfills, or deferred JavaScript steps. Never use
destructive SQL, `drizzle-kit push`,
table renames, or column drops.

For app reads and writes, use Drizzle's query builder and operators from
`drizzle-orm`. Do not write product code with raw SQL when Drizzle can express
the query, and import schema helpers from `@agent-native/core/db/schema`.

```ts filename="server/plugins/db.ts"
import { runMigrations } from "@agent-native/core/db";

export default runMigrations(
  [
    {
      version: 1,
      sql: `CREATE TABLE IF NOT EXISTS projects (
      id TEXT PRIMARY KEY,
      title TEXT NOT NULL,
      status TEXT NOT NULL DEFAULT 'draft',
      sort_order INTEGER NOT NULL DEFAULT 0,
      owner_email TEXT NOT NULL,
      org_id TEXT,
      visibility TEXT NOT NULL DEFAULT 'private',
      created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
      updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
    )`,
    },
  ],
  { table: "my_app_migrations" },
);
```

Use the [Database](/docs/server-database) and [Security](/docs/security) docs before adding schemas that hold user or org data.

## Define Operations As Actions {#actions}

Actions are the single source of truth for app behavior. The agent calls them as tools, the frontend calls them through hooks, and other apps can reach them through MCP/A2A.

<AnnotatedCode
  id="doc-block-1o0ohdj"
  title="actions/create-project.ts"
  filename="actions/create-project.ts"
  language="ts"
  code={
    'import { defineAction } from "@agent-native/core/action";\nimport { getDb } from "../server/db/index.js";\nimport { nanoid } from "nanoid";\nimport { z } from "zod";\nimport * as schema from "../server/db/schema";\n\nexport default defineAction({\n  description: "Create a project.",\n  schema: z.object({\n    title: z.string().min(1).describe("Project title"),\n  }),\n  run: async ({ title }, ctx) => {\n    const db = getDb();\n    const id = nanoid();\n    await db.insert(schema.projects).values({\n      id,\n      title,\n      ownerEmail: ctx.userEmail,\n      orgId: ctx.orgId,\n    });\n    return { id, title };\n  },\n});'
  }
  annotations={[
    {
      lines: "2",
      note: "`getDb` is created per app via `createGetDb(schema)` in `server/db/index.ts`.",
    },
    {
      lines: "8",
      label: "Tool surface",
      note: "The `description` is what the agent reads to decide when to call this action as a tool.",
    },
    {
      lines: "9-11",
      label: "Typed contract",
      note: "One zod `schema` validates input from the agent, the UI, HTTP, MCP, and A2A.",
    },
    {
      lines: "18-19",
      label: "Scoped write",
      note: "Stamp `ownerEmail` / `orgId` from `ctx` so the row is correctly scoped for sharing and access checks.",
    },
  ]}
/>

Use `http: { method: "GET" }` or `readOnly: true` for read-only actions. Use `parallelSafe: true` only for mutating actions that are safe to run concurrently with same-turn tool calls. Use `toolCallable: false` for high-blast-radius actions that should not run from sandboxed tools.

## Build The UI {#ui}

Routes live in `app/routes/` and use React Router v8 file routing. Query data through actions or API handlers, and make mutations optimistic by default.

```tsx
import {
  useActionMutation,
  useActionQuery,
} from "@agent-native/core/client/hooks";
export default function ProjectsPage() {
  const { data: projects = [] } = useActionQuery("list-projects", {});
  const create = useActionMutation("create-project");

  return (
    <button onClick={() => create.mutate({ title: "Launch plan" })}>
      New project ({projects.length})
    </button>
  );
}
```

Wire live sync once near the app shell so React Query caches refresh when the agent, another tab, or an action changes data:

```tsx
import { useDbSync } from "@agent-native/core/client/hooks";
import { useQueryClient } from "@tanstack/react-query";

export function AppSync() {
  const queryClient = useQueryClient();
  useDbSync({ queryClient });
  return null;
}
```

**The agent-native promise: agent writes show up in the UI without a manual refresh.** `useActionQuery` is the easy path — every hook refetches when a mutating action emits `source: "action"`. If you reach for raw `useQuery` with a custom key (for example, a low-level client helper that reads integration status), fold the per-source counter into the queryKey for targeted refreshes:

```tsx
import { useChangeVersions } from "@agent-native/core/client/hooks";

const v = useChangeVersions(["dashboards", "action"]);
useQuery({
  queryKey: ["dashboard", id, v],
  queryFn: () => fetchDashboard(id),
  placeholderData: (prev) => prev, // no flicker on refetch
});
```

Common sources: `"action"` (every successful agent action — the reliable fallback), `"app-state"`, `"settings"`, plus any custom resource source your store emits via `recordChange`. See the `real-time-sync` skill for the full pattern.

Templates render with the default shadcn-style UI. If your company already has
its own React design system, see [Custom Design Systems](/docs/custom-design-system)
to register it as a typed adapter instead of adopting the default primitives.

## Add Application State {#application-state}

Application state is how the agent knows what the user is seeing. At minimum, add:

- A UI hook that writes semantic `navigation` state when routes, selected records, active tabs, or editor selections change.
- A `view-screen` action that reads that state and returns the current screen snapshot.
- A `navigate` action that writes a one-shot `navigate` command for the UI to consume.

Use `useAgentRouteState` for the UI hook so application-state writes, tab-scoped command reads, delete-after-read, and duplicate-command protection stay consistent:

```tsx
import { useAgentRouteState } from "@agent-native/core/client/navigation";
import { TAB_ID } from "@/lib/tab-id";

export function useNavigationState() {
  useAgentRouteState({
    browserTabId: TAB_ID,
    requestSource: TAB_ID,
    getNavigationState: ({ pathname, searchParams }) => ({
      view: pathname === "/" ? "home" : pathname.slice(1),
      selectedId: searchParams.get("id"),
    }),
    getCommandPath: (command: any) => command.path ?? "/",
    navigateOptions: { replace: true, flushSync: true },
  });
}
```

Keep shareable filters in URL query params. The framework exposes them to the agent as `<current-url>` and the built-in agent can change them with `set-search-params`; `navigation` should hold semantic IDs and aliases, not a second copy of the full query string.

For app navigation, prefer one `navigate` command that includes a same-origin
`path` when the URL is known. Do not also write `__set_url__` for the same move;
that key is reserved for the framework URL tools and URL-only filter changes.

```ts filename="actions/navigate.ts"
import { defineAction } from "@agent-native/core/action";
import { writeAppState } from "@agent-native/core/application-state";
import { z } from "zod";

export default defineAction({
  description: "Navigate the UI.",
  schema: z.object({
    view: z.enum(["home", "project"]),
    projectId: z.string().optional(),
    path: z.string().optional(),
  }),
  run: async (args) => {
    await writeAppState("navigate", args);
    return { ok: true };
  },
});
```

See [Context Awareness](/docs/context-awareness) for the full pattern.

## Use API Routes Sparingly {#api-routes}

Prefer actions for app operations. Create custom Nitro routes only for surfaces that cannot be actions cleanly:

- File upload or binary streaming.
- Public anonymous pages and webhooks.
- OAuth callbacks and provider-specific protocol handlers.
- Server-rendered public content.

Custom routes that touch ownable data must call `getSession(event)` and wrap database work in `runWithRequestContext({ userEmail, orgId }, fn)` before using access helpers.

## Write Agent Instructions {#write-agents-md}

`AGENTS.md` is the agent's map of your app — a small, skimmable file with a
purpose line, core rules, application-state keys, an action table, and a skills
index:

```markdown
# My Template

One workspace for projects, tasks, and notes.

## Core Rules

- Data lives in PostgreSQL via Drizzle. Use actions for all writes; schema is additive.
- Use `view-screen` before acting on "this project" if the screen is unclear.

## Application State

- `navigation.view`: `home` | `project`
- `navigation.projectId`: selected project on a project page

## Actions

| Action           | Purpose                  |
| ---------------- | ------------------------ |
| `list-projects`  | List accessible projects |
| `create-project` | Create a project         |
```

Update `AGENTS.md` whenever you add a new action, route, state key, or recurring
workflow. [Writing Agent Instructions](/docs/writing-agent-instructions) is the
full guide — how to keep `AGENTS.md` skimmable, what belongs in each of the four
guidance surfaces, and how to word skill and tool descriptions so the agent
triggers them reliably.

## Add Skills {#skills}

Use skills for detailed patterns that would bloat `AGENTS.md`: provider-specific APIs, import/export formats, complex editing flows, or domain terminology.

```markdown
---
name: project-imports
description: How to import projects from the legacy CSV export.
---

# Project Imports

Use this skill when the user uploads a legacy project CSV.

## Rules

- Validate required columns before creating rows.
- Use `create-project` for each project so ownership and sync are correct.
- Save rejected rows as a note attached to the import summary.
```

Store template skills in `.agents/skills/<name>/SKILL.md`. If users should be able to edit the guidance at runtime, surface it through workspace resources as well.

## Register Setup Steps {#onboarding}

If a template needs an API key, OAuth connection, or provider account, register an onboarding step instead of burying the requirement in a README.

```ts filename="server/plugins/onboarding.ts"
import { defineNitroPlugin } from "@agent-native/core/server";
import { registerOnboardingStep } from "@agent-native/core/onboarding";

export default defineNitroPlugin(() => {
  registerOnboardingStep({
    id: "github",
    title: "Connect GitHub",
    description: "Needed to import repositories and pull requests.",
    order: 100,
    methods: [
      {
        id: "token",
        kind: "form",
        primary: true,
        label: "Save token",
        payload: {
          fields: [
            { key: "GITHUB_TOKEN", label: "GitHub token", secret: true },
          ],
        },
      },
    ],
    isComplete: () => !!process.env.GITHUB_TOKEN,
  });
});
```

See [Onboarding & API Keys](/docs/onboarding).

## Make It Workspace-Ready {#workspace-ready}

Templates should fit naturally into [Multi-App Workspaces](/docs/multi-app-workspace), usually coordinated by [Dispatch](/docs/dispatch).

Checklist:

- Mount A2A through the framework agent chat plugin or `mountA2A()` so sibling apps can call your agent.
- Keep the agent card descriptions specific enough for Dispatch to route work accurately.
- Register required secrets/onboarding so setup appears in the sidebar and Dispatch can manage shared credentials.
- Keep cross-cutting instructions in workspace `AGENTS.md` or workspace resources, not copied into every app.
- Use sharing/access helpers for all ownable resources so org-scoped workspaces stay isolated.

## Publish A Template {#publishing}

Before sharing:

1. Run `pnpm install`, `pnpm typecheck`, and the template's tests.
2. Verify it works with no optional provider keys configured.
3. Check auth, sharing, and two-user data isolation.
4. Document required env vars and onboarding steps.
5. Include examples or seed rows through additive migrations, not tracked runtime data files.

Community templates install directly from a public GitHub repository. Put the
Agent-Native app at the repository root:

```bash
npx --yes @agent-native/core@latest create my-app --template community:owner/repo
```

Add `#branch`, `#tag`, or `#commit` when you want people to install a specific
revision:

```bash
npx --yes @agent-native/core@latest create my-app --template community:owner/repo#v1.0.0
```

The CLI downloads the source only. It does not install dependencies or run the
template's scripts. Community templates are third-party code, so review the
repository, license, dependencies, and install scripts before running
`pnpm install`.

To be listed in the community catalog, include:

- A focused, complete Agent-Native app at the repository root.
- A public repository with a license and a README that covers setup, required
  environment variables, and what the template does.
- A root `package.json` that depends on `@agent-native/core`.
- Passing typechecks and tests.
- Optionally, a hosted version people can try and a representative screenshot.

[Submit a community template](https://github.com/BuilderIO/agent-native/issues/new?template=community-template.yml)
when the repository is ready. Community listings stay separate from the
smaller first-party catalog and remain maintained by their authors.

## Contributing to the framework monorepo {#contributing}

### Test unpublished framework changes {#test-unpublished-framework-changes}

When you are working inside the framework monorepo and need a generated
workspace to use unpublished package or template changes, run create with the
local-package flag:

```bash
AGENT_NATIVE_CREATE_USE_LOCAL_CORE=1 pnpm --filter @agent-native/core create my-platform
```

The generated workspace links the local `@agent-native/core` and
`@agent-native/dispatch` packages, so changes to Core APIs, Dispatch workspace
behavior, or first-party templates can be tested before publishing. The package
`prepack` scripts build `dist` before linking, which keeps the generated
workspace pointed at current build output.

## What's next

- [**Writing Agent Instructions**](/docs/writing-agent-instructions) — how to
  keep `AGENTS.md` and skills skimmable as the template grows.
- [**Agent-Native Toolkit**](/docs/agent-native-toolkit) — reusable sharing,
  settings, history, and review surfaces instead of rebuilding them per app.
- [**Custom Design Systems**](/docs/custom-design-system) — render the UI with
  your company's own React components instead of the default shadcn-style UI.
- [**Multi-App Workspace**](/docs/multi-app-workspace) — make the template fit
  naturally alongside sibling apps and Dispatch.
- [**Syncing Template Changes**](/docs/syncing-template-changes) — pull later
  upstream template fixes into an app that was already created from it.
- [**Templates**](/docs/cloneable-saas) — turn a template into a
  publishable product you can share and deploy.
