# Umzug: a migration runner that keeps your ORM optional

> Umzug is a TypeScript migration library for Node that will run against any database you can hand it a context object for. Sequelize is the best supported storage, not a requirement, and the v3 rewrite traded a plain JavaScript API for real typings.

**sequelize/umzug** — Framework agnostic migration tool for Node.js

- Repository: https://github.com/sequelize/umzug
- Stars: 2,214 · Forks: 161
- Language: TypeScript
- License: MIT
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/sequelize-umzug

## What the context option actually lets you swap

Umzug does not know what a database is. It knows how to find migration files in a specified order, how to ask a storage which of those files have already run, and how to call an `up` or `down` function that you wrote. Everything else is the object you hand it as `context`.

The README makes the point explicitly in its minimal example, saying that although the example uses Sequelize, Umzug is not coupled to Sequelize and it is just one of the most commonly used supported storages. Read the `examples/` directory listing and the claim holds up: there is a vanilla example, a vanilla ESM example, a Sequelize TypeScript example, an ES modules example, a raw SQL example, a seeders example, a custom template example, an events example and a bundling codegen example. Nine starting points, most of which have nothing to do with an ORM.

The options themselves are documented as a TypeScript interface rather than in prose. The README points at `src/types.ts` for the `UmzugOptions` interface, which is a reasonable choice for a typed library and a mildly annoying one for a reader who wanted a table.

## Installing and wiring up the smallest possible instance

The install is one line from npm, and there is no separate CLI package to add.

```bash
npm install umzug
```

The minimal example then builds a Sequelize instance against a local SQLite file, points Umzug at a glob of migration files, and passes the query interface as context:

```js
const {Sequelize} = require('sequelize')
const {Umzug, SequelizeStorage} = require('umzug')

const sequelize = new Sequelize({dialect: 'sqlite', storage: './db.sqlite'})

const umzug = new Umzug({
  migrations: {glob: 'migrations/*.js'},
  context: sequelize.getQueryInterface(),
  storage: new SequelizeStorage({sequelize}),
  logger: console,
})

await umzug.up()
```

Two details are worth pausing on. First, `await umzug.up()` at the top level requires the entry file to be treated as a module, and the README's own comment in the TypeScript variant wraps the call in an immediately invoked async function instead. If you are on CommonJS without top-level await, do the same. Second, the comment above the call explains the bookkeeping: a table and Sequelize model called `SequelizeMeta` is created automatically if it does not exist, and parsed to record which migrations have run.

## Writing migrations as up and down pairs

A migration file exports two named async functions. The one in the README creates a `users` table and drops it again:

```js
async function up({context: queryInterface}) {
  await queryInterface.createTable('users', {
    id: {
      type: Sequelize.INTEGER,
      allowNull: false,
      primaryKey: true,
    },
    name: {
      type: Sequelize.STRING,
      allowNull: false,
    },
  })
}

async function down({context: queryInterface}) {
  await queryInterface.dropTable('users')
}

module.exports = {up, down}
```

There is a naming note in the README that is easy to miss and helpful if you are moving from an older version: the `context` argument was renamed to `queryInterface` for clarity, and the object you get is whatever you passed when constructing the instance in the entry file. The destructuring `{context: queryInterface}` in the migration is therefore just a rename at the point of use, and if you pass something that is not a Sequelize query interface, the name is yours to choose.

The TypeScript path is worth knowing about even if you ship JavaScript. The README shows an entry file that requires `ts-node/register` before importing, a glob of `migrations/*.ts`, and a type export pulled off the instance so the `context` argument is typed correctly:

```typescript
export type Migration = typeof umzug._types.migration;

export const up: Migration = ({ context: queryInterface }) => queryInterface.createTable(...)
export const down: Migration = ({ context: queryInterface }) => queryInterface.dropTable(...)
```

That is the payoff of the TypeScript rewrite in two lines: your migration file gets the same editor completion the library has.

## Rolling forward and back with to, step and an explicit list

The run methods take a small options object, and the three ways of narrowing a run are all documented with examples. Naming a target stops there:

```js
await umzug.up({to: '20141101203500-task'})
```

Counting is the other default:

```js
await umzug.up({step: 2})
```

And passing `migrations` runs exactly those files, ignoring order, which is the escape hatch for a specific repair:

```js
await umzug.up({migrations: ['20141101203500-task', '20141101203501-task-2']})
```

The `down` method mirrors all three, and adds one convention worth remembering: passing `0` as `to` reverts everything.

```js
await umzug.down({to: 0})
```

Two inspection methods sit alongside them, `pending()` for files not yet executed and `executed()` for files already run, both returning arrays. In a deployment pipeline those are the pair you want, because you can log the count and the names before touching the database.

The limitation to keep in view is the one the library cannot paper over. A `down` function only reverses what its author recorded. Drop a column in `up` and forget to capture the data, and no amount of orchestration brings it back.

## TypeScript, events and the v3 breaking change

The highlights list on the README is short and worth reading literally: written in TypeScript with built-in typings and auto-completion in your IDE, a programmatic API, a built-in CLI, database agnosticism, logging of the migration process, and multiple storages for migration data.

The migration notes are the part to read carefully. The README opens its documentation section with a note that these docs are for the latest version of Umzug, which has several breaking changes from v2.x, and links to an upgrading section plus a v2.x branch for anyone still on the previous stable version. So there are two substantially different APIs in circulation under the same name, and the version you install decides which one you get. The package manifest currently reads version 3.8.3, matching the v3.8.3 release published on 2026-05-01.

The `examples/` directory also advertises an events example and a bundling codegen example, which tells you two things about where the project is heading. First, the instance is an event emitter, so you can hook into the migration lifecycle rather than only awaiting the final result. Second, there is a `codegen.js` at the top of the repository tree and an example named for bundling with code generation, which suggests attention to shipping Umzug inside bundled serverless deployments where file globbing at runtime is the hard part.

The dependency list is short enough to be worth noting: `emittery`, `tinyglobby` for globbing, `type-fest` and `pony-cause`. Sequelize appears only in devDependencies for the tests, which is the clearest structural evidence that the library is not coupled to it.

## Who this replaces and what it does not do

The comparison most readers arrive with is against `sequelize-cli`, the tool that shipped migrations inside the Sequelize ecosystem. The difference is that a CLI wraps a library, while Umzug is the library with a CLI attached. If your application already constructs its own data layer on startup, you can construct an Umzug instance in the same file and call `up()` before serving traffic, with no extra process and no config file. If you are migrating a codebase that has never used Sequelize, Umzug does not require adopting it.

Other runners exist with different centres of gravity. Umzug's distinguishing detail is that it stores nothing about your schema and keeps no model layer. It is a scheduler for files you wrote. If you want a generator that inspects your models and writes migrations for you, Umzug does not have one, and that is a real gap rather than a preference. The `5-custom-template` and `4-sequelize-seeders` examples suggest the extension points are open, but the package itself ships no scaffolding command.

There is also a documentation boundary. The README stops at the migration file chapter, and everything about storages beyond `SequelizeStorage`, the CLI surface, and the v2 to v3 upgrade path lives outside it. If you are evaluating on storage choice or on how the CLI behaves in CI, read the repository examples and the v2.x branch before deciding; the README will not settle those questions.

## Conclusion

Umzug is the right tool when you want migrations as versioned files with real up and down functions and you have already decided, or will soon decide, that your data layer is not Sequelize. The programmatic API is small enough to read in one sitting, storages cover both the sequelize-table case and the no-database case, and the CLI means migrations can run in a deploy script without booting your application. It is the wrong tool if you want migrations generated for you, because the package has no generator, or if you need automatic rollback of destructive schema changes, because down is a function you wrote yourself and Umzug has no way to recover the SQL you did not record. Read `examples/` before picking a storage, since the folder is where the framework-agnosticism actually shows: vanilla CommonJS, ES modules, raw SQL files, seeders and a code generation setup all live there, and one of them is more likely to match how you deploy than anything the README spells out.

## FAQ

### What is Umzug?

Umzug is a framework-agnostic migration tool for Node.js, written in TypeScript, that provides a programmatic API and a CLI for running and rolling back migration tasks. It supports multiple storages for tracking which migrations have run and keeps logging of the migration process.

### What does "Umzug" mean in English?

Umzug is the German word for a move or relocation, which fits a tool whose job is moving a database schema from one version to the next. It has nothing to do with the package's behaviour beyond the name, and the README never explains it.

### Does Umzug require Sequelize to manage migrations?

No. The README states plainly that although its minimal example uses Sequelize, Umzug is not coupled to it and it is just one of the most commonly used supported storages. You pass whatever object you need as `context` and choose a storage separately, and the examples folder includes vanilla, ES module and raw SQL setups.

### How do you roll back an Umzug migration?

The `down` method reverts migrations, one by default or in batches with `step`. You can revert up to a named migration with `down({to: '20141031080000-task'})`, revert a specific set with `down({migrations: [...]})`, or pass `0` as `to` to revert everything. The reversal itself is the `down` function you wrote in the migration file, so it only undoes what you recorded there.

## Sources

- [Issues](https://github.com/sequelize/umzug/issues)
- [License: MIT](https://github.com/sequelize/umzug/blob/main/LICENSE)
- [README](https://github.com/sequelize/umzug/blob/main/README.md)
- [Releases](https://github.com/sequelize/umzug/releases)
- [sequelize/umzug on GitHub](https://github.com/sequelize/umzug)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sequelize-umzug
