# How Omarchy Migrations Work and How to Write New Migration Scripts

> Learn how Omarchy migrations work and write new migration scripts. Understand one-time repair scripts for package updates and state adjustments.

- Repository: [37signals/omarchy](https://github.com/basecamp/omarchy)
- Tags: how-to-guide
- Published: 2026-08-19

---

**Omarchy migrations are one-time repair scripts that run after package updates to adjust user or system state that Pacman cannot modify safely, with each script stored in `migrations/*.sh` and tracked per-user via marker files in `~/.local/state/omarchy/migrations/`.**

In the `basecamp/omarchy` repository, migrations form a critical bridge between immutable package updates and mutable user configuration. Unlike traditional database migrations, these Bash scripts repair symlinks, update systemd units, or adjust application state that requires user-scoped changes after system-level updates complete.

## What Are Omarchy Migrations?

Omarchy migrations are **idempotent repair scripts** designed to run after Pacman finishes updating packages. They live in the `migrations/` directory at the repository root, using Unix timestamp filenames like [`migrations/1781158082.sh`](https://github.com/basecamp/omarchy/blob/main/migrations/1781158082.sh) to ensure sequential execution.

The migration system addresses a specific gap in Arch Linux package management: certain configuration changes, theme updates, or user-level state modifications cannot be performed safely by Pacman during installation. Instead, the `omarchy-migrate` command—located at `bin/omarchy-migrate`—orchestrates these changes after the fact.

## When and How Migrations Execute

The migration runner executes scripts through three distinct triggers, ensuring no user is left with broken configuration after an update.

### Automatic Execution During Updates

When you run `omarchy update`, the process follows this sequence:

1. Pacman completes package installation
2. `omarchy-migrate` runs all pending migrations for the current user
3. `omarchy-hook post-update` executes final cleanup

This ensures that system updates immediately trigger necessary configuration repairs before the user continues working.

### Graphical Login Notifications

The systemd service `omarchy-migrate-notify.service` runs `omarchy-migrate --pending` at every graphical login. If pending migrations exist (indicated by exit code `0`), the service opens a terminal notification prompting the user to run `omarchy-migrate`.

This mechanism guarantees that secondary user accounts—who may not have performed the original system update—still receive necessary configuration changes.

### Manual Invocation

Users can trigger migrations manually at any time by running:

```bash
omarchy-migrate

```

The runner automatically skips any migrations already recorded in the user's state directory, making repeated execution safe.

## Understanding the Migration Lifecycle

The Omarchy migration system uses a marker-file pattern to ensure robust, per-user execution tracking.

### Per-User State Tracking

After a migration completes successfully, the runner creates a marker file:

```

~/.local/state/omarchy/migrations/<migration-filename>

```

For example, after running [`migrations/1781158082.sh`](https://github.com/basecamp/omarchy/blob/main/migrations/1781158082.sh), the file `~/.local/state/omarchy/migrations/1781158082.sh` appears. This **per-user state** ensures that every account on the system runs each migration independently, preventing permission errors or cross-user configuration leaks.

### Idempotency Requirements

Because migrations may execute multiple times (once per user, or manually during testing), every script must be **idempotent**. The script should detect whether its intended change already exists and exit without side effects if so.

According to the source code in [`agents/skills/migrations.md`](https://github.com/basecamp/omarchy/blob/main/agents/skills/migrations.md), proper idempotency prevents duplicate configuration entries, broken symlinks, or repeated package installations if a user manually re-runs the migrator.

## How to Write New Migration Scripts

Creating a new migration involves scaffolding, implementing idempotent logic, and testing against isolated environments.

### Generating a Migration Stub

Use the helper command to generate a properly named script:

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

```

This creates a file named `migrations/<unix-timestamp>.sh` with `0644` permissions. The timestamp naming convention ensures deterministic execution order across all installations.

### Migration Script Format and Conventions

All migration scripts must follow the **new migration format** documented in [`agents/skills/migrations.md`](https://github.com/basecamp/omarchy/blob/main/agents/skills/migrations.md):

- **No shebang line** – The runner executes scripts explicitly with `bash -euo pipefail`
- **Descriptive echo** – Begin with `echo` describing the migration's purpose
- **Use `$OMARCHY_PATH`** – Reference the repository root through this environment variable
- **Idempotent checks** – Verify existing state before modifying
- **Helper commands** – Use `omarchy-cmd-present`, `omarchy-pkg-add`, and other Omarchy utilities for system integration
- **No shell restart** – Do not restart the Omarchy shell; `omarchy update` handles this automatically

**Example migration** (re-linking the Neovim theme):

```bash
echo "Relink Neovim theme to Omarchy current state"

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

[[ -L $theme_link ]] || exit 0
ln -sfn "$current_relative_target" "$theme_link"

```

This script checks if the symlink exists before attempting to recreate it, demonstrating proper idempotency.

### Testing Your Migration

Test migrations against a temporary home directory to prevent contamination of your working environment:

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

```

To re-run a migration locally after the marker file has been created, remove the marker and invoke the migrator:

```bash
rm ~/.local/state/omarchy/migrations/1781158082.sh
omarchy-migrate

```

## Summary

- **Omarchy migrations** are repair scripts stored in `migrations/*.sh` that run after Pacman updates to fix user-level configuration.
- The `omarchy-migrate` command (in `bin/omarchy-migrate`) executes scripts with `bash -euo pipefail` and tracks completion via marker files in `~/.local/state/omarchy/migrations/`.
- Migrations trigger automatically during `omarchy update`, at graphical login via `omarchy-migrate-notify.service`, or through manual invocation.
- Every migration must be **idempotent**, checking for existing state before making changes.
- Use `omarchy-dev-add-migration --no-edit` to scaffold new scripts, and test them in temporary home directories before deployment.

## Frequently Asked Questions

### Where are Omarchy migration scripts stored?

All migration scripts live in the `migrations/` directory at the repository root, using Unix timestamp filenames (e.g., [`migrations/1781158082.sh`](https://github.com/basecamp/omarchy/blob/main/migrations/1781158082.sh)). This naming convention ensures chronological execution order across different systems and update sequences.

### How does Omarchy ensure migrations run for every user?

The system writes marker files to `~/.local/state/omarchy/migrations/<filename>` after each migration completes. Because this path resides in the user's home directory, every user account must run the migration independently. The `omarchy-migrate-notify.service` triggers at graphical login to catch users who missed the update window.

### What makes a migration script idempotent?

An idempotent migration safely executes multiple times without cumulative side effects. Scripts should check for existing configuration (e.g., `[[ -L $symlink ]]` or `[[ -f $file ]]`) and exit early if the desired state already exists. The runner invokes each script with `bash -euo pipefail`, so explicit error handling and state verification are essential.

### How do I test a migration script before committing?

Test against a temporary home directory using `HOME=$(mktemp -d) bash -euo pipefail migrations/<filename>.sh`. To re-test after the migration has already run, delete the corresponding marker file from `~/.local/state/omarchy/migrations/` and run `omarchy-migrate` again. This validates idempotency and ensures the script handles both initial and repeat executions correctly.