# Why Cursor breaks database migrations

- Tool: Cursor
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Cursor Free+, PostgreSQL/MySQL/SQLite with any migration tool
- Last updated: March 2026

## 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.

## Before you start

- 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
-- Generated: 2026-03-25
-- Database: PostgreSQL 16

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email VARCHAR(255) UNIQUE NOT NULL,
  name VARCHAR(100) NOT NULL,
  role VARCHAR(20) DEFAULT 'user',
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE orders (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  status VARCHAR(20) DEFAULT 'pending',
  total DECIMAL(10, 2) NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE 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.

```
---
description: Database migration safety rules
globs: "migrations/**/*.sql,prisma/**/*.prisma"
alwaysApply: true
---

## Database Migration Rules
- ALWAYS reference @schema/current_schema.sql before generating migrations
- Use IF NOT EXISTS for CREATE TABLE and CREATE INDEX
- Use IF EXISTS for DROP operations
- NEVER use DROP TABLE or DROP COLUMN without explicit user confirmation
- ALWAYS include a rollback/down migration
- Use explicit column types, NEVER use implicit type coercion
- Add NOT NULL constraints only with DEFAULT values for existing data
- Database engine: PostgreSQL 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 (Cmd+L):
// @schema/current_schema.sql
// @migrations/20260320_add_orders_table.sql
// Generate a new migration to add a 'shipping_address'
// JSONB column to the orders table with a default empty
// object. Include both UP and DOWN migrations.
// Reference the current orders table schema.

-- Expected output:
-- UP
ALTER TABLE orders
  ADD COLUMN shipping_address JSONB DEFAULT '{}'::jsonb;

-- DOWN
ALTER TABLE orders
  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.

```
-- For PostgreSQL, use a transaction with ROLLBACK:
BEGIN;

-- Paste the Cursor-generated migration here
ALTER TABLE orders
  ADD COLUMN shipping_address JSONB DEFAULT '{}'::jsonb;

-- Check that it applied correctly
\d orders

-- Roll back without committing
ROLLBACK;
```

> 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.

```
# Cmd+K in Cursor terminal:
# "Generate a pg_dump command that exports only the
# schema (no data) from my local database to
# schema/current_schema.sql"

pg_dump --schema-only --no-owner --no-privileges \
  -f schema/current_schema.sql \
  $DATABASE_URL
```

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

## Complete code example

File: `migrations/20260325_add_shipping_to_orders.sql`

```sql
-- Migration: Add shipping fields to orders table
-- Generated with Cursor assistance
-- Date: 2026-03-25

-- ============ UP ============

ALTER TABLE orders
  ADD COLUMN IF NOT EXISTS shipping_address JSONB DEFAULT '{}'::jsonb;

ALTER TABLE orders
  ADD COLUMN IF NOT EXISTS shipping_method VARCHAR(50);

ALTER TABLE orders
  ADD COLUMN IF NOT EXISTS tracking_number VARCHAR(100);

ALTER TABLE orders
  ADD COLUMN IF NOT EXISTS shipped_at TIMESTAMP;

ALTER TABLE orders
  ADD COLUMN IF NOT EXISTS delivered_at TIMESTAMP;

-- Add index for shipping status queries
CREATE INDEX IF NOT EXISTS idx_orders_shipped_at
  ON orders(shipped_at)
  WHERE shipped_at IS NOT NULL;

-- Add partial index for undelivered orders
CREATE INDEX IF NOT EXISTS idx_orders_undelivered
  ON orders(status)
  WHERE delivered_at IS NULL AND status = 'shipped';

-- ============ DOWN ============

DROP INDEX IF EXISTS idx_orders_undelivered;
DROP INDEX IF EXISTS idx_orders_shipped_at;

ALTER TABLE orders DROP COLUMN IF EXISTS delivered_at;
ALTER TABLE orders DROP COLUMN IF EXISTS shipped_at;
ALTER TABLE orders DROP COLUMN IF EXISTS tracking_number;
ALTER TABLE orders DROP COLUMN IF EXISTS shipping_method;
ALTER TABLE orders DROP COLUMN IF EXISTS shipping_address;
```

## Common mistakes

- **Not providing the current schema as context** — Without a schema reference, Cursor guesses table structures from scattered queries in your codebase. It may use wrong column names or miss constraints. Fix: Always include @schema/current_schema.sql or @prisma/schema.prisma in your migration prompts.
- **Accepting ALTER TABLE ADD COLUMN NOT NULL without a DEFAULT** — Adding a NOT NULL column to a table with existing data fails because existing rows have no value for the new column. Fix: Add a .cursor/rules directive: 'Add NOT NULL constraints only with DEFAULT values for existing data.'
- **Skipping the DOWN migration** — Cursor often generates only the UP migration. Without a DOWN migration, you cannot roll back safely. Fix: Explicitly ask for 'both UP and DOWN migrations' in every prompt. Add this requirement to your database rules.
- **Running Cursor-generated migrations directly on production** — Even with good context, Cursor can generate subtly incorrect SQL that passes syntax checks but causes data loss. Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/cursor-tutorial/how-to-troubleshoot-cursor-ai-generating-inaccurate-sql-schema-migrations
© RapidDev — https://www.rapidevelopers.com/cursor-tutorial/how-to-troubleshoot-cursor-ai-generating-inaccurate-sql-schema-migrations
