You can in fact use schemas in migrations

I saw well-intended recommendations not to use schemas in migrations lately. Although the advice of switching to raw SQL is a good one, we don’t have to give up on schemas entirely.

Why do people leave schemas in the first place?

If you reference your application schemas like MyApp.Accounts.User and MyApp.Accounting.Invoice in your migrations, things work only until they don’t. Why? When the migration is running, it has to work with the final schema of your application. But migrations change that schema all the time! So there will be a mismatch, and you’ll have to rewrite your old migrations.

What people recommend instead?

A safe way is to resort to pure SQL or call the Repo module without referencing schemas. That’s a fool-proof way, and I don’t disagree.

So what are you trying to say?

I see this kind of advice missing the real point. It’s never been about schemas.

Define and use schemas as much as you want in migrations; just make sure they are relevant for the specific change only:

defmodule MyApp.Repo.Migrations.AddSubdivisionsToCountries do
  use Ecto.Migration

  alias MyApp.Repo

  defmodule Country do
    use Ecto.Schema

    schema "countries" do
      field(:iso, :string)

  defmodule CountrySubdivision do
    use Ecto.Schema

    schema "country_subdivisions" do
      belongs_to(:country, Country)

  def change do
    countries = Repo.all(Country)

    for country <- countries do
      Repo.insert!(%CountrySubdivision{country_id:, ...})

The real key is to define your schemas representing the schema at the time of the migration being written.

If you want to use schemas for convenience, copy the relevant application schemas over to the migration and ideally make them as minimal as possible (keeping only the mapping you need).


Schemas are just a tool. You can use them in your Ecto migrations all you want as long as you are not referencing your current application schemas.


I wrote a complete guide on web application deployment. Ruby with Puma, Python with Gunicorn, NGINX, PostgreSQL, Redis, networking, processes, systemd, backups, and all your usual suspects.

More →