Creating Templates
How to build and publish your own agent-native app template.
Overview
Templates are complete, forkable agent-native apps that solve a specific use case. The analytics, content, slides, and video templates that ship with Agent-Native are all built this way. Anyone can create a template and share it with the community.
A good template:
- Solves a real workflow end-to-end (not a toy demo)
- Works out of the box with example data
- Has a comprehensive
AGENTS.mdso the AI agent understands the architecture - Includes scripts for key operations the agent can call
- Follows the five rules: files as database, all AI through agent chat, scripts for operations, SSE sync, agent can modify code
Start from the starter
The fastest way to start is with the built-in starter template:
npx @agent-native/core create my-templateThis scaffolds a minimal agent-native app with the standard directory structure, a working dev server, file watching, SSE, and an example script. Build your template on top of this.
Project structure
Every template follows the same convention:
my-template/
client/ # React frontend (Vite SPA)
App.tsx # Entry point — routes, providers, file watcher
pages/ # Route components
components/ # UI components
components/ui/ # Reusable primitives (shadcn/ui)
hooks/ # React hooks
lib/utils.ts # cn() utility
server/ # Express backend
index.ts # createAppServer() — routes + middleware
node-build.ts # Production entry point
routes/ # API route handlers
shared/ # Isomorphic types (imported by client & server)
api.ts # Shared interfaces
scripts/ # Agent-callable scripts
run.ts # Script dispatcher (don't modify)
*.ts # Your scripts — one per operation
data/ # File-based state (watched by SSE)
.gitkeep # Or seed data for the template
.agents/skills/ # Agent skills — detailed guidance per topic
AGENTS.md # Master agent instructions
package.json # Scripts: dev, build, start, script, typecheck
vite.config.ts # Client Vite config
vite.config.server.ts # Server Vite config
tsconfig.json # TypeScript configBuild your client
The client is a standard React SPA. Use React Router for navigation, React Query for data fetching, and TailwindCSS + shadcn/ui for styling.
// client/App.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useFileWatcher } from "@agent-native/core";
import { BrowserRouter, Routes, Route } from "react-router-dom";
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<FileWatcher />
<BrowserRouter>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</BrowserRouter>
</QueryClientProvider>
);
}
function FileWatcher() {
useFileWatcher({ queryClient, queryKeys: ["items", "projects"] });
return null;
}The useFileWatcher hook connects to /api/events and invalidates react-query caches when files change. This is how the UI stays in sync when the agent modifies data.
Add API routes
API routes serve data from files and handle mutations. They go in server/index.tsor a server/routes/ directory for larger apps:
// server/index.ts
import { createServer, createFileWatcher, createSSEHandler } from "@agent-native/core";
import { readdir, readFile, writeFile, mkdir } from "node:fs/promises";
import path from "node:path";
export function createAppServer() {
const app = createServer();
const watcher = createFileWatcher("./data");
// List items from files
app.get("/api/items", async (_req, res) => {
const dir = "./data/items";
await mkdir(dir, { recursive: true });
const files = await readdir(dir);
const items = await Promise.all(
files.filter(f => f.endsWith(".json")).map(async f => {
const content = await readFile(path.join(dir, f), "utf-8");
return JSON.parse(content);
})
);
res.json(items);
});
// Create an item (write a file)
app.post("/api/items", async (req, res) => {
const item = { id: crypto.randomUUID(), ...req.body, createdAt: new Date().toISOString() };
await mkdir("./data/items", { recursive: true });
await writeFile(`./data/items/${item.id}.json`, JSON.stringify(item, null, 2));
res.json(item);
});
// SSE events (keep last)
app.get("/api/events", createSSEHandler(watcher));
return app;
}Both the UI and the agent can create items — the UI via POST /api/items, the agent by writing directly to data/items/. The SSE watcher ensures both paths trigger UI updates.
Add scripts
Scripts are the agent's toolbox. Each script handles one operation — fetching data from an API, generating content, processing files, etc:
// scripts/import-data.ts
import { parseArgs } from "@agent-native/core";
import { writeFile, mkdir } from "node:fs/promises";
export default async function importData(args: string[]) {
const { url, name } = parseArgs(args);
if (!url) { console.error("--url is required"); process.exit(1); }
const res = await fetch(url);
const data = await res.json();
const slug = name ?? "imported";
await mkdir("./data/imports", { recursive: true });
await writeFile(`./data/imports/${slug}.json`, JSON.stringify(data, null, 2));
console.log(`Imported ${Array.isArray(data) ? data.length + " records" : "data"} to data/imports/${slug}.json`);
}# The agent can run this
pnpm script import-data --url https://api.example.com/data --name usersScripts should write their output to data/ — the SSE watcher will notify the UI. Use console.log for output the agent can see. Use console.error and process.exit(1) for errors.
Add data models
Seed your template with example data so it works immediately. Put JSON files in data/ matching the structure your API routes expect:
data/
items/
example-1.json # {"id": "example-1", "title": "...", "status": "active"}
example-2.json
config.json # App-level config
sync-config.json # (optional) Firestore sync glob patternsKeep your data models simple — flat JSON files, one per entity. The agent can grep, read, and modify them. Deeply nested structures or binary formats make it harder for the agent to work with the data.
Write AGENTS.md
This is the most important file in your template. AGENTS.md tells the AI agent how your app works, what it can and can't do, and how to make changes:
# My Template — Agent-Native App
## Architecture
This is an **@agent-native/core** application.
### Core Principles
1. **Files as database** — All state in `data/`. No traditional DB.
2. **All AI through agent chat** — No inline LLM calls.
3. **Scripts for operations** — `pnpm script <name>` for complex work.
4. **SSE sync** — File watcher keeps UI in sync.
5. **Agent can update code** — Edit components, routes, scripts.
### Directory Structure
\`\`\`
client/ # React SPA
server/ # Express API
scripts/ # Agent-callable scripts
data/ # File-based state
\`\`\`
### Available Scripts
- `pnpm script import-data --url <url>` — Import data from API
- `pnpm script generate-report --id <id>` — Generate a report
### Data Model
Items are stored as `data/items/<id>.json`:
\`\`\`json
{ "id": "...", "title": "...", "status": "active" }
\`\`\`
### Key Patterns
- API routes in `server/routes/` serve files from `data/`
- UI delegates AI work via `sendToAgentChat()`
- Scripts write results to `data/` — SSE updates the UIBe specific about your data models, available scripts, and key patterns. The better your AGENTS.md, the better the agent will work with your template.
Add skills
For complex topics that don't fit in AGENTS.md, create skills in .agents/skills/. Each skill is a Markdown file with detailed guidance for a specific topic:
# .agents/skills/bigquery/SKILL.md
## BigQuery Integration
### Column Reference
- `event_name` — The event type (string)
- `event_timestamp` — Microsecond timestamp (int64)
- `user_pseudo_id` — Anonymous user ID (string)
### Common Queries
...
### Gotchas
- Always use `event_date` partition filter to avoid full table scans
- Timestamps are in microseconds, not millisecondsSkills let you give the agent deep domain knowledge for specific integrations or patterns without bloating your main AGENTS.md.
Onboarding & API keys
If your template needs API keys or external service configuration, document them in a .env.example file:
# .env.example
BIGQUERY_PROJECT_ID=your-project-id
STRIPE_SECRET_KEY=sk_live_...
OPENAI_API_KEY=sk-...When users fork your template, they copy .env.example to .envand fill in their own values. Keep the number of required keys minimal — the template should work with example data before any keys are configured.
Publishing
To share your template:
- Push your template to a public GitHub repo
- Make sure it works with
pnpm install && pnpm dev - Include seed data in
data/so it works without API keys - Write a clear README explaining what the template does and how to configure it
Community templates can be shared via GitHub. The agent-native CLI supports creating from any git repo:
npx @agent-native/core create my-app --template github:user/repo