Diff and sync your Supabase environments.
Supabase projects running in multiple environments (dev, staging, production) silently diverge with no first-class tooling to detect or fix it.
CVE-2025-48757 found 170+ apps with fully exposed databases due to RLS policies that were never promoted to production. SupaForge catches this on the first scan.
Built by Akal Forge β precision developer tools, forged to last.
npm install -g @akalforge/supaforge
# Create config interactively
supaforge init
# Check for drift
supaforge diff
# Show detailed SQL diffs
supaforge diff --detail
# Fix the drift
supaforge diff --apply
# Alias for diff
supaforge hukamOnly have one Supabase project? SupaForge works as a snapshot, backup, and audit tool for a single remote database β no second environment needed.
npm install -g @akalforge/supaforge
# Interactive setup β choose "single" mode
supaforge init
# Or create config manually
cat > supaforge.config.json << 'EOF'
{
"environments": {
"prod": {
"dbUrl": "$PROD_DATABASE_URL",
"projectRef": "https://your-project.supabase.co",
"accessToken": "$SUPABASE_ACCESS_TOKEN"
}
}
}
EOF
# Capture a full snapshot (schema, RLS, cron, storage, auth, etc.)
supaforge snapshot --env=prod
# Clone remote to local for development
supaforge clone --env=prod --apply
# Incremental backup (snapshot + migration file)
supaforge snapshot --env=prod --migrationSingle-database configs omit
sourceandtarget. Thediffcommand requires two environments β usesnapshot,clone, andrestoreinstead.
| Check | Source | Status |
|---|---|---|
| Schema | @dbdiff/cli |
β Ready |
| Data | @dbdiff/cli --type=data |
β Ready |
| RLS Policies | pg_policies view |
β Ready |
| Edge Functions | Management API | β Ready |
| Storage | Storage API | β Ready |
| Auth Config | Management API | β Ready |
| Cron Jobs | cron.job table |
β Ready |
| Webhooks | supabase_functions.hooks + pg_net |
β Ready |
| Realtime Publications | pg_publication + pg_publication_tables |
β Ready |
| Vault Secrets | vault.secrets |
β Ready |
| Postgres Extensions | pg_extension |
β Ready |
How SupaForge maps to every standard Supabase module (see Supabase Features):
| Supabase Module | Feature | SupaForge Check | Notes |
|---|---|---|---|
| Database | Postgres schema | β Schema | Tables, columns, indexes, constraints, views, triggers, functions, sequences |
| Reference / seed data | β Data | Row-level diff for all public tables (configurable) | |
| Database webhooks | β Webhooks | supabase_functions.hooks + pg_net extension |
|
| Postgres extensions | β Extensions | Enabled/disabled detection via pg_extension |
|
| Vault / Secrets | β Vault | Secret name/description drift; values are environment-specific | |
| Postgres roles | π Planned | Custom roles and grants | |
| Realtime publications | β Realtime | Which tables are published for Realtime | |
| PostgREST config | β¬ Not planned | Managed by Supabase platform; not user-configurable per environment | |
| Replication | β¬ Not planned | Private alpha; not accessible via standard APIs | |
| Auth | Auth config | β Auth | 20+ settings via Management API (providers, JWT, MFA, CAPTCHA) |
| RLS policies | β RLS | Full policy diffing with UP/DOWN SQL generation | |
| Storage | Buckets | β Storage | Bucket metadata (name, public/private, size limits, MIME types) |
| Storage RLS policies | β Storage | storage schema policy diffing |
|
| Edge Functions | Function metadata | β Edge Functions | Slug, version, status (source code requires manual deploy) |
| Cron | pg_cron jobs |
β Cron | Schedule, command, active status with SQL generation |
| Realtime | Publications | β Realtime | pg_publication + pg_publication_tables |
| Broadcast / Presence | β¬ N/A | Runtime features, not environment config | |
| Platform | Network restrictions | β¬ N/A | Platform-level (not diffable via SQL or Management API) |
| SSL enforcement | β¬ N/A | Platform-level | |
| Custom domains | β¬ N/A | Platform-level | |
| Branching | β¬ N/A | SupaForge provides its own cloning via supaforge clone |
|
| Read replicas | β¬ N/A | Platform-level |
β = Covered Β π = Planned Β β¬ = Not applicable / not planned
supaforge init Create config interactively
supaforge diff Summary: what's drifted?
supaforge diff --detail Show detailed SQL diffs
supaforge diff --apply Fix the drift
supaforge diff --check=rls Limit to a specific check
supaforge diff --skip=storage Skip a specific check
supaforge diff --skip=auth --skip=vault Skip multiple checks (repeatable)
supaforge hukam Alias for diff π
supaforge snapshot Capture full 9-layer snapshot
supaforge snapshot --migration Also generate incremental migration diff
supaforge snapshot --list List all snapshots
supaforge snapshot --prune --apply Delete old snapshots
supaforge clone --env=prod Preflight checks
supaforge clone --env=prod --apply Clone remote to local
supaforge clone --env=prod --force Force re-clone (drop existing DB)
supaforge clone --env=prod --start-local Auto-start a local PostgreSQL container
supaforge clone --list List existing clones
supaforge clone --delete=<name> --apply Remove a clone
supaforge restore --env=local --from-snapshot=latest --apply Restore from snapshot
supaforge restore --env=local --from-migrations --apply Replay migrations
supaforge mcp Start MCP stdio server for AI agents
All commands that modify state preview by default. Add
--applyto execute.
SupaForge ships a built-in Model Context Protocol server. Configure Claude Desktop, Cursor, or any MCP-compatible AI client to call SupaForge tools directly:
{
"mcpServers": {
"supaforge": {
"command": "supaforge",
"args": ["mcp"]
}
}
}The MCP server exposes:
| Tool | Description |
|---|---|
scan_drift |
Scan for drift and return a structured report |
apply_fixes |
Apply SQL fixes (supports dryRun=true preview) |
take_snapshot |
Capture a point-in-time environment snapshot |
create_migration |
Generate a migration file from snapshot diff |
get_check_result |
Retrieve the result for a specific check from the last scan |
Resources: supaforge://config, supaforge://last-scan, supaforge://migrations
Prompts: review_drift_before_deploy, fix_critical_issues
Create supaforge.config.json in your project root:
{
"environments": {
"dev": {
"dbUrl": "postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:5432/postgres",
"projectRef": "abc123",
"accessToken": "your-service-role-key"
},
"prod": {
"dbUrl": "postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:5432/postgres",
"projectRef": "xyz789",
"accessToken": "your-service-role-key"
}
},
"source": "dev",
"target": "prod",
"ignoreSchemas": ["auth", "storage", "realtime", "vault"],
"checks": {
"data": {
"tables": ["plans", "feature_flags", "pricing_tiers"]
},
"exclude": ["storage", "vault", "auth", "edge-functions", "realtime"]
}
}Supabase internal schemas (auth, storage, realtime, vault, etc.) are ignored by default.
checks.exclude permanently skips the listed checks on every diff/hukam/sync run β useful when diffing against a clone where checks like storage, auth, edge-functions, vault, and realtime have no local equivalent and produce only noise. The --skip CLI flag does the same on a one-off basis; both are merged at runtime.
SupaForge includes a lightweight hook bus for extensibility:
import { HookBus, scan, createDefaultRegistry, loadConfig } from '@akalforge/supaforge'
const bus = new HookBus()
bus.on('supaforge.scan.before', (ctx) => {
console.log(`Scanning ${ctx.config.source} β ${ctx.config.target}`)
})
bus.on('supaforge.check.after', ({ check, result }) => {
if (result.status === 'drifted') {
console.log(`β Drift detected in ${check}`)
}
})
const config = await loadConfig()
const registry = createDefaultRegistry()
const result = await scan(registry, { config }, bus)packages/cli/
βββ src/
β βββ commands/ # CLI commands (diff, snapshot, clone, restore)
β βββ checks/ # Drift detection checks
β β βββ base.ts # Abstract Check class
β β βββ registry.ts # CheckRegistry
β β βββ rls.ts # RLS policy diffing
β β βββ cron.ts # Cron job diffing
β β βββ ... # edge-functions, storage, auth, webhooks, schema, data
β βββ types/ # TypeScript interfaces
β βββ utils/ # Shared utilities (error handling)
β βββ constants.ts # Centralised config values, timeouts, paths
β βββ config.ts # Config loader + validator
β βββ hooks.ts # HookBus (actions + filters)
β βββ scanner.ts # Scan orchestrator
β βββ scoring.ts # Health score (0β100)
β βββ render.ts # Terminal output
βββ test/ # 434 tests across 35 files
git clone https://github.com/akalforge/supaforge.git
cd supaforge/packages/cli
npm install
npm test # Run all tests (434 across 35 files)
npm run lint # Type-check
npm run build # Build with tsup
# Run in dev mode
./bin/dev.js diffIntegration tests run against real Postgres containers and verify the full stack including @dbdiff/cli:
# Full flow: start containers β seed β test β teardown
npm run test:integration
# Keep containers running for debugging
./scripts/test-integration.sh --no-teardownSee packages/cli/README.md for manual setup and more options.
Releases are dry-run by default. Pass --apply to publish for real.
node scripts/release.js patch # Dry-run: 0.0.1 β 0.0.2
node scripts/release.js minor # Dry-run: 0.0.1 β 0.1.0
node scripts/release.js prerelease # Dry-run: 0.0.1 β 0.0.2-rc.1
node scripts/release.js prerelease --preid=beta # Dry-run: β 0.0.2-beta.1
node scripts/release.js 1.0.0-rc.1 # Dry-run: explicit version
node scripts/release.js patch --apply # Actually bump, commit, tag, pushThe tag push triggers .github/workflows/release.yml which publishes to npm and GitHub Packages.
See CONTRIBUTING.md for development setup, coding standards, and pull request guidelines.
To report a vulnerability, see SECURITY.md.
MIT β Copyright (c) 2026 Akal Forge