The @supabase/pg-delta package provides a programmatic API for generating and applying migration plans.
npm install @supabase/pg-deltaimport { createPlan, applyPlan } from "@supabase/pg-delta";
import { supabase } from "@supabase/pg-delta/integrations/supabase";
// Create a migration plan
const result = await createPlan(
"postgresql://localhost:5432/source_db",
"postgresql://localhost:5432/target_db",
{ filter: supabase.filter, serialize: supabase.serialize }
);
if (result) {
const { plan } = result;
console.log(plan.statements); // SQL statements to execute
// Apply the plan
const applyResult = await applyPlan(
plan,
"postgresql://localhost:5432/source_db",
"postgresql://localhost:5432/target_db"
);
console.log(applyResult.status);
}import {
createPlan,
applyPlan,
type Plan,
type CreatePlanOptions,
type IntegrationDSL,
} from "@supabase/pg-delta";import { supabase } from "@supabase/pg-delta/integrations/supabase";Create a migration plan by comparing two databases.
source(string | Sql): Source database connection URL or postgres.js client (current state)target(string | Sql): Target database connection URL or postgres.js client (desired state)options(CreatePlanOptions, optional): Configuration options
Promise<{ plan: Plan; sortedChanges: Change[]; ctx: DiffContext } | null>
- Returns an object with the plan and metadata if there are changes
- Returns
nullif databases are identical
import { createPlan } from "@supabase/pg-delta";
const result = await createPlan(
process.env.SOURCE_DB_URL!,
process.env.TARGET_DB_URL!
);
if (result) {
console.log(`Found ${result.plan.statements.length} statements`);
console.log(result.plan.statements.join(";\n"));
} else {
console.log("No differences found");
}Apply a plan's SQL statements to a database with integrity checks. Validates fingerprints before and after application to ensure plan integrity.
plan(Plan): The migration plan to applysource(string | Sql): Source database connection URL or postgres.js clienttarget(string | Sql): Target database connection URL or postgres.js clientoptions(ApplyPlanOptions, optional): Configuration optionsverifyPostApply(boolean, default: true): Verify fingerprint after applying
Promise<ApplyPlanResult>
The result is a discriminated union with the following possible statuses:
type ApplyPlanResult =
| { status: "invalid_plan"; message: string }
| { status: "fingerprint_mismatch"; current: string; expected: string }
| { status: "already_applied" }
| { status: "applied"; statements: number; warnings?: string[] }
| { status: "failed"; error: unknown; script: string };import { createPlan, applyPlan } from "@supabase/pg-delta";
const result = await createPlan(sourceUrl, targetUrl);
if (result) {
const applyResult = await applyPlan(result.plan, sourceUrl, targetUrl);
switch (applyResult.status) {
case "applied":
console.log(`Applied ${applyResult.statements} statements`);
break;
case "already_applied":
console.log("Plan already applied");
break;
case "fingerprint_mismatch":
console.error("Source database has changed since plan was created");
break;
case "failed":
console.error("Failed to apply:", applyResult.error);
break;
}
}A migration plan containing all changes to transform one database schema into another.
interface Plan {
version: number;
toolVersion?: string;
source: { fingerprint: string };
target: { fingerprint: string };
statements: string[];
role?: string;
filter?: FilterDSL;
serialize?: SerializeDSL;
risk?: { level: "safe" } | { level: "data_loss"; statements: string[] };
}Options for creating a plan.
interface CreatePlanOptions {
/** Filter - either FilterDSL (stored in plan) or ChangeFilter function (not stored) */
filter?: FilterDSL | ChangeFilter;
/** Serialize - either SerializeDSL (stored in plan) or ChangeSerializer function (not stored) */
serialize?: SerializeDSL | ChangeSerializer;
/** Role to use when executing the migration (SET ROLE will be added to statements) */
role?: string;
}A serializable representation of an integration combining filter and serialization options.
type IntegrationDSL = {
/** Filter DSL - determines which changes to include/exclude */
filter?: FilterDSL;
/** Serialization DSL - customizes how changes are serialized */
serialize?: SerializeDSL;
};See Integrations for the full DSL documentation.
Integrations provide pre-configured filter and serialize options. Use them by spreading their properties into CreatePlanOptions:
import { createPlan } from "@supabase/pg-delta";
import { supabase } from "@supabase/pg-delta/integrations/supabase";
// Use the supabase integration
const result = await createPlan(sourceUrl, targetUrl, {
filter: supabase.filter,
serialize: supabase.serialize,
});You can create your own integration using the IntegrationDSL type:
import { createPlan, type IntegrationDSL } from "@supabase/pg-delta";
const myIntegration: IntegrationDSL = {
filter: {
schema: "public",
},
serialize: [
{
when: { type: "schema", operation: "create" },
options: { skipAuthorization: true },
},
],
};
const result = await createPlan(sourceUrl, targetUrl, {
filter: myIntegration.filter,
serialize: myIntegration.serialize,
});Both createPlan and applyPlan may throw errors for connection failures or database query errors. Always wrap calls in try-catch blocks:
try {
const result = await createPlan(sourceUrl, targetUrl);
// Handle result
} catch (error) {
console.error("Failed to create plan:", error);
process.exit(1);
}import { createPlan, applyPlan } from "@supabase/pg-delta";
async function migrate() {
const sourceUrl = process.env.SOURCE_DB_URL!;
const targetUrl = process.env.TARGET_DB_URL!;
const result = await createPlan(sourceUrl, targetUrl);
if (!result) {
console.log("No differences found");
return;
}
console.log("Migration plan:");
console.log(result.plan.statements.join(";\n"));
const applyResult = await applyPlan(result.plan, sourceUrl, targetUrl);
if (applyResult.status === "applied") {
console.log(`Successfully applied ${applyResult.statements} statements`);
}
}import { createPlan, applyPlan } from "@supabase/pg-delta";
import { supabase } from "@supabase/pg-delta/integrations/supabase";
const result = await createPlan(sourceUrl, targetUrl, {
filter: supabase.filter,
serialize: supabase.serialize,
});
if (result) {
await applyPlan(result.plan, sourceUrl, targetUrl);
}import { createPlan } from "@supabase/pg-delta";
// SET ROLE will be added to the beginning of the migration
const result = await createPlan(sourceUrl, targetUrl, {
role: "postgres",
});import { createPlan } from "@supabase/pg-delta";
// Only include changes in the public schema
const result = await createPlan(sourceUrl, targetUrl, {
filter: { schema: "public" },
});