How to Write Omarchy Migrations: Rules and Best Practices

Omarchy migrations are idempotent shell scripts stored in migrations/*.sh that execute per-user via omarchy-migrate to synchronize existing installations after updates that pacman cannot handle alone.

Omarchy migrations provide a mechanism to update user-specific configurations and state when the basecamp/omarchy repository changes. These single-run scripts live in the migrations/ directory and execute automatically during omarchy update or at graphical login, ensuring every user receives necessary filesystem changes regardless of when they originally installed Omarchy.

What Are Omarchy Migrations?

Omarchy migrations are transformation scripts that bring existing installations into sync after package updates. Unlike database schema migrations, these are shell scripts that modify user configuration files, install optional dependencies, or retire legacy services. According to agents/skills/migrations.md, they run exclusively per-user via the omarchy-migrate command and must complete successfully before the user accesses their desktop environment.

Core Rules for Writing Omarchy Migrations

File Location and Naming

All migration scripts belong in the migrations/ directory with a .sh extension. Use the helper command omarchy-dev-add-migration --no-edit to scaffold new files with correct Unix timestamps (e.g., migrations/1785095882.sh). This naming convention ensures sequential execution based on creation time.

Permissions and Execution Context

Migration files must use permissions 0644 (readable, not executable) because the runner invokes them with bash -euo pipefail. Do not include a #!/bin/bash shebang. The omarchy-migrate binary in bin/omarchy-migrate executes these scripts as the current Omarchy user, normally during the update process or at graphical login.

Per-User Completion State

After a migration succeeds, the system creates a marker file at ~/.local/state/omarchy/migrations/<migration filename>. Each user maintains their own marker directory, guaranteeing that every user account on the system runs the migration exactly once. This design isolates user state while ensuring consistent transformation across multi-user installations.

Idempotence Requirements

Every migration must detect whether its work has already been performed by another user or previous run and exit harmlessly. This prevents duplicate configuration changes and ensures safe re-runs during testing. Check for existing files, symlinks, or configuration entries before performing destructive operations.

Major Version Boundaries

Regular migrations handle routine updates within a major version. Major version upgrades (e.g., 3 → 4) are handled by the omarchy-upgrade-to-quattro command rather than the standard migration system, as documented in the migration guide.

Required Migration Structure

Opening Description

Begin every migration with an echo statement that briefly describes the operation. This provides debugging output visible during omarchy update execution and creates an audit trail in system logs.

Path Handling

Use $OMARCHY_PATH for repository-relative paths when referencing assets within the Omarchy installation. For user-specific modifications, reference $HOME directly. Never hardcode absolute paths that assume specific username or home directory structures.

Helper Commands

Leverage Omarchy helper utilities for common operations:

  • omarchy-cmd-present — Verify a command exists before using it
  • omarchy-cmd-missing — Check if a binary is absent (returns success if missing)
  • omarchy-pkg-add — Install optional packages only when needed

These wrappers ensure consistent package management and command validation without requiring manual pacman calls.

Prohibited Actions

Never restart the Omarchy shell within a migration. The omarchy update command handles shell lifecycle management automatically after all migrations complete. Restarting prematurely would interrupt the update process and leave the system in an inconsistent state.

Creating and Testing Migrations

Scaffolding New Migrations

Generate correctly named migration files using:

omarchy-dev-add-migration --no-edit

This creates migrations/<unix-timestamp>.sh with appropriate permissions and places it in the correct directory.

Testing Locally

Test migrations against a temporary home directory to avoid contaminating your active configuration:

TMPHOME=$(mktemp -d)
HOME=$TMPHOME bash -euo pipefail migrations/1785095882.sh

Alternatively, delete the completion marker to force re-execution on your actual account:

rm -f "$HOME/.local/state/omarchy/migrations/1785095882.sh"
omarchy-migrate

Code Examples

Minimal Migration Skeleton


# migrations/$(date +%s).sh

echo "Link Neovim theme to the current Omarchy theme state"

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

# Bail out if the symlink already exists or target is missing

[[ -L $theme_link && $(readlink "$theme_link") == "$target" ]] && exit 0

# Create (or replace) the symlink

ln -sfn "$target" "$theme_link"

This example demonstrates idempotence by checking for existing symlinks, uses $HOME for portability, and starts with a descriptive echo.

Using Omarchy Helper Commands

echo "Ensure optional package 'bat' is installed"

# Only install if the command is missing

omarchy-cmd-missing bat && omarchy-pkg-add bat

The omarchy-cmd-missing helper returns success only when the binary is absent, allowing conditional installation without manual path checks. Note that set -e is unnecessary because the migration runner already invokes scripts with bash -euo pipefail.

Real-World Example

The file migrations/1785095882.sh demonstrates retiring a legacy systemd watcher and installing a new login-time notifier. This migration checks for the presence of the old service, removes it if found, and enables the replacement—exemplifying the idempotent pattern required for safe system updates.

Summary

  • Location: Store all migration scripts in migrations/*.sh using Unix timestamp filenames
  • Permissions: Set files to 0644 with no shebang; the runner handles execution via bash -euo pipefail
  • State Tracking: Markers are stored per-user at ~/.local/state/omarchy/migrations/<filename>
  • Idempotence: Every script must detect existing work and exit harmlessly to prevent duplicate changes
  • Structure: Begin with echo, use $OMARCHY_PATH for repo assets, leverage helper commands like omarchy-pkg-add
  • Testing: Use temporary $HOME directories or delete markers to validate changes safely

Frequently Asked Questions

Where should I place new Omarchy migration scripts?

Place all migration scripts in the migrations/ directory at the repository root. Use the omarchy-dev-add-migration --no-edit command to generate properly named files using Unix timestamps (e.g., migrations/1785095882.sh). Manual creation is possible but the helper ensures consistent naming conventions required by bin/omarchy-migrate.

How does Omarchy track which migrations have already executed?

The system creates marker files in ~/.local/state/omarchy/migrations/ using the exact filename of the migration script. Each user account maintains its own markers, ensuring per-user execution. The omarchy-migrate runner checks for these markers before executing scripts and creates them immediately after successful completion.

What does it mean for a migration to be idempotent?

An idempotent migration detects whether its intended changes already exist and exits cleanly without performing redundant work. For example, checking [[ -L $symlink ]] before creating a link, or verifying a package is missing before installing it. This prevents duplicate configuration entries and allows safe re-running if a user deletes their marker file.

How do I test migrations without affecting my live Omarchy installation?

Create a sandboxed environment by setting a temporary $HOME variable before executing the script with the same flags used by the runner: TMPHOME=$(mktemp -d); HOME=$TMPHOME bash -euo pipefail migrations/filename.sh. This isolates filesystem changes to a disposable directory while validating logic against the actual Omarchy repository structure referenced via $OMARCHY_PATH.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →