JavaScript Schema to Postgres (Supabase) Migration Generator
SQLSmith converts JavaScript object schemas into clean, safe, Supabase-ready Postgres SQL migration files with schema diffing, CLI tools, and type overrides.
- β¨ JS β SQL Schema Generation - Write schemas in JavaScript, get clean SQL
- π Schema Diffing - Generate ALTER statements from schema changes
- π¦ Supabase-Ready - Timestamped migrations in the Supabase format
- π― Shorthand Notation - Concise column definitions (
"uuid:pk","text:unique:notnull") - π RLS Policy Templates - Optional Supabase Row Level Security policy generation
- βοΈ Highly Configurable - Type overrides, naming strategies, timestamps, and more
- π§ͺ Well-Tested - Comprehensive test suite included
- π οΈ CLI + Programmatic API - Use from command line or Node.js code
npm install -D sqlsmith
# or
yarn add -D sqlsmithnpx sqlsmith initThis creates:
sqlsmith.config.js- Configuration fileschema.example.js- Example schema file
Edit schema.example.js:
export default {
users: {
id: "uuid:pk",
name: "text:notnull",
email: "text:unique:notnull",
age: "integer:default=18",
profile: {
bio: "text",
avatar_url: "text"
},
created_at: "timestamptz:default=now()"
},
profiles: {
id: "uuid:pk",
user_id: "uuid:fk=users.id",
visibility: "text:default='public'"
}
};# Generate SQL file
npx sqlsmith generate --schema=schema.example.js --out=schema.sql
# Or create a timestamped migration
npx sqlsmith migrate --schema=schema.example.js --message="init"Generated SQL:
CREATE TABLE IF NOT EXISTS public.users (
id uuid PRIMARY KEY,
name text NOT NULL,
email text UNIQUE NOT NULL,
age integer DEFAULT 18,
profile jsonb,
created_at timestamptz DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.profiles (
id uuid PRIMARY KEY,
user_id uuid REFERENCES public.users(id),
visibility text DEFAULT 'public'
);Create configuration and example schema files
npx sqlsmith initGenerate SQL from schema
npx sqlsmith generate --schema=schema.js --out=schema.sqlCreate timestamped migration file
npx sqlsmith migrate --schema=schema.js --message="create users table"Generate ALTER statements from schema differences
npx sqlsmith diff --from=old-schema.js --to=new-schema.js --out=diff.sqlGenerate Supabase RLS policy templates
npx sqlsmith policies --table=users --out=policies.sqlimport { generateSQL, diffSchemas, migrate } from "sqlsmith";
// Generate SQL from schema
const sql = generateSQL(schemaObject);
// Generate ALTER statements
const diff = diffSchemas(oldSchema, newSchema);
// Create migration file
const filePath = migrate(schemaObject, "./supabase/migrations", "init");Column definitions use the format: "<type>[:modifier1][:modifier2]..."
{
id: "uuid:pk", // Primary key
email: "text:unique:notnull", // Unique and NOT NULL
age: "integer:default=18", // Default value
created_at: "timestamptz:default=now()", // Function default
user_id: "uuid:fk=users.id", // Foreign key
settings: "jsonb:default='{}'" // JSON default
}pk- Primary key (implies NOT NULL)notnull- NOT NULL constraintunique- UNIQUE constraintdefault=<value>- Default valuefk=<table>.<column>- Foreign key reference
JavaScript types to PostgreSQL:
string β text
number β integer
boolean β boolean
uuid β uuid
object β jsonb
array β jsonbExplicit PostgreSQL types supported:
text,varchar,integer,bigint,smallintnumeric,decimal,real,double precisionuuid,boolean,date,timestamp,timestamptzjson,jsonb,text[],integer[], etc.
Nested objects are automatically converted to jsonb:
{
users: {
profile: {
bio: "text",
avatar_url: "text"
}
}
}Becomes:
profile jsonbsqlsmith.config.js:
export default {
schema: 'public', // PostgreSQL schema
namingStrategy: 'snake_case', // snake_case or camelCase
timestamps: true, // Add created_at/updated_at
jsonForObjects: true, // Convert nested objects to jsonb
foreignKeyStrategy: 'inline', // inline or separateTable
policyGeneration: false, // Generate RLS policies
supabaseDir: './supabase/migrations',
typeOverrides: {
// Custom type mappings
id: 'uuid',
email: 'varchar(255)'
}
};| Option | Type | Default | Description |
|---|---|---|---|
schema |
string | 'public' |
PostgreSQL schema name |
namingStrategy |
string | 'snake_case' |
Naming convention |
timestamps |
boolean/object | false |
Auto-add timestamps |
jsonForObjects |
boolean | true |
Convert objects to jsonb |
foreignKeyStrategy |
string | 'inline' |
FK strategy |
policyGeneration |
boolean | false |
Generate RLS policies |
supabaseDir |
string | './supabase/migrations' |
Migration directory |
typeOverrides |
object | {} |
Custom type mappings |
SQLSmith can generate ALTER statements by comparing two schemas:
// old-schema.js
export default {
users: {
id: "uuid:pk",
name: "text"
}
};
// new-schema.js
export default {
users: {
id: "uuid:pk",
name: "text",
email: "text:unique:notnull" // Added column
}
};npx sqlsmith diff --from=old-schema.js --to=new-schema.jsOutput:
ALTER TABLE public.users ADD COLUMN email text UNIQUE NOT NULL;SQLSmith warns about destructive changes:
-- WARNING: DROP_COLUMN on users.name is DESTRUCTIVE!
-- ALTER TABLE public.users DROP COLUMN name;SQLSmith generates timestamped migration files compatible with Supabase:
supabase/migrations/
20251121143022_create_users.sql
20251121143045_add_profiles.sql
Generate Row Level Security policy templates:
npx sqlsmith policies --table=usersOutput:
-- Enable Row Level Security
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
-- Policy: Authenticated users can view all records
CREATE POLICY "authenticated_select_policy" ON public.users
FOR SELECT
TO authenticated
USING (true);
-- Policy: Users can update their own records
CREATE POLICY "user_update_own_policy" ON public.users
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);Run the test suite:
npm testTests include:
- Parser validation
- SQL generation
- Schema diffing
- Type mapping
- Configuration handling
sqlsmith/
βββ bin/
β βββ sqlsmith.js # CLI entry point
βββ src/
β βββ index.js # Main API
β βββ cli.js # CLI commands
β βββ parser/
β β βββ parseSchema.js # Schema parser
β β βββ validateSchema.js # Schema validator
β βββ generator/
β β βββ sqlCreate.js # CREATE TABLE generator
β β βββ sqlAlter.js # ALTER TABLE generator
β β βββ typeMapper.js # Type mapping
β βββ integrator/
β β βββ supabaseWriter.js # Migration file writer
β β βββ policyGenerator.js# RLS policy generator
β βββ utils/
β βββ fileWriter.js # File utilities
β βββ logger.js # Logger
β βββ timestamp.js # Timestamp generator
βββ templates/
β βββ migration.sql.tpl # Migration template
β βββ policy.template.sql # Policy template
βββ examples/
β βββ schema.example.js # Example schema
βββ tests/
β βββ parser.test.js # Parser tests
β βββ generator.test.js # Generator tests
βββ package.json
Contributions are welcome! Please feel free to submit a Pull Request.
MIT Β© Your Name
export default {
posts: {
id: "uuid:pk",
title: "text:notnull",
content: "text",
author_id: "uuid:fk=users.id",
published: "boolean:default=false",
created_at: "timestamptz:default=now()"
}
};// sqlsmith.config.js
export default {
timestamps: {
created_at: true,
updated_at: true
}
};
// schema.js
export default {
articles: {
id: "uuid:pk",
title: "text:notnull"
// created_at and updated_at added automatically
}
};export default {
organizations: {
id: "uuid:pk",
name: "text:notnull",
settings: "jsonb:default='{}'",
created_at: "timestamptz:default=now()"
},
users: {
id: "uuid:pk",
email: "text:unique:notnull",
org_id: "uuid:fk=organizations.id",
role: "text:default='member'",
metadata: {
bio: "text",
avatar: "text",
preferences: {}
}
},
posts: {
id: "uuid:pk",
author_id: "uuid:fk=users.id",
org_id: "uuid:fk=organizations.id",
title: "text:notnull",
content: "text",
tags: "text[]",
published_at: "timestamptz"
}
};Built with β€οΈ for the Supabase and PostgreSQL community
Contributions are welcome! Please feel free to submit a Pull Request.
npm installnpm installContributions are welcome! Please feel free to submit a Pull Request.
See the documentation for more details.
npm installnpm install