# How to Write a Migration Script in the Migrations/ Directory with the Timestamp Naming Convention

> Learn how to write a migration script in Omarchy's migrations directory using the timestamp naming convention. Follow these steps for effective database changes.

- Repository: [Omacom/omarchy](https://github.com/omacom/omarchy)
- Tags: how-to-guide
- Published: 2026-09-09

---

**To write a migration script in Omarchy's `migrations/` directory, create a file named with a Unix timestamp followed by `.sh` (e.g., [`1788862626.sh`](https://github.com/omacom/omarchy/blob/main/1788862626.sh)), start with a descriptive `echo` line, write idempotent bash logic without a shebang, and ensure the file permissions are set to `0644`.**

Omarchy uses migration scripts to perform one-off repairs when package updates need to modify state that pacman cannot handle automatically. When you write a migration script in the `migrations/` directory according to the `omac/omarchy` source code, you must follow a strict timestamp naming convention and formatting requirements so the `omarchy-migrate` runner can discover and execute it reliably.

## Timestamp Naming Convention

Every migration file must reside in the repository's `migrations/` directory and use a Unix timestamp (seconds since epoch) as its filename with the `.sh` extension.

For example:

```

migrations/1788862626.sh

```

This timestamp guarantees total ordering and prevents name collisions across concurrent development. The `omarchy-dev-add-migration` helper automates this naming by extracting the current commit date in Unix format and creating the file for you, as implemented in `/bin/omarchy-dev-add-migration` lines 30-36.

## Required File Format and Structure

The migration runner executes scripts with `bash -euo pipefail`, imposing specific formatting constraints documented in [`agents/skills/migrations.md`](https://github.com/omacom/omarchy/blob/main/agents/skills/migrations.md).

### Permissions and Shebang

Migration files must have permissions set to `0644` (`-rw-r--r--`). The runner does not rely on executable bits. Crucially, **do not include a shebang line**—the migration runner supplies the interpreter explicitly.

### Header and Variable Usage

Begin each script with a single `echo` line that briefly describes the migration's purpose. For any repository-relative paths, use the `$OMARCHY_PATH` variable instead of hardcoded paths.

### Idempotence Requirements

All migration actions must be safe to run repeatedly. Check existing system state before modifying it so the script can be re-executed without side effects. For example, verify a symlink exists before recreating it, or check if a package is present before installing.

### Preferred Helper Commands

When possible, use Omarchy helper utilities such as `omarchy-cmd-present`, `omarchy-pkg-add`, and other standardized tools for common operations rather than raw shell commands.

## Practical Example

Here is a real-world migration from [`migrations/1788862626.sh`](https://github.com/omacom/omarchy/blob/main/migrations/1788862626.sh) that relinks a Neovim theme configuration:

```bash

# Example migration demonstrating idempotent symlink handling

echo "Relink Neovim theme to Omarchy current state"

theme_link="$HOME/.config/nvim/lua/plugins/theme.lua"
current_target="../../../../.local/state/omarchy/current/theme/neovim.lua"

# Exit early if the symlink does not exist (idempotent check)

[[ -L $theme_link ]] || exit 0

# Re-create the symlink atomically

ln -sfn "$current_target" "$theme_link"

```

This pattern ensures the script exits successfully if no action is needed and safely updates the link when required.

## Step-by-Step Creation Process

While you can manually create files, the recommended workflow uses the provided helper to ensure correct naming and location.

1. **Generate the file using the helper**:

   ```bash
   omarchy-dev-add-migration --no-edit
   ```

   This creates `migrations/<unix-timestamp>.sh` with the correct permissions and prints the absolute path. Omit `--no-edit` to open the file immediately in `nvim`.

2. **Add the descriptive header**:

   Insert an `echo` line describing the migration's purpose as the first executable statement.

3. **Implement idempotent logic**:

   Write bash code that checks state before modifying, using `$OMARCHY_PATH` for repository references and preferring Omarchy helper commands.

4. **Verify permissions**:

   Ensure the file mode is `0644` (though the helper sets this by default).

5. **Test locally**:

   Validate the migration against a temporary home directory to mimic the runner environment:

   ```bash
   HOME=$(mktemp -d) bash -euo pipefail migrations/<timestamp>.sh
   ```

   This mimics the execution environment used by `omarchy-migrate` as documented in [`agents/skills/migrations.md`](https://github.com/omacom/omarchy/blob/main/agents/skills/migrations.md) lines 51-58.

## How Migration Execution Works

Understanding the runtime behavior helps ensure your scripts handle edge cases correctly.

- **During package updates**: After `omarchy update` completes, `omarchy-migrate` runs all pending migrations for the current user.
- **At graphical login**: A systemd service triggers `omarchy-migrate --pending` and presents a terminal if any migrations remain unexecuted.
- **Manual invocation**: Users can run `omarchy-migrate` directly at any time; completed migrations are skipped automatically.

Completion tracking occurs per-user under `~/.local/state/omarchy/migrations/<migration-filename>`, ensuring each user on a machine executes the script independently.

## Summary

- **Timestamp naming**: Use Unix epoch seconds with `.sh` extension (e.g., [`1788862626.sh`](https://github.com/omacom/omarchy/blob/main/1788862626.sh)) in the `migrations/` directory.
- **No shebang required**: The runner supplies `bash -euo pipefail`; omit the interpreter declaration.
- **Start with echo**: Begin with a descriptive `echo` line explaining the migration's purpose.
- **Idempotence is mandatory**: Check state before modifying to allow safe re-execution.
- **Use helpers**: Leverage `omarchy-dev-add-migration` for creation and Omarchy utility commands for operations.
- **Permissions**: Set files to `0644` (readable, not executable).

## Frequently Asked Questions

### Why does Omarchy use Unix timestamps instead of sequential numbers for migration naming?

Unix timestamps provide a decentralized naming scheme that prevents collisions when multiple developers create migrations simultaneously without coordinating sequence numbers. The numeric value ensures chronological ordering while avoiding merge conflicts that sequential numbering (001, 002) typically creates in distributed development.

### What happens if my migration script fails during execution?

The `omarchy-migrate` runner executes scripts with `bash -euo pipefail`, meaning it exits immediately on errors or undefined variables. If a migration fails, it will not be marked as complete in `~/.local/state/omarchy/migrations/`, causing it to retry on the next update or login until it succeeds or is manually resolved.

### Can I use languages other than Bash for migration scripts?

No. The migration runner specifically expects Bourne-again shell (bash) scripts and invokes them with explicit bash arguments. While you could theoretically call other interpreters from within the bash script, the migration file itself must be valid bash to satisfy the runner's execution model.

### How do I test a migration script without affecting my live system?

Create a temporary home directory and execute the script manually with the same flags the runner uses: `HOME=$(mktemp -d) bash -euo pipefail migrations/<timestamp>.sh`. This isolates the migration's effects while accurately simulating the environment that `omarchy-migrate` provides during actual execution.