diesel-rs/diesel · error

Invalid migration directory: the directory's name should be

Error message

Invalid migration directory: the directory's name should be <timestamp>_<name_of_migration>, and it should contain up.sql and optionally down.sql.

What it means

Raised by `migration_literal_from_path` when a directory inside the migrations folder does not have a name starting with a timestamp/version (`<timestamp>_<name>`). The `embed_migrations!` macro parses each child directory name with `version_from_string`; if no numeric version prefix is found it panics with this message at compile time.

Solutions

  1. Rename the offending directory to `<timestamp>_<name>` (timestamp ascending, e.g. `20230901120000_fix_users`).
  2. Remove stray non-migration directories/files from the migrations folder.
  3. Use `diesel migration generate <name>` so naming is always valid.
  4. Check `cargo tree`/diesel version: very old timestamps (e.g. date-based) vs numeric schemas can matter; align names with your diesel version's expected format.

Example fix

// before
migrations/fix_users/up.sql
// after
migrations/20230901120000_fix_users/up.sql
Defensive patterns

Strategy: validation

Validate before calling

use std::fs;
for e in fs::read_dir("migrations").unwrap() {
    let name = e.unwrap().file_name().to_string_lossy().into_owned();
    let Some((version, _)) = name.split_once('_') else {
        panic!("bad migration dir: {name}")
    };
    assert!(!version.is_empty() && version.chars().all(|c| c.is_ascii_digit()), "bad version in {name}");
}

Type guard

fn valid_migration_name(name: &str) -> bool {
    match name.split_once('_') {
        Some((v, rest)) => !v.is_empty() && v.chars().all(|c| c.is_ascii_digit()) && !rest.is_empty(),
        None => false,
    }
}

Prevention

When it happens

Trigger: A subdirectory of the embedded migrations path whose name does not start with `<timestamp>_` (e.g. `fix_users`, `temp`, or a name where the part before `_` is not parseable as a version number).

Common situations: Hand-creating migration folders with casual names; renaming a migration and accidentally removing the timestamp prefix; checkout leaving stray directories (`.git`, `node_modules`, editor dirs) inside `migrations/`; non-diesel migration folders copied in.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of diesel-rs/diesel@6fa6ed01b2 (2026-09-07). Data as JSON: /api/errors/8e86818a1959f930. Report an issue: GitHub.

Appendix: source

Thrown at diesel_migrations/migrations_macros/src/embed_migrations.rs:45

    path: &Path,
) -> Result<Vec<proc_macro2::TokenStream>, Box<dyn Error>> {
    let mut migrations = migrations_directories(path)?.collect::<Result<Vec<_>, _>>()?;

    migrations.sort_by_key(DirEntry::path);

    Ok(migrations
        .into_iter()
        .map(|e| migration_literal_from_path(&e.path()))
        .collect())
}

fn migration_literal_from_path(path: &Path) -> proc_macro2::TokenStream {
    let name = path
        .file_name()
        .unwrap_or_else(|| panic!("Can't get file name from path `{path:?}`"))
        .to_string_lossy();
    if version_from_string(&name).is_none() {
        panic!(
            "Invalid migration directory: the directory's name should be \
             <timestamp>_<name_of_migration>, and it should contain \
             up.sql and optionally down.sql."
        );
    }
    let up_sql_path = path.join("up.sql");
    let up_sql_path = up_sql_path.to_str();
    let down_sql_path = path.join("down.sql");
    let metadata = TomlMetadata::read_from_file(&path.join("metadata.toml")).unwrap_or_default();
    let run_in_transaction = metadata.run_in_transaction;

    let down_sql = match down_sql_path.metadata() {
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => quote! { None },
        _ => {
            let down_sql_path = down_sql_path.to_str();
            quote! { Some(include_str!(#down_sql_path)) }
        }
    };

View on GitHub (pinned to 6fa6ed01b2)