Skip to content

Models & Schema Definition

Pulsabase offers a two-way schema sync approach to schema management. Define your schema in TypeScript using decorators, or build it visually in the Dashboard and pull it as typed models.

The CLI provides complete bidirectional sync:

  1. Code → Database (db:sync): You define models in TypeScript, and Pulsabase generates and applies SQL migrations to update your remote database.
  2. Database → Code (db:pull): You create tables in the visual Dashboard, and Pulsabase generates the perfectly typed TypeScript models locally in your project.

A model is a TypeScript class with decorated properties (columns) and a static schema block for table-level configuration.

import { column, primary, text, unique, boolean, integer, timestamp, ModelSchema, rules, crossRules } from 'pulsabase';
export default class User {
@primary // UUID primary key (auto-generated)
id: string;
@text(false) // Non-nullable TEXT column
name: string;
@text(false)
@unique() // Adds a UNIQUE constraint
email: string;
@integer()
age?: number;
@boolean()
is_active?: boolean;
// The `schema` block defines table-level features.
// Only the TypeScript compiler needs this — it's not sent to the DB directly.
static schema: ModelSchema = {
tableName: 'users',
publication: true, // Enable real-time subscriptions for this table
timestamps: true, // Auto-manage `created_at` / `updated_at`
indexes: [
{ columns: ['email'], unique: true },
{ columns: ['age'] },
],
validation: {
email: [rules.required(), rules.email('Must be a valid email')],
name: [rules.required(), rules.min(3, 'Name is too short')],
age: [rules.positive('Age must be positive')],
},
crossValidation: [
crossRules.dateAfter('ends_at', 'starts_at', 'End must be after start'),
],
policies: [
{ action: ['SELECT', 'UPDATE'], authenticated: true, owner: 'id' },
{ action: ['INSERT'], authenticated: true },
],
};
}
DecoratorSQL TypeNotes
@primaryUUID DEFAULT gen_random_uuid()Primary key. Auto-generated.
@text(nullable?)TEXTDefault: nullable. falseNOT NULL
@integer()INTEGER
@bigInteger()BIGINTFor large numbers
@float()DOUBLE PRECISION
@boolean()BOOLEAN
@timestamp()TIMESTAMPTZTimezone-aware timestamp
@json()JSONBQueryable JSON (PostgreSQL JSONB)
@uuid()UUIDFor FK references
@column(type)customRaw SQL type override
@unique()Adds a UNIQUE constraint
@default(value)Sets a SQL DEFAULT expression
PropertyTypeDescription
tableNamestringRequired. The PostgreSQL table name.
timestampsbooleanAuto-add and manage created_at / updated_at columns.
publicationbooleanRegister the table for real-time change events.
indexesarrayDefine custom indexes (see below).
validationobjectServer-side validation rules per field.
crossValidationarrayCross-field validation rules.
policiesarrayRLS policies (see Row-Level Security).

Validation runs server-side before data reaches the database. It provides structured error responses that are ideal for displaying form errors.

import { rules, crossRules } from 'pulsabase';
static schema: ModelSchema = {
tableName: 'events',
validation: {
title: [rules.required(), rules.min(5, 'Title must be at least 5 chars')],
email: [rules.email('Invalid email format')],
ticket_price: [rules.positive('Price must be > 0')],
phone: [rules.regex(/^\+?[0-9]{10,14}$/, 'Invalid phone number')],
},
crossValidation: [
crossRules.dateAfter('end_date', 'start_date', 'End date must be after start date'),
],
};
static schema: ModelSchema = {
tableName: 'documents',
indexes: [
{ columns: ['user_id'] }, // Standard BTREE
{ columns: ['email', 'project_id'], unique: true }, // Composite unique
{ columns: ['created_at'] }, // Timestamp index
{ columns: ['embedding'], type: 'HNSW', opclass: 'vector_l2_ops' } // pgvector
],
};
Terminal window
# Preview what changes will be generated (no modifications applied)
pulsabase db:sync --dry-run
# Apply the schema to the database
pulsabase db:sync
# Pull the database schema as local model files
pulsabase db:pull

See CLI Reference for all available options.