Skip to main content
An entity is a table. You declare one with entity(name, fields, options).
import { entity, field } from "@pylonsync/sdk";

const User = entity(
  "User",
  {
    email: field.string().unique(),
    name: field.string(),
    createdAt: field.datetime(),
  },
  {
    indexes: [{ name: "by_email", fields: ["email"], unique: true }],
  },
);
Every entity gets an auto-generated id (ULID string) and the column order you declared.

Field types

MethodTypeNotes
field.string()TEXTAny UTF-8 string
field.int()INTEGER64-bit signed
field.float()REAL64-bit IEEE-754
field.boolean()INTEGER (0/1)
field.datetime()TEXT (ISO-8601)Store with new Date().toISOString()
field.richtext()TEXTFor prose; the client SDK has editors ready
field.id(entity)TEXTForeign key to another entity’s id

Modifiers

  • .optional() — column is nullable
  • .unique() — adds a unique index on that one column
field.string().optional()          // nullable
field.string().unique()            // UNIQUE constraint
field.id("User")                   // FK to User.id
field.int().optional()             // nullable int

Indexes

Declare composite or non-unique indexes in the options block:
entity(
  "Message",
  {
    roomId: field.id("Room"),
    authorId: field.id("User"),
    sentAt: field.datetime(),
    body: field.richtext(),
  },
  {
    indexes: [
      { name: "by_room_time", fields: ["roomId", "sentAt"], unique: false },
      { name: "by_author", fields: ["authorId"], unique: false },
    ],
  },
);
Indexes are created and maintained automatically. Live queries use them to stay fast under load.

Relationships

Pylon doesn’t have a separate relation primitive — use field.id("Other") and query with filters. The typed client db.query("Message", { roomId }) narrows by indexed columns.
// In a server function:
const msgs = await ctx.db.query("Message", { roomId: args.roomId });

// Or in a React client:
const { data: messages } = db.useQuery("Message", { roomId });

Schema changes

Edit app.ts, save — pylon dev picks up the change and runs a live migration. Pylon’s storage layer plans the diff (add column, drop index, etc.) and applies it to your database, whether SQLite or Postgres. Destructive operations (dropping a column that has data) require you to bump manifest.version.

Next

Policies

Control who can read and write each row.

Functions

Write server-side logic.