🪄 Supabase migrations made magical: Live-reloading SQL + Sane, reviewable diffs + Maintainable templates = Delightful DX
105
stars
105
commits
TypeScript
primary language
Sep 5, 2026
updated
srtd — Live-Reloading, declarative, SQL migration templates for Supabase and PostgresThe workflow of Vite or Nodemon—but for SQL. Hot-reload SQL while you iterate. Build as migrations when it's time to ship.

Database development usually has a slow, frustrating feedback loop:
Code reviews aren't much better. Changing a single line in a function often shows up as a full rewrite in Git, making it hard to see what actually changed.
srtd exists to fix both problems:
After searching for two years while building Timely's Memory Engine on Supabase, the itch needed to be scratched.
srtd intentionally separates iteration, review, and execution.
Templates evolve over time. Migrations are the execution record.
Without templates, changing one line in a function means your PR shows a complete rewrite—the old DROP + CREATE replaced by a new one. Reviewers have to read the whole thing, and manually compare it to the last migration to touch the function to spot changes. Talk about friction!
With srtd, your PR shows what you actually changed;
|
Without srtd
|
With srtd
|
git blame works. Code reviews are useful. Your database logic is treated like real code.
npm install -g @t1mmen/srtd
cd your-supabase-project
# Create a template
mkdir -p supabase/migrations-templates
cat > supabase/migrations-templates/hello.sql << 'EOF'
DROP FUNCTION IF EXISTS hello;
CREATE FUNCTION hello() RETURNS text AS $$
BEGIN RETURN 'Hello from srtd!'; END;
$$ LANGUAGE plpgsql;
EOF
# Start watch mode
srtd watch
Edit hello.sql, save—it's live on your local database. No migration file, no restart, no waiting.
When ready to deploy:
srtd build # Creates supabase/migrations/20241226_srtd-hello.sql
supabase migration up # Deploy with Supabase CLI
Already have functions deployed? Create templates for them, then register so srtd knows they're not new:
srtd register existing_function.sql # Won't rebuild until the template actually changes
srtd watch # Edit template → auto-applied to local DB
Hot reload is a DX feature. It exists to make iteration smooth and fast.

srtd build # → migrations/20250109123456_srtd-template-name.sql
supabase migration up # Deploy via Supabase CLI
Generated migrations fully redefine objects—databases prefer explicit redefinition, humans prefer small diffs. srtd optimizes for both in different layers.
Use .wip.sql files for experiments:
my_experiment.wip.sql → Applies locally, never builds to migration
watch and applybuildWhen ready: srtd promote my_experiment.wip.sql

Declare dependencies between templates with @depends-on comments:
-- @depends-on: helper_functions.sql
CREATE FUNCTION complex_calc() ...
During apply and build, templates are sorted so dependencies run first. Circular dependencies are detected and reported. Use --no-deps to disable.
Templates need to be idempotent—safe to run multiple times. This works great for:
| Object | Pattern |
|---|---|
| Functions | DROP FUNCTION IF EXISTS + CREATE FUNCTION |
| Views | CREATE OR REPLACE VIEW |
| RLS Policies | DROP POLICY IF EXISTS + CREATE POLICY |
| Triggers | Drop + recreate trigger and function |
| Roles | REVOKE ALL + GRANT |
| Enums | ADD VALUE IF NOT EXISTS |
Not for templates: Table structures, indexes, data modifications—use regular migrations for those.
srtd # Interactive menu
srtd watch # Live reload—applies templates on save
srtd build # Generate migration files
srtd apply # Apply all templates once (no watch)
srtd register # Mark templates as already deployed
srtd promote # Convert .wip template to buildable
srtd clear # Reset build state
srtd init # Initialize config file
srtd doctor # Validate setup and diagnose issues
# Build options
srtd build --force # Rebuild all templates
srtd build --apply # Apply immediately after building
srtd build --bundle # Combine all into single migration
srtd build --no-deps # Disable dependency ordering
# Apply options
srtd apply --force # Force apply all templates
srtd apply --no-deps # Disable dependency ordering
# Clear options
srtd clear --local # Clear local build log only
srtd clear --shared # Clear shared build log only
srtd clear --reset # Reset everything to defaults
# Global options (all commands)
--json # Machine-readable output
--non-interactive # Disable prompts (for scripts)
All commands support --json for machine-readable output (CI/CD, LLM integrations):
srtd build --json # Single JSON object with results array and summary
srtd watch --json # NDJSON stream (one event per line)
Output includes success, command, timestamp, and command-specific fields. Errors use a top-level error field.
Works great with Claude Code background tasks—pair with the srtd-cli skill and srtd rule for SQL template guidance.
Defaults work for standard Supabase projects. Optional srtd.config.json:
{
"templateDir": "supabase/migrations-templates",
"migrationDir": "supabase/migrations",
"pgConnection": "postgresql://postgres:postgres@localhost:54322/postgres",
"migrationPrefix": "srtd",
"migrationFilename": "$timestamp_$prefix$migrationName.sql",
"wipIndicator": ".wip",
"wrapInTransaction": true,
"filter": "**/*.sql",
"banner": "/* Auto-generated by srtd. Edit the template, not this file. */",
"footer": ""
}
Run srtd doctor to validate your configuration and diagnose setup issues.
The migrationFilename option lets you match your project's existing migration structure:
| Variable | Description | Example |
|---|---|---|
$timestamp | Build timestamp (YYYYMMDDHHmmss) | 20240315143022 |
$migrationName | Template name (without .sql) | create_users |
$prefix | Migration prefix with trailing dash | srtd- |
Default (Supabase-style):
{ "migrationFilename": "$timestamp_$prefix$migrationName.sql" }
// → migrations/20240315143022_srtd-create_users.sql
Directory per migration (Prisma-style):
{ "migrationFilename": "$timestamp_$migrationName/migration.sql" }
// → migrations/20240315143022_create_users/migration.sql
Flyway-style (V prefix):
{
"migrationFilename": "V$timestamp__$migrationName.sql",
"migrationPrefix": ""
}
// → migrations/V20240315143022__create_users.sql
Nested directories are created automatically.
| File | Purpose | Git |
|---|---|---|
.buildlog.json | What's been built to migrations | Commit |
.buildlog.local.json | What's applied to your local DB | Gitignore |
Full redefinitions ensure deterministic, idempotent execution across environments. Templates encode intent; migrations encode execution.
No. Hot reload is strictly a local development feature. Production deploys always use generated migrations.
SQL is already the right language. srtd adds workflow improvements, not syntax.
Supabase Repeatable Template Definitions—but then general Postgres support happened, and... naming things is hard.
Bug fixes, docs, and test coverage welcome. See CONTRIBUTING.md.
For development: CLAUDE.md.
Built by Timm Stokke with Claude, after two years of being annoyed.
TypeScript
98.3%
🪄 Supabase migrations made magical: Live-reloading SQL + Sane, reviewable diffs + Maintainable templates = Delightful DX
105
stars
105
commits
TypeScript
primary language
Sep 5, 2026
updated
srtd — Live-Reloading, declarative, SQL migration templates for Supabase and PostgresThe workflow of Vite or Nodemon—but for SQL. Hot-reload SQL while you iterate. Build as migrations when it's time to ship.

Database development usually has a slow, frustrating feedback loop:
Code reviews aren't much better. Changing a single line in a function often shows up as a full rewrite in Git, making it hard to see what actually changed.
srtd exists to fix both problems:
After searching for two years while building Timely's Memory Engine on Supabase, the itch needed to be scratched.
srtd intentionally separates iteration, review, and execution.
Templates evolve over time. Migrations are the execution record.
Without templates, changing one line in a function means your PR shows a complete rewrite—the old DROP + CREATE replaced by a new one. Reviewers have to read the whole thing, and manually compare it to the last migration to touch the function to spot changes. Talk about friction!
With srtd, your PR shows what you actually changed;
|
Without srtd
|
With srtd
|
git blame works. Code reviews are useful. Your database logic is treated like real code.
npm install -g @t1mmen/srtd
cd your-supabase-project
# Create a template
mkdir -p supabase/migrations-templates
cat > supabase/migrations-templates/hello.sql << 'EOF'
DROP FUNCTION IF EXISTS hello;
CREATE FUNCTION hello() RETURNS text AS $$
BEGIN RETURN 'Hello from srtd!'; END;
$$ LANGUAGE plpgsql;
EOF
# Start watch mode
srtd watch
Edit hello.sql, save—it's live on your local database. No migration file, no restart, no waiting.
When ready to deploy:
srtd build # Creates supabase/migrations/20241226_srtd-hello.sql
supabase migration up # Deploy with Supabase CLI
Already have functions deployed? Create templates for them, then register so srtd knows they're not new:
srtd register existing_function.sql # Won't rebuild until the template actually changes
srtd watch # Edit template → auto-applied to local DB
Hot reload is a DX feature. It exists to make iteration smooth and fast.

srtd build # → migrations/20250109123456_srtd-template-name.sql
supabase migration up # Deploy via Supabase CLI
Generated migrations fully redefine objects—databases prefer explicit redefinition, humans prefer small diffs. srtd optimizes for both in different layers.
Use .wip.sql files for experiments:
my_experiment.wip.sql → Applies locally, never builds to migration
watch and applybuildWhen ready: srtd promote my_experiment.wip.sql

Declare dependencies between templates with @depends-on comments:
-- @depends-on: helper_functions.sql
CREATE FUNCTION complex_calc() ...
During apply and build, templates are sorted so dependencies run first. Circular dependencies are detected and reported. Use --no-deps to disable.
Templates need to be idempotent—safe to run multiple times. This works great for:
| Object | Pattern |
|---|---|
| Functions | DROP FUNCTION IF EXISTS + CREATE FUNCTION |
| Views | CREATE OR REPLACE VIEW |
| RLS Policies | DROP POLICY IF EXISTS + CREATE POLICY |
| Triggers | Drop + recreate trigger and function |
| Roles | REVOKE ALL + GRANT |
| Enums | ADD VALUE IF NOT EXISTS |
Not for templates: Table structures, indexes, data modifications—use regular migrations for those.
srtd # Interactive menu
srtd watch # Live reload—applies templates on save
srtd build # Generate migration files
srtd apply # Apply all templates once (no watch)
srtd register # Mark templates as already deployed
srtd promote # Convert .wip template to buildable
srtd clear # Reset build state
srtd init # Initialize config file
srtd doctor # Validate setup and diagnose issues
# Build options
srtd build --force # Rebuild all templates
srtd build --apply # Apply immediately after building
srtd build --bundle # Combine all into single migration
srtd build --no-deps # Disable dependency ordering
# Apply options
srtd apply --force # Force apply all templates
srtd apply --no-deps # Disable dependency ordering
# Clear options
srtd clear --local # Clear local build log only
srtd clear --shared # Clear shared build log only
srtd clear --reset # Reset everything to defaults
# Global options (all commands)
--json # Machine-readable output
--non-interactive # Disable prompts (for scripts)
All commands support --json for machine-readable output (CI/CD, LLM integrations):
srtd build --json # Single JSON object with results array and summary
srtd watch --json # NDJSON stream (one event per line)
Output includes success, command, timestamp, and command-specific fields. Errors use a top-level error field.
Works great with Claude Code background tasks—pair with the srtd-cli skill and srtd rule for SQL template guidance.
Defaults work for standard Supabase projects. Optional srtd.config.json:
{
"templateDir": "supabase/migrations-templates",
"migrationDir": "supabase/migrations",
"pgConnection": "postgresql://postgres:postgres@localhost:54322/postgres",
"migrationPrefix": "srtd",
"migrationFilename": "$timestamp_$prefix$migrationName.sql",
"wipIndicator": ".wip",
"wrapInTransaction": true,
"filter": "**/*.sql",
"banner": "/* Auto-generated by srtd. Edit the template, not this file. */",
"footer": ""
}
Run srtd doctor to validate your configuration and diagnose setup issues.
The migrationFilename option lets you match your project's existing migration structure:
| Variable | Description | Example |
|---|---|---|
$timestamp | Build timestamp (YYYYMMDDHHmmss) | 20240315143022 |
$migrationName | Template name (without .sql) | create_users |
$prefix | Migration prefix with trailing dash | srtd- |
Default (Supabase-style):
{ "migrationFilename": "$timestamp_$prefix$migrationName.sql" }
// → migrations/20240315143022_srtd-create_users.sql
Directory per migration (Prisma-style):
{ "migrationFilename": "$timestamp_$migrationName/migration.sql" }
// → migrations/20240315143022_create_users/migration.sql
Flyway-style (V prefix):
{
"migrationFilename": "V$timestamp__$migrationName.sql",
"migrationPrefix": ""
}
// → migrations/V20240315143022__create_users.sql
Nested directories are created automatically.
| File | Purpose | Git |
|---|---|---|
.buildlog.json | What's been built to migrations | Commit |
.buildlog.local.json | What's applied to your local DB | Gitignore |
Full redefinitions ensure deterministic, idempotent execution across environments. Templates encode intent; migrations encode execution.
No. Hot reload is strictly a local development feature. Production deploys always use generated migrations.
SQL is already the right language. srtd adds workflow improvements, not syntax.
Supabase Repeatable Template Definitions—but then general Postgres support happened, and... naming things is hard.
Bug fixes, docs, and test coverage welcome. See CONTRIBUTING.md.
For development: CLAUDE.md.
Built by Timm Stokke with Claude, after two years of being annoyed.
TypeScript
98.3%