Skip to content

Instantly share code, notes, and snippets.

@alexanderson1993
Last active December 1, 2025 09:11
Show Gist options
  • Select an option

  • Save alexanderson1993/0852a8162ebac591b62a79883a81e1a8 to your computer and use it in GitHub Desktop.

Select an option

Save alexanderson1993/0852a8162ebac591b62a79883a81e1a8 to your computer and use it in GitHub Desktop.
Prisma D1 Migration CLI
migrate.mov

A handy CLI for working with the new Cloudflare D1/Prisma integration. You can read about that here: https://blog.cloudflare.com/prisma-orm-and-d1

Getting Started

  • Install wrangler, Prisma, and the other dependencies
npm install prisma@latest @prisma/client@latest @prisma/adapter-d1
npm install --save-dev wrangler toml tiny-parse-argv @clack/prompts
  • Create your D1 Database

npx wrangler d1 create prod-prisma-d1-app

  • Create a wrangler.toml file
// wrangler.toml
name="my-d1-prisma-app"
main = "src/index.ts"
compatibility_date = "2024-03-20"
compatibility_flags = ["nodejs_compat"]

[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "prod-prisma-d1-app"
database_id = "<unique-ID-for-your-database>"
  • Initialize Prisma
npx prisma init --datasource-provider sqlite
  • Turn on the adapters feature of Prisma
// schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
+ previewFeatures = ["driverAdapters"]
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

Usage

npx tsx prisma/migrate.ts

┌  D1 Prisma Migrate CLI
│
│  migrate <command>
│    Commands:
│      create - Create a new migration
│      apply - Apply pending migrations
│    Options:
│      -h, --help - Show this help message

Each command includes help with the --help option.

import fs from "node:fs/promises";
import path from "node:path";
import { exec } from "node:child_process";
import {
intro,
outro,
log,
select,
text,
spinner,
isCancel,
confirm,
} from "@clack/prompts";
import toml from "toml";
import parseArgv from "tiny-parse-argv";
const args = parseArgv(process.argv.slice(2));
const command = args._[0];
const projectRoot = path.resolve();
const asyncExec = (command: string) =>
new Promise<string>((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
reject(stderr);
} else {
resolve(stdout);
}
});
});
intro("D1 Prisma Migrate CLI");
if (args.help || !command) {
switch (command) {
case "create":
log.message(`migrate create
Create a new migration
Options:
-n, --name - The name of the migration
-d, --database - The name of the D1 database
--create-only - Only create the migration file, do not apply it
--schema - Custom path to the Prisma schema
-h, --help - Show this help message`);
break;
case "apply":
log.message(`migrate apply
Apply pending migrations
Options:
-d, --database - The name of the D1 database
--remote - Apply migrations to your remote database
--schema - Custom path to the Prisma schema
-h, --help - Show this help message`);
break;
default:
log.message(`migrate <command>
Commands:
create - Create a new migration
apply - Apply pending migrations
Options:
-h, --help - Show this help message`);
break;
}
process.exit(0);
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
let wranglerConfig: any;
// Check wrangler.toml to see what D1 namespaces are used
try {
const wranglerToml = await fs.readFile("wrangler.toml", "utf-8");
wranglerConfig = toml.parse(wranglerToml);
} catch {
log.error("Could not read wrangler.toml");
process.exit(1);
}
const databases: { value: string; label: string }[] =
wranglerConfig.d1_databases?.map((db: { database_name: string }) => ({
value: db.database_name,
label: db.database_name,
})) || [];
let database = args.d || args.database || databases[0]?.value;
if (command === "create") {
const database = await getDatabase();
const migrationName =
args.name ||
args.n ||
(await text({
message: "What is the name of the migration?",
validate: (input) => {
if (input.length === 0) {
return "Migration name cannot be empty";
}
},
}));
if (isCancel(migrationName)) {
process.exit(1);
}
const s = spinner();
s.start("Creating migration");
const result = await asyncExec(
`npx wrangler d1 migrations create ${database} ${migrationName}`
);
s.stop("Creating migration");
const migrationPath = result
.trim()
.split("\n")
.find((line) => line.endsWith(".sql"));
if (!migrationPath) {
log.error("Could not find migration path");
process.exit(1);
}
s.start("Generating migration diff from Prisma schema");
await asyncExec(
`npx prisma migrate diff --script --from-local-d1 --to-schema-datamodel ${
args.schema || "./prisma/schema.prisma"
} >> ${migrationPath}`
);
s.stop("Generating migration diff from Prisma schema");
if (args["create-only"]) {
outro(`Migration created at ${migrationPath.replace(projectRoot, ".")}`);
process.exit();
}
}
if (command === "apply" || command === "create") {
const database = await getDatabase();
const s = spinner();
s.start("Applying migrations");
await asyncExec(
`npx wrangler d1 migrations apply ${database} ${
args.remote ? "--remote" : "--local"
}`
);
s.stop("Applying migrations");
s.start("Generating Prisma client");
await asyncExec(
`npx prisma generate ${args.schema ? `--schema ${args.schema}` : ""}`
);
s.stop("Generating Prisma client");
outro("Migrations applied");
}
async function getDatabase() {
if (databases.length === 0) {
log.error("No D1 databases found in wrangler.toml");
process.exit(1);
}
database =
database ||
(await select({
message: "Select a database",
options: databases,
initialValue: databases[0].value,
}));
if (isCancel(database)) {
process.exit(1);
}
return database;
}
@Hiutaky
Copy link

Hiutaky commented Oct 3, 2025

I've edited the script since it was not working using the --from-local-d1 flag and also I was using wrangler.jsonc instead of toml.

The way the script generate the migrations is quite similar to the original but, instead of getting the schema using --from-local-d1, I had to:

  • backup the updated schema
  • obtain the old schema from the database
  • perform the diff by comparing the 2 schemas
  • copy the updated schema as prisma.schema again
  • apply the migration
import fs from "node:fs/promises";
import path from "node:path";
import { exec } from "node:child_process";
import {
  intro,
  outro,
  log,
  select,
  text,
  spinner,
  isCancel,
} from "@clack/prompts";
import parseArgv from "tiny-parse-argv";
import { parse } from "jsonc-parse";

const args = parseArgv(process.argv.slice(2));
const command = args._[0];

const projectRoot = path.resolve();

const asyncExec = (command: string) =>
  new Promise<string>((resolve, reject) => {
    exec(command, (error, stdout, stderr) => {
      if (error) {
        reject(stderr);
      } else {
        resolve(stdout);
      }
    });
  });

intro("D1 Prisma Migrate CLI");

if (args.help || !command) {
  switch (command) {
    case "create":
      log.message(`migrate create
  Create a new migration
  Options:
    -n, --name - The name of the migration
    -d, --database - The name of the D1 database
    --create-only - Only create the migration file, do not apply it
    --schema - Custom path to the Prisma schema
    -h, --help - Show this help message`);
      break;
    case "apply":
      log.message(`migrate apply
  Apply pending migrations
  Options:
    -d, --database - The name of the D1 database
    --remote - Apply migrations to your remote database
    --schema - Custom path to the Prisma schema
    -h, --help - Show this help message`);
      break;
    default:
      log.message(`migrate <command>
  Commands:
    create - Create a new migration
    apply - Apply pending migrations
  Options:
    -h, --help - Show this help message`);
      break;
  }
  process.exit(0);
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
let wranglerConfig: any;

// Check wrangler.toml to see what D1 namespaces are used
try {
  const wranglerJsonc = await fs.readFile("wrangler.jsonc", "utf-8");
  wranglerConfig = parse(wranglerJsonc)
//   wranglerConfig = toml.parse(wranglerToml);
} catch {
  log.error("Could not read wrangler.toml");
  process.exit(1);
}

const databases: { value: string; label: string }[] =
  wranglerConfig.d1_databases?.map((db: { database_name: string }) => ({
    value: db.database_name,
    label: db.database_name,
  })) || [];

let database = args.d || args.database || databases[0]?.value;

if (command === "create") {
  const database = await getDatabase();


  const migrationName =
    args.name ||
    args.n ||
    (await text({
      message: "What is the name of the migration?",
      validate: (input) => {
        if (input.length === 0) {
          return "Migration name cannot be empty";
        }
      },
    }));

  if (isCancel(migrationName)) {
    process.exit(1);
  }

  const s = spinner();
  s.start("Creating migration");
  const result = await asyncExec(
    `npx wrangler d1 migrations create ${database} ${migrationName}`
  );

  s.stop("Creating migration");

  const migrationPath = result
    .trim()
    .split("\n")
    .find((line) => line.endsWith(".sql"));

  if (!migrationPath) {
    log.error("Could not find migration path");
    process.exit(1);
  }

  s.start("Generating migration diff from Prisma schema");
  const BASE_SCHEMA = args.schema ?? './prisma/schema.prisma';
  const TEMP_SCHEMA = './prisma/schema.prisma.updated';
  /**
   * Create a backup schema copy to be used to calculate the diff later
   * Pull the latest ( non migrated ) db schema
   */
  await fs.copyFile( BASE_SCHEMA, TEMP_SCHEMA );
  await asyncExec(
      `bunx prisma db pull`
  );

  await asyncExec(
    `npx prisma migrate diff --from-schema-datamodel ${TEMP_SCHEMA} --script --to-schema-datamodel ${
      args.schema || "./prisma/schema.prisma"
    } >> ${migrationPath}`
  );

  s.stop("Generating migration diff from Prisma schema");
  fs.copyFile(TEMP_SCHEMA, BASE_SCHEMA)
  fs.rm(TEMP_SCHEMA)
  if (args["create-only"]) {
    outro(`Migration created at ${migrationPath.replace(projectRoot, ".")}`);
    process.exit();
  }
}

if (command === "apply" || command === "create") {
  const database = await getDatabase();

  const s = spinner();
  s.start("Applying migrations");

  await asyncExec(
    `npx wrangler d1 migrations apply ${database} ${
      args.remote ? "--remote" : "--local"
    }`
  );

  s.stop("Applying migrations");

  s.start("Generating Prisma client");

  await asyncExec(
    `npx prisma generate ${args.schema ? `--schema ${args.schema}` : ""}`
  );

  s.stop("Generating Prisma client");

  outro("Migrations applied");
}

async function getDatabase() {
  if (databases.length === 0) {
    log.error("No D1 databases found in wrangler.toml");
    process.exit(1);
  }

  database =
    database ||
    (await select({
      message: "Select a database",
      options: databases,
      initialValue: databases[0].value,
    }));

  if (isCancel(database)) {
    process.exit(1);
  }
  return database;
}```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment