Skip to main content
This walkthrough gets a Pylon server running with a schema, one policy, and one server function. You’ll then hit the API with curl to see it work end-to-end. Once you want a realtime UI on top, point the React SDK, Swift SDK, or React Native SDK at the running server, or clone one of the example apps.
Just want a working app? Skip the manual setup with one command:
npm create @pylonsync/pylon@latest my-app -- --template todo --platforms web
cd my-app && bun install && bun run dev
That scaffolds a Pylon backend + Next.js 16 + React 19 frontend + shared UI package wired together. Templates: barebones, todo, b2b, consumer, chat. Platforms: web, ios, mac, expo.Ready to ship to production? See Deploying to Vercel.

1. Install the CLI

curl -fsSL https://pylonsync.com/install.sh | bash
Downloads a prebuilt pylon binary to ~/.local/bin. Linux and macOS, x86_64 and arm64. No Rust toolchain required. Bun ≥ 1.0 is needed at runtime to execute server functions. Verify:
pylon --version
Other ways to install:
# Homebrew (macOS + Linux)
brew install pylonsync/tap/pylon

# Cargo (compiles from source)
cargo install pylon-cli

# Docker
docker pull ghcr.io/pylonsync/pylon:latest

2. Scaffold a new app

pylon init notes --frontend=none
cd notes
This scaffolds a Bun workspace with the API at apps/api/:
notes/
  apps/
    api/
      app.ts             # schema + manifest entry point
      sdk.ts             # local re-export of @pylonsync/sdk
      tsconfig.json
      package.json
  package.json           # workspace root
  .gitignore
  README.md
--frontend=none skips the optional apps/web/ scaffold. Drop the flag to also generate a Next.js, React, or TanStack frontend (or pick interactively when stdin is a TTY). You’ll add a functions/ directory under apps/api/ by hand in step 4.

3. Define the schema

Replace apps/api/app.ts with:
import { entity, field, policy, buildManifest } from "@pylonsync/sdk";

const Note = entity(
  "Note",
  {
    title: field.string(),
    body: field.richtext(),
    authorId: field.string(),
    updatedAt: field.datetime(),
  },
  {
    indexes: [{ name: "by_author", fields: ["authorId"], unique: false }],
  },
);

const notePolicy = policy({
  name: "note_public",
  entity: "Note",
  allowRead: "true",
  allowInsert: "auth.userId == data.authorId",
  allowUpdate: "auth.userId == data.authorId",
  allowDelete: "auth.userId == data.authorId",
});

const manifest = buildManifest({
  name: "notes",
  version: "0.1.0",
  entities: [Note],
  policies: [notePolicy],
  queries: [],
  actions: [],
  routes: [],
});

console.log(JSON.stringify(manifest, null, 2));
The trailing console.log is required — pylon dev captures stdout as the manifest.

4. Add a server function

Create apps/api/functions/createNote.ts:
import { mutation, v } from "@pylonsync/functions";

export default mutation({
  // `auth: "user"` is the default — the framework rejects
  // anonymous callers with 401 before the handler runs. So
  // `ctx.auth.userId` is `string`, not nullable, here.
  args: {
    title: v.string(),
    body: v.string(),
  },
  async handler(ctx, args) {
    const id = await ctx.db.insert("Note", {
      title: args.title,
      body: args.body,
      authorId: ctx.auth.userId,
      updatedAt: new Date().toISOString(),
    });
    return { id };
  },
});
The filename becomes the RPC name — this will be callable at POST /api/fn/createNote. pylon init already adds @pylonsync/sdk + @pylonsync/functions to apps/api/package.json. If you skipped init or stripped the deps, install them manually:
cd apps/api && bun add @pylonsync/sdk @pylonsync/functions

5. Run the server

cd apps/api
pylon dev
pylon dev auto-discovers app.ts (and schema.ts for legacy projects), watches app.ts and functions/*.ts, auto-migrates the database (SQLite at .pylon/dev.db by default, Postgres if DATABASE_URL is set), and serves the API on http://localhost:4321. Expected output:
✓ notes v0.1.0 — 1 entities, 0 queries, 0 actions, 1 policies, 0 routes
  Server:   http://localhost:4321
  Database: .pylon/dev.db

6. Hit the API

Create a guest session:
curl -X POST http://localhost:4321/api/auth/guest
# → { "token": "eyJ…", "user_id": "usr_01H…" }
Call the mutation (replace TOKEN + USER_ID with the values from above):
curl -X POST http://localhost:4321/api/fn/createNote \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"First note","body":"hello from Pylon"}'
# → { "id": "not_01H…" }
List the rows:
curl http://localhost:4321/api/entities/Note
# → [{ "id": "not_01H…", "title": "First note", ... }]
You now have a working Pylon backend with a typed schema, row-level policy, RPC function, and HTTP API.

Next steps

Entities

All the field types and index options.

Policies

How row-level access rules work.

Functions

Queries, mutations, actions, validators.

Live queries

How db.useQuery stays in sync.
Want a full UI on top? For all three, the same Pylon server backs the UI — wire format is identical across platforms.

Hand the rest to a coding agent

Once your backend is running, the rest is shell. Pylon Cloud has a one-paste handoff flow that signs your coding agent (Claude Code, Codex, OpenCode, Cursor, Aider, grok build) into your account, installs the Pylon skill, and asks what to build — no token in chat history. From there the agent can use the full CLI surface: pylon secrets, pylon logs tail, pylon deploy, pylon domains add, pylon db backup, pylon data list <Entity>, etc.