drizzle-team/drizzle-orm · error · Error

Failed to parse migration: ${journalEntry.tag}

Error message

Failed to parse migration: ${journalEntry.tag}

What it means

An Error `Failed to parse migration: ${journalEntry.tag}` thrown by readMigrationFiles() in drizzle-orm/src/op-sqlite/migrator.ts:34. The surrounding try block only does query.split('--> statement-breakpoint') and pushes a result object; the catch is a broad safety net that converts any unexpected failure during that transform into a named migration error.

Source

Thrown at drizzle-orm/src/op-sqlite/migrator.ts:34

		const query = migrations[`m${journalEntry.idx.toString().padStart(4, '0')}`];

		if (!query) {
			throw new Error(`Missing migration: ${journalEntry.tag}`);
		}

		try {
			const result = query.split('--> statement-breakpoint').map((it) => {
				return it;
			});

			migrationQueries.push({
				sql: result,
				bps: journalEntry.breakpoints,
				folderMillis: journalEntry.when,
				hash: '',
			});
		} catch {
			throw new Error(`Failed to parse migration: ${journalEntry.tag}`);
		}
	}

	return migrationQueries;
}

export async function migrate<TSchema extends Record<string, unknown>>(
	db: OPSQLiteDatabase<TSchema>,
	config: MigrationConfig,
) {
	const migrations = await readMigrationFiles(config);
	return db.dialect.migrate(migrations, db.session);
}

interface State {
	success: boolean;
	error?: Error;
}

View on GitHub (pinned to b7862528fd)

Solutions

  1. Check that each migration import is the raw SQL string (default export), not a module object — use `import m0000 from './0000_init.sql'` not `import * as m0000`.
  2. Inspect the actual value of migrations[`m${pad4(idx)}`] for the failing tag — log it before calling migrate().
  3. Re-generate the migration with drizzle-kit if the file content looks corrupt.
  4. Ensure the bundler loads .sql files as plain strings (raw-loader / asset extension config).

Example fix

// before — namespace import yields an object, query.split is undefined -> throws
import * as m0000 from './drizzle/0000_init.sql';
migrate(db, { journal, migrations: { m0000 } }); // Failed to parse migration: 0000_init

// after — default import gives the SQL string
import m0000 from './drizzle/0000_init.sql';
migrate(db, { journal, migrations: { m0000 } });
Defensive patterns

Strategy: validation

Validate before calling

function assertMigrationsAreStrings(migrations: Record<string, unknown>): void {
  for (const [key, value] of Object.entries(migrations)) {
    if (typeof value !== 'string') {
      throw new Error(`Migration ${key} is not a string (got ${typeof value}). Use a default import.`);
    }
  }
}

assertMigrationsAreStrings(migrations);

Type guard

function allMigrationsAreStrings(m: Record<string, unknown>): boolean {
  return Object.values(m).every((v) => typeof v === 'string');
}

Try / catch

try {
  await migrate(db, { journal, migrations });
} catch (e) {
  if (e instanceof Error && /Failed to parse migration/i.test(e.message)) {
    // log the actual value of each migration entry to find the non-string import
    console.error(migrations);
  }
  throw e;
}

Prevention

When it happens

Trigger: For a journal entry whose m{idx} file exists, something inside the parse try-block throws. In practice this is rare because split() does not throw on strings; it would require the migration value to be a non-string (e.g. undefined slipped past the missing-check, or a malformed import that resolves to an object/default) so that .split is undefined or throws.

Common situations: A migration import resolved to a module namespace object instead of the SQL string (missing default import, bundler interop issue), so query.split is not a function and throws, which the catch rewraps. Can also appear with corrupted/truncated migration content that breaks downstream assumptions.

Related errors


AI-assisted analysis of drizzle-team/drizzle-orm@b7862528fd (2026-08-03). Data as JSON: /data/errors/9194a1a4e81c249e.json. Report an issue: GitHub.