Skip to main content
RapidDev - Software Development Agency
cursor-tutorial

Why Cursor breaks database migrations

Cursor generates SQL migrations that break databases when it lacks schema context or uses outdated column names. By referencing your current schema file with @file, adding database rules to .cursor/rules, and validating migrations with a dry-run step, you prevent data loss and ensure Cursor produces accurate schema changes every time.

What you'll learn

  • Why Cursor generates incorrect SQL migrations and how to prevent it
  • How to provide schema context so Cursor knows your current database state
  • How to write .cursor/rules for safe migration generation
  • How to validate Cursor-generated migrations before running them
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner7 min read10-15 minCursor Free+, PostgreSQL/MySQL/SQLite with any migration toolMarch 2026RapidDev Engineering Team
TL;DR

Cursor generates SQL migrations that break databases when it lacks schema context or uses outdated column names. By referencing your current schema file with @file, adding database rules to .cursor/rules, and validating migrations with a dry-run step, you prevent data loss and ensure Cursor produces accurate schema changes every time.

Why Cursor breaks database migrations and how to fix it

Cursor generates SQL migrations by inferring schema from the files in your project context. Without explicit schema references, it guesses column names, types, and constraints, often incorrectly. This tutorial shows how to give Cursor accurate schema context, set up guardrails for migration safety, and validate output before it touches your database.

Prerequisites

  • Cursor installed with a project that uses database migrations
  • A current schema file or migration history in your project
  • Familiarity with your migration tool (Prisma, Knex, TypeORM, raw SQL)
  • Database backup or development environment for testing

Step-by-step guide

1

Export your current schema as a reference file

Create a schema snapshot that Cursor can reference. This gives the AI an accurate picture of your current database state. For Prisma, your schema.prisma file works. For raw SQL projects, export the schema and save it in your project.

schema/current_schema.sql
1-- schema/current_schema.sql
2-- Generated: 2026-03-25
3-- Database: PostgreSQL 16
4
5CREATE TABLE users (
6 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
7 email VARCHAR(255) UNIQUE NOT NULL,
8 name VARCHAR(100) NOT NULL,
9 role VARCHAR(20) DEFAULT 'user',
10 created_at TIMESTAMP DEFAULT NOW(),
11 updated_at TIMESTAMP DEFAULT NOW()
12);
13
14CREATE TABLE orders (
15 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
16 user_id UUID REFERENCES users(id) ON DELETE CASCADE,
17 status VARCHAR(20) DEFAULT 'pending',
18 total DECIMAL(10, 2) NOT NULL,
19 created_at TIMESTAMP DEFAULT NOW()
20);
21
22CREATE INDEX idx_orders_user_id ON orders(user_id);
23CREATE INDEX idx_orders_status ON orders(status);

Pro tip: Regenerate this file after every migration. Use pg_dump --schema-only for PostgreSQL or mysqldump --no-data for MySQL.

Expected result: A single file containing your complete current database schema that Cursor can reference.

2

Add database migration rules to .cursor/rules

Create rules that enforce safe migration patterns. These rules prevent Cursor from generating destructive operations without safeguards and ensure correct SQL syntax for your database engine.

.cursor/rules/database.mdc
1---
2description: Database migration safety rules
3globs: "migrations/**/*.sql,prisma/**/*.prisma"
4alwaysApply: true
5---
6
7## Database Migration Rules
8- ALWAYS reference @schema/current_schema.sql before generating migrations
9- Use IF NOT EXISTS for CREATE TABLE and CREATE INDEX
10- Use IF EXISTS for DROP operations
11- NEVER use DROP TABLE or DROP COLUMN without explicit user confirmation
12- ALWAYS include a rollback/down migration
13- Use explicit column types, NEVER use implicit type coercion
14- Add NOT NULL constraints only with DEFAULT values for existing data
15- Database engine: PostgreSQL 16
16- ALWAYS add created_at and updated_at timestamps to new tables

Expected result: Cursor will follow safe migration patterns and reference your schema before generating changes.

3

Generate a migration with proper context

When asking Cursor to create a migration, always reference your schema file and any recent migration files. Open Chat (Cmd+L) and provide explicit context about what change you need.

Cursor Chat prompt
1// Cursor Chat prompt (Cmd+L):
2// @schema/current_schema.sql
3// @migrations/20260320_add_orders_table.sql
4// Generate a new migration to add a 'shipping_address'
5// JSONB column to the orders table with a default empty
6// object. Include both UP and DOWN migrations.
7// Reference the current orders table schema.
8
9-- Expected output:
10-- UP
11ALTER TABLE orders
12 ADD COLUMN shipping_address JSONB DEFAULT '{}'::jsonb;
13
14-- DOWN
15ALTER TABLE orders
16 DROP COLUMN IF EXISTS shipping_address;

Expected result: Cursor generates a migration that correctly references the existing orders table structure with proper UP and DOWN sections.

4

Validate the migration with a dry run

Before applying any Cursor-generated migration, validate it against your development database. Use Cursor's terminal (Cmd+K in terminal) to run a syntax check or dry run. This catches column name mismatches, type errors, and constraint violations before they reach production.

Terminal (psql)
1-- For PostgreSQL, use a transaction with ROLLBACK:
2BEGIN;
3
4-- Paste the Cursor-generated migration here
5ALTER TABLE orders
6 ADD COLUMN shipping_address JSONB DEFAULT '{}'::jsonb;
7
8-- Check that it applied correctly
9\d orders
10
11-- Roll back without committing
12ROLLBACK;

Pro tip: Wrap every migration test in BEGIN/ROLLBACK so your development database stays clean during validation.

Expected result: The migration runs without errors inside the transaction, and ROLLBACK leaves your database unchanged.

5

Update your schema reference after applying migrations

After successfully running a migration, regenerate your schema reference file. This keeps Cursor's context accurate for the next migration. Use Cmd+K in the terminal to generate the updated schema dump.

Terminal
1# Cmd+K in Cursor terminal:
2# "Generate a pg_dump command that exports only the
3# schema (no data) from my local database to
4# schema/current_schema.sql"
5
6pg_dump --schema-only --no-owner --no-privileges \
7 -f schema/current_schema.sql \
8 $DATABASE_URL

Expected result: An updated schema file reflecting the latest migration, ready for Cursor to reference in future prompts.

Complete working example

migrations/20260325_add_shipping_to_orders.sql
1-- Migration: Add shipping fields to orders table
2-- Generated with Cursor assistance
3-- Date: 2026-03-25
4
5-- ============ UP ============
6
7ALTER TABLE orders
8 ADD COLUMN IF NOT EXISTS shipping_address JSONB DEFAULT '{}'::jsonb;
9
10ALTER TABLE orders
11 ADD COLUMN IF NOT EXISTS shipping_method VARCHAR(50);
12
13ALTER TABLE orders
14 ADD COLUMN IF NOT EXISTS tracking_number VARCHAR(100);
15
16ALTER TABLE orders
17 ADD COLUMN IF NOT EXISTS shipped_at TIMESTAMP;
18
19ALTER TABLE orders
20 ADD COLUMN IF NOT EXISTS delivered_at TIMESTAMP;
21
22-- Add index for shipping status queries
23CREATE INDEX IF NOT EXISTS idx_orders_shipped_at
24 ON orders(shipped_at)
25 WHERE shipped_at IS NOT NULL;
26
27-- Add partial index for undelivered orders
28CREATE INDEX IF NOT EXISTS idx_orders_undelivered
29 ON orders(status)
30 WHERE delivered_at IS NULL AND status = 'shipped';
31
32-- ============ DOWN ============
33
34DROP INDEX IF EXISTS idx_orders_undelivered;
35DROP INDEX IF EXISTS idx_orders_shipped_at;
36
37ALTER TABLE orders DROP COLUMN IF EXISTS delivered_at;
38ALTER TABLE orders DROP COLUMN IF EXISTS shipped_at;
39ALTER TABLE orders DROP COLUMN IF EXISTS tracking_number;
40ALTER TABLE orders DROP COLUMN IF EXISTS shipping_method;
41ALTER TABLE orders DROP COLUMN IF EXISTS shipping_address;

Common mistakes

Why it's a problem: Not providing the current schema as context

How to avoid: Always include @schema/current_schema.sql or @prisma/schema.prisma in your migration prompts.

Why it's a problem: Accepting ALTER TABLE ADD COLUMN NOT NULL without a DEFAULT

How to avoid: Add a .cursor/rules directive: 'Add NOT NULL constraints only with DEFAULT values for existing data.'

Why it's a problem: Skipping the DOWN migration

How to avoid: Explicitly ask for 'both UP and DOWN migrations' in every prompt. Add this requirement to your database rules.

Why it's a problem: Running Cursor-generated migrations directly on production

How to avoid: Always test migrations on a development database first using BEGIN/ROLLBACK transactions.

Best practices

  • Maintain an up-to-date schema reference file and regenerate it after every migration
  • Always reference @schema/current_schema.sql in migration prompts
  • Add database safety rules to .cursor/rules requiring IF EXISTS/IF NOT EXISTS guards
  • Request both UP and DOWN migrations in every prompt
  • Test all generated migrations in a transaction with ROLLBACK before committing
  • Use Plan Mode (Shift+Tab) for complex multi-table migrations to review the plan first
  • Include your database engine and version in .cursor/rules so Cursor uses correct syntax

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a PostgreSQL 16 database with a users table (id UUID, email VARCHAR, name VARCHAR, role VARCHAR, created_at, updated_at) and an orders table (id UUID, user_id UUID FK, status VARCHAR, total DECIMAL, created_at). Generate a migration to add shipping_address JSONB, shipping_method, tracking_number, shipped_at, and delivered_at columns to orders. Include UP and DOWN migrations with IF EXISTS guards.

Cursor Prompt

In Cursor Chat (Cmd+L): @schema/current_schema.sql @migrations/ Generate a new migration to add shipping fields (shipping_address JSONB, shipping_method VARCHAR, tracking_number VARCHAR, shipped_at TIMESTAMP, delivered_at TIMESTAMP) to the orders table. Include both UP and DOWN. Use IF NOT EXISTS guards. Add appropriate indexes.

Frequently asked questions

Why does Cursor generate column names that do not match my schema?

Cursor infers column names from your application code, not from the actual database. If your code uses camelCase but your database uses snake_case, Cursor may generate the wrong names. Reference the actual schema file to fix this.

Can Cursor generate Prisma migrations?

Yes. Reference @prisma/schema.prisma in your prompt and ask Cursor to update the schema file. Then run npx prisma migrate dev yourself to generate the actual SQL migration from the updated schema.

How do I handle migrations for multiple database engines?

Specify the database engine in your .cursor/rules file and in each prompt. If your project supports multiple engines, create separate rule files for each: database-postgres.mdc and database-mysql.mdc.

Should I let Cursor run migrations directly in Agent mode?

No. Even with YOLO mode, never let Cursor execute database migrations automatically. Always review the SQL, test with BEGIN/ROLLBACK, and run the final migration manually or through your established CI/CD pipeline.

What if Cursor generates a migration that loses data?

If you followed the dry-run step with BEGIN/ROLLBACK, no data was lost. If a destructive migration was applied, use the DOWN migration to revert, or restore from your pre-migration backup.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.