windmill-labs/windmill · error

Invalid migration name '${name}': use only letters, digits,

Error message

Invalid migration name '${name}': use only letters, digits, '_' and '-'

What it means

`createMigration` scaffolds empty `<timestamp>_<name>.up.sql`/`.down.sql` files for a datatable migration. It validates the name against a regex allowing only letters, digits, underscore and hyphen, and throws this error for anything else (spaces, dots, slashes, etc.), preventing invalid filenames or path traversal.

Source

Thrown at cli/src/commands/datatable_migrations.ts:53

function nextMigrationTimestamp(dir: string): string {
  const now = Number(migrationTimestamp());
  let max = 0;
  if (fs.existsSync(dir)) {
    for (const file of fs.readdirSync(dir)) {
      const m = file.match(/^(\d+)_.*\.(up|down)\.sql$/);
      if (m) max = Math.max(max, Number(m[1]));
    }
  }
  return String(max >= now ? max + 1 : now);
}

/**
 * Scaffold a new migration under migrations/datatable/<datatable>/ as empty
 * `<timestamp>_<name>.up.sql` and `.down.sql` files. Purely local — no network.
 */
export function createMigration(datatable: string, name: string): void {
  if (!MIGRATION_NAME_RE.test(name)) {
    throw new Error(
      `Invalid migration name '${name}': use only letters, digits, '_' and '-'`,
    );
  }
  const dir = path.join(process.cwd(), MIGRATIONS_DIR, datatable);
  fs.mkdirSync(dir, { recursive: true });

  const timestamp = nextMigrationTimestamp(dir);
  const base = `${timestamp}_${name}`;
  const up = path.join(dir, `${base}.up.sql`);
  const down = path.join(dir, `${base}.down.sql`);
  // Frame the body in an explicit transaction so it applies atomically, matching
  // the template the UI's "New migration" modal seeds.
  const template = (direction: string) =>
    `-- ${direction} migration: ${name}\nBEGIN;\n\n-- Add your migration here\n\nEND;\n`;
  fs.writeFileSync(up, template("up"), "utf-8");
  fs.writeFileSync(down, template("down"), "utf-8");

  log.info(

View on GitHub (pinned to e474e8803c)

Solutions

  1. Rename the migration using only letters, digits, '_' and '-', e.g. `add-users-table`.
  2. Quote the argument in your shell to avoid word-splitting, then replace offending characters.
  3. Use kebab-case or snake_case names going forward.

Example fix

// before
wmill datatable migrate new main add users table
// after
wmill datatable migrate new main add-users-table
Defensive patterns

Strategy: validation

Validate before calling

const RE = /^[A-Za-z0-9_-]+$/;
if (!RE.test(name)) throw new Error(`migration name '${name}' must match [A-Za-z0-9_-]`);

Type guard

function isValidMigrationName(n: string): boolean { return /^[A-Za-z0-9_-]+$/.test(n); }

Try / catch

try { createMigration(dt, name) } catch (e) { if (String(e).includes('Invalid migration name')) name = name.replace(/[^A-Za-z0-9_-]/g, '-'); }

Prevention

When it happens

Trigger: Running `wmill datatable migrate new <datatable> <name>` where `<name>` contains characters outside [A-Za-z0-9_-] — e.g. spaces, dots ('1.2.3'), slashes, or shell-expanded glob characters.

Common situations: Copy-pasting a description with spaces as the migration name; using dotted version numbers; accidentally passing a file path as the name.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/4601d45c40bdc482. Report an issue: GitHub.