Search documentation of Phoenix

# Ecto

View Source

> Requirement: This guide expects that you have gone through the introductory guides and got a Phoenix application up and running.

Most web applications today need some form of data validation and persistence. In the Elixir ecosystem, we have `Ecto` to enable this. Before we jump into building database-backed web features, we're going to focus on the finer details of Ecto to give a solid base to build our web features on top of. Let's get started!

Phoenix uses Ecto to provide builtin support to the following databases:

- PostgreSQL (via `postgrex`)
- MySQL (via `myxql`)
- MSSQL (via `tds`)
- ETS (via `etso`)
- SQLite3 (via `ecto_sqlite3`)

Newly generated Phoenix projects include Ecto with the PostgreSQL adapter by default. You can pass the `--database` option to change or `--no-ecto` flag to exclude this.

Ecto also provides support for other databases and it has many learning resources available. Please check out Ecto's README for general information.

This guide assumes that we have generated our new application with Ecto integration and that we will be using PostgreSQL. The introductory guides cover how to get your first application up and running. For using other databases, see the Swapping Databases how-to guide.

## Using `phx.gen.schema`

Once we have Ecto and PostgreSQL installed and configured, the easiest way to use Ecto is to generate an Ecto schema through the `phx.gen.schema` task. Ecto schemas are a way for us to specify how Elixir data types map to and from external sources, such as database tables. Let's generate a `User` schema with `name`, `email`, `bio`, and `number_of_pets` fields.

    $ mix phx.gen.schema User users name:string email:string \
    bio:string number_of_pets:integer

    * creating ./lib/hello/user.ex
    * creating priv/repo/migrations/20170523151118_create_users.exs

    Remember to update your repository by running migrations:

       $ mix ecto.migrate

A couple of files were generated with this task. First, we have a `user.ex` file, containing our Ecto schema with our schema definition of the fields we passed to the task. Next, a migration file was generated inside `priv/repo/migrations/` which will create our database table that our schema maps to.

With our files in place, let's follow the instructions and run our migration:

    $ mix ecto.migrate
    Compiling 1 file (.ex)
    Generated hello app

    [info] == Running Hello.Repo.Migrations.CreateUsers.change/0 forward

    [info] create table users

    [info] == Migrated in 0.0s

Mix assumes that we are in the development environment unless we tell it otherwise with `MIX_ENV=prod mix ecto.migrate`.

If we log in to our database server, and connect to our `hello_dev` database, we should see our `users` table. Ecto assumes that we want an integer column called `id` as our primary key, so we should see a sequence generated for that as well.

    $ psql -U postgres

    Type "help" for help.

    postgres=# \connect hello_dev
    You are now connected to database "hello_dev" as user "postgres".
    hello_dev=# \d
                    List of relations
     Schema |       Name        |   Type   |  Owner
    --------+-------------------+----------+----------
     public | schema_migrations | table    | postgres
     public | users             | table    | postgres
     public | users_id_seq      | sequence | postgres
    (3 rows)
    hello_dev=# \q

If we take a look at the migration generated by `phx.gen.schema` in `priv/repo/migrations/`, we'll see that it will add the columns we specified. It will also add timestamp columns for `inserted_at` and `updated_at` which come from the `timestamps/1` function.

    defmodule Hello.Repo.Migrations.CreateUsers do
      use Ecto.Migration

      def change do
        create table(:users) do
          add :name, :string
          add :email, :string
          add :bio, :string
          add :number_of_pets, :integer

          timestamps(type: :utc_datetime)
        end
      end
    end

And here's what that translates to in the actual `users` table.

    $ psql
    hello_dev=# \d users
    Table "public.users"
    Column         |            Type                | Modifiers
    ---------------+--------------------------------+----------------------------------------------------
    id             | bigint                         | not null default nextval('users_id_seq'::regclass)
    name           | character varying(255)         |
    email          | character varying(255)         |
    bio            | character varying(255)         |
    number_of_pets | integer                        |
    inserted_at    | timestamp(0) without time zone | not null
    updated_at     | timestamp(0) without time zone | not null
    Indexes:
    "users_pkey" PRIMARY KEY, btree (id)

Notice that we do get an `id` column as our primary key by default, even though it isn't listed as a field in our migration.

## Repo configuration

Our `Hello.Repo` module is the foundation we need to work with databases in a Phoenix application. Phoenix generated it for us in `lib/hello/repo.ex`, and this is what it looks like.

    defmodule Hello.Repo do
      use Ecto.Repo,
        otp_app: :hello,
        adapter: Ecto.Adapters.Postgres
    end

It begins by defining the repository module. Then it configures our `otp_app` name, and the `adapter` – `Postgres`, in our case.

Our repo has three main tasks - to bring in all the common query functions from [`Ecto.Repo`], to set the `otp_app` name equal to our application name, and to configure our database adapter. We'll talk more about how to use `Hello.Repo` in a bit.

When `phx.new` generated our application, it included some basic repository configuration as well. Let's look at `config/dev.exs`.

    ...
    # Configure your database
    config :hello, Hello.Repo,
      username: "postgres",
      password: "postgres",
      hostname: "localhost",
      database: "hello_dev",
      show_sensitive_data_on_connection_error: true,
      pool_size: 10
    ...

We also have similar configuration in `config/test.exs` and `config/runtime.exs` which can also be changed to match your actual credentials.

## The schema

Ecto schemas are responsible for mapping Elixir values to external data sources, as well as mapping external data back into Elixir data structures. We can also define relationships to other schemas in our applications. For example, our `User` schema might have many posts, and each post would belong to a user. Ecto also handles data validation and type casting with changesets, which we'll discuss in a moment.

Here's the `User` schema that Phoenix generated for us.

    defmodule Hello.User do
      use Ecto.Schema
      import Ecto.Changeset

      schema "users" do
        field :bio, :string
        field :email, :string
        field :name, :string
        field :number_of_pets, :integer

        timestamps(type: :utc_datetime)
      end

      @doc false
      def changeset(user, attrs) do
        user
        |> cast(attrs, [:name, :email, :bio, :number_of_pets])
        |> validate_required([:name, :email, :bio, :number_of_pets])
      end
    end

Ecto schemas at their core are simply Elixir structs. Our `schema` block is what tells Ecto how to cast our `%User{}` struct fields to and from the external `users` table. Often, the ability to simply cast data to and from the database isn't enough and extra data validation is required. This is where Ecto changesets come in. Let's dive in!

## Changesets and validations

Changesets define a pipeline of transformations our data needs to undergo before it will be ready for our application to use. These transformations might include type-casting, user input validation, and filtering out any extraneous parameters. Often we'll use changesets to validate user input before writing it to the database. Ecto repositories are also changeset-aware, which allows them not only to refuse invalid data, but also perform the minimal database updates possible by inspecting the changeset to know which fields have changed.

Let's take a closer look at our default changeset function.

    def changeset(user, attrs) do
      user
      |> cast(attrs, [:name, :email, :bio, :number_of_pets])
      |> validate_required([:name, :email, :bio, :number_of_pets])
    end

Right now, we have two transformations in our pipeline. In the first call, we invoke `Ecto.Changeset.cast/3`, passing in our external parameters and marking which fields are required for validation.

`cast/3` first takes a struct, then the parameters (the proposed updates), and then the final field is the list of columns to be updated. `cast/3` also will only take fields that exist in the schema.

Next, `Ecto.Changeset.validate_required/3` checks that this list of fields is present in the changeset that `cast/3` returns. By default with the generator, all fields are required.

We can verify this functionality in `IEx`. Let's fire up our application inside IEx by running `iex -S mix`. In order to minimize typing and make this easier to read, let's alias our `Hello.User` struct.

    $ iex -S mix

    iex> alias Hello.User
    Hello.User

Next, let's build a changeset from our schema with an empty `User` struct, and an empty map of parameters.

    iex> changeset = User.changeset(%User{}, %{})
    #Ecto.Changeset<
      action: nil,
      changes: %{},
      errors: [
        name: {"can't be blank", [validation: :required]},
        email: {"can't be blank", [validation: :required]},
        bio: {"can't be blank", [validation: :required]},
        number_of_pets: {"can't be blank", [validation: :required]}
      ],
      data: #Hello.User<>,
      valid?: false
    >

Once we have a changeset, we can check if it is valid.

    iex> changeset.valid?
    false

Since this one is not valid, we can ask it what the errors are.

    iex> changeset.errors
    [
      name: {"can't be blank", [validation: :required]},
      email: {"can't be blank", [validation: :required]},
      bio: {"can't be blank", [validation: :required]},
      number_of_pets: {"can't be blank", [validation: :required]}
    ]

Now, let's make `number_of_pets` optional. In order to do this, we simply remove it from the list in the `changeset/2` function, in `Hello.User`.

    |> validate_required([:name, :email, :bio])

Now casting the changeset should tell us that only `name`, `email`, and `bio` can't be blank. We can test that by running `recompile()` inside IEx and then rebuilding our changeset.

    iex> recompile()
    Compiling 1 file (.ex)
    :ok

    iex> changeset = User.changeset(%User{}, %{})
    #Ecto.Changeset<
      action: nil,
      changes: %{},
      errors: [
        name: {"can't be blank", [validation: :required]},
        email: {"can't be blank", [validation: :required]},
        bio: {"can't be blank", [validation: :required]}
      ],
      data: #Hello.User<>,
      valid?: false
    >

    iex> changeset.errors
    [
      name: {"can't be blank", [validation: :required]},
      email: {"can't be blank", [validation: :required]},
      bio: {"can't be blank", [validation: :required]}
    ]

What happens if we pass a key-value pair that is neither defined in the schema nor required?

Inside our existing IEx shell, let's create a `params` map with valid values plus an extra `random_key: "random value"`.

    iex> params = %{name: "Joe Example", email: "joe@example.com", bio: "An example to all", number_of_pets: 5, random_key: "random value"}
    %{
      bio: "An example to all",
      email: "joe@example.com",
      name: "Joe Example",
      number_of_pets: 5,
      random_key: "random value"
    }

Next, let's use our new `params` map to create another changeset.

    iex> changeset = User.changeset(%User{}, params)
    #Ecto.Changeset<
      action: nil,
      changes: %{
        bio: "An example to all",
        email: "joe@example.com",
        name: "Joe Example",
        number_of_pets: 5
      },
      errors: [],
      data: #Hello.User<>,
      valid?: true
    >

Our new changeset is valid.

    iex> changeset.valid?
    true

We can also check the changeset's changes - the map we get after all of the transformations are complete.

    iex(9)> changeset.changes
    %{bio: "An example to all", email: "joe@example.com", name: "Joe Example",
      number_of_pets: 5}

Notice that our `random_key` key and `"random_value"` value have been removed from the final changeset. Changesets allow us to cast external data, such as user input on a web form or data from a CSV file into valid data into our system. Invalid parameters will be stripped and bad data that is unable to be cast according to our schema will be highlighted in the changeset errors.

We can validate more than just whether a field is required or not. Let's take a look at some finer-grained validations.

What if we had a requirement that all biographies in our system must be at least two characters long? We can do this easily by adding another transformation to the pipeline in our changeset which validates the length of the `bio` field.

    def changeset(user, attrs) do
      user
      |> cast(attrs, [:name, :email, :bio, :number_of_pets])
      |> validate_required([:name, :email, :bio, :number_of_pets])
      |> validate_length(:bio, min: 2)
    end

Now, if we try to cast data containing a value of `"A"` for our user's `bio`, we should see the failed validation in the changeset's errors.

    iex> recompile()

    iex> changeset = User.changeset(%User{}, %{bio: "A"})

    iex> changeset.errors[:bio]
    {"should be at least %{count} character(s)",
     [count: 2, validation: :length, kind: :min, type: :string]}

If we also have a requirement for the maximum length that a bio can have, we can simply add another validation.

    def changeset(user, attrs) do
      user
      |> cast(attrs, [:name, :email, :bio, :number_of_pets])
      |> validate_required([:name, :email, :bio, :number_of_pets])
      |> validate_length(:bio, min: 2)
      |> validate_length(:bio, max: 140)
    end

Let's say we want to perform at least some rudimentary format validation on the `email` field. All we want to check for is the presence of the `@`. The `Ecto.Changeset.validate_format/3` function is just what we need.

    def changeset(user, attrs) do
      user
      |> cast(attrs, [:name, :email, :bio, :number_of_pets])
      |> validate_required([:name, :email, :bio, :number_of_pets])
      |> validate_length(:bio, min: 2)
      |> validate_length(:bio, max: 140)
      |> validate_format(:email, ~r/@/)
    end

If we try to cast a user with an email of `"example.com"`, we should see an error message like the following:

    iex> recompile()

    iex> changeset = User.changeset(%User{}, %{email: "example.com"})

    iex> changeset.errors[:email]
    {"has invalid format", [validation: :format]}

There are many more validations and transformations we can perform in a changeset. Please see the Ecto Changeset documentation for more information.

## Data persistence

We've explored migrations and schemas, but we haven't yet persisted any of our schemas or changesets. We briefly looked at our repository module in `lib/hello/repo.ex` earlier, and now it's time to put it to use.

Ecto repositories are the interface into a storage system, be it a database like PostgreSQL or an external service like a RESTful API. The `Repo` module's purpose is to take care of the finer details of persistence and data querying for us. As the caller, we only care about fetching and persisting data. The `Repo` module takes care of the underlying database adapter communication, connection pooling, and error translation for database constraint violations.

Let's head back over to IEx with `iex -S mix`, and insert a couple of users into the database.

    iex> alias Hello.{Repo, User}
    [Hello.Repo, Hello.User]

    iex> Repo.insert(%User{email: "user1@example.com"})
    [debug] QUERY OK db=6.5ms queue=0.5ms idle=1358.3ms
    INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user1@example.com", ~U[2021-02-25 01:58:55Z], ~U[2021-02-25 01:58:55Z]]
    {:ok,
     %Hello.User{
       __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
       bio: nil,
       email: "user1@example.com",
       id: 1,
       inserted_at: ~U[2021-02-25 01:58:55Z],
       name: nil,
       number_of_pets: nil,
       updated_at: ~U[2021-02-25 01:58:55Z]
     }}

    iex> Repo.insert(%User{email: "user2@example.com"})
    [debug] QUERY OK db=1.3ms idle=1402.7ms
    INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user2@example.com", ~U[2021-02-25 02:03:28Z], ~U[2021-02-25 02:03:28Z]]
    {:ok,
     %Hello.User{
       __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
       bio: nil,
       email: "user2@example.com",
       id: 2,
       inserted_at: ~U[2021-02-25 02:03:28Z],
       name: nil,
       number_of_pets: nil,
       updated_at: ~U[2021-02-25 02:03:28Z]
     }}

We started by aliasing our `User` and `Repo` modules for easy access. Next, we called `Repo.insert/2` with a User struct. Since we are in the `dev` environment, we can see the debug logs for the query our repository performed when inserting the underlying `%User{}` data. We received a two-element tuple back with `{:ok, %User{}}`, which lets us know the insertion was successful.

We could also insert a user by passing a changeset to `Repo.insert/2`. If the changeset is valid, the repository will use an optimized database query to insert the record, and return a two-element tuple back, as above. If the changeset is not valid, we receive a two-element tuple consisting of `:error` plus the invalid changeset.

With a couple of users inserted, let's fetch them back out of the repo.

    iex> Repo.all(User)
    [debug] QUERY OK source="users" db=5.8ms queue=1.4ms idle=1672.0ms
    SELECT u0."id", u0."bio", u0."email", u0."name", u0."number_of_pets", u0."inserted_at", u0."updated_at" FROM "users" AS u0 []
    [
      %Hello.User{
        __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
        bio: nil,
        email: "user1@example.com",
        id: 1,
        inserted_at: ~U[2021-02-25 01:58:55Z],
        name: nil,
        number_of_pets: nil,
        updated_at: ~U[2021-02-25 01:58:55Z]
      },
      %Hello.User{
        __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
        bio: nil,
        email: "user2@example.com",
        id: 2,
        inserted_at: ~U[2021-02-25 02:03:28Z],
        name: nil,
        number_of_pets: nil,
        updated_at: ~U[2021-02-25 02:03:28Z]
      }
    ]

That was easy! `Repo.all/1` takes a data source, our `User` schema in this case, and translates that to an underlying SQL query against our database. After it fetches the data, the Repo then uses our Ecto schema to map the database values back into Elixir data structures according to our `User` schema. We're not just limited to basic querying – Ecto includes a full-fledged query DSL for advanced SQL generation. In addition to a natural Elixir DSL, Ecto's query engine gives us multiple great features, such as SQL injection protection and compile-time optimization of queries. Let's try it out.

    iex> import Ecto.Query
    Ecto.Query

    iex> Repo.all(from u in User, select: u.email)
    [debug] QUERY OK source="users" db=0.8ms queue=0.9ms idle=1634.0ms
    SELECT u0."email" FROM "users" AS u0 []
    ["user1@example.com", "user2@example.com"]

First, we imported `Ecto.Query`, which imports the `from/2` macro of Ecto's Query DSL. Next, we built a query which selects all the email addresses in our users table. Let's try another example.

    iex> Repo.one(from u in User, where: ilike(u.email, "%1%"),
                                   select: count(u.id))
    [debug] QUERY OK source="users" db=1.6ms SELECT count(u0."id") FROM "users" AS u0 WHERE (u0."email" ILIKE '%1%') []
    1

Now we're starting to get a taste of Ecto's rich querying capabilities. We used `Repo.one/2` to fetch the count of all users with an email address containing `1`, and received the expected count in return. This just scratches the surface of Ecto's query interface, and much more is supported such as sub-querying, interval queries, and advanced select statements. For example, let's build a query to fetch a map of all user id's to their email addresses.

    iex> Repo.all(from u in User, select: %{u.id => u.email})
    [debug] QUERY OK source="users" db=0.9ms
    SELECT u0."id", u0."email" FROM "users" AS u0 []
    [
      %{1 => "user1@example.com"},
      %{2 => "user2@example.com"}
    ]

That little query packed a big punch. It both fetched all user emails from the database and efficiently built a map of the results in one go. You should browse the Ecto.Query documentation to see the breadth of supported query features.

In addition to inserts, we can also perform updates and deletes with `Repo.update/2` and `Repo.delete/2` to update or delete a single schema. Ecto also supports bulk persistence with the `Repo.insert_all/3`, `Repo.update_all/3`, and `Repo.delete_all/2` functions.

There is quite a bit more that Ecto can do and we've only barely scratched the surface. With a solid Ecto foundation in place, we're now ready to continue building our app and integrate the web-facing application with our backend persistence. Along the way, we'll expand our Ecto knowledge and learn how to properly isolate our web interface from the underlying details of our system. Please take a look at the Ecto documentation for the rest of the story.

In our Data modelling guides, we'll find out how to wrap up our Ecto access and business logic behind modules that group related functionality. We'll see how Phoenix helps us design maintainable applications, and we'll find out about other neat Ecto features along the way.

## Mix tasks

Ecto comes with a collection of Mix tasks to make it easier to manage your database and your application. Here is a quick look into the most important ones.

### `mix ecto.create`

This task will create the database specified by our application repositories, but we can pass in another repo if we want.

Here's what it looks like in action.

    $ mix ecto.create
    The database for Hello.Repo has been created.

There are a few things that can go wrong with `ecto.create`. If our Postgres database doesn't have a "postgres" role (user), we'll get an error like this one.

    $ mix ecto.create
    ** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  role "postgres" does not exist

We can fix this by creating the "postgres" role in the `psql` console with the permissions needed to log in and create a database.

    =# CREATE ROLE postgres LOGIN CREATEDB;
    CREATE ROLE

If the "postgres" role does not have permission to log in to the application, we'll get this error.

    $ mix ecto.create
    ** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  role "postgres" is not permitted to log in

To fix this, we need to change the permissions on our "postgres" user to allow login.

    =# ALTER ROLE postgres LOGIN;
    ALTER ROLE

If the "postgres" role does not have permission to create a database, we'll get this error.

    $ mix ecto.create
    ** (Mix) The database for Hello.Repo couldn't be created, reason given: ERROR:  permission denied to create database

To fix this, we need to change the permissions on our "postgres" user in the `psql` console to allow database creation.

    =# ALTER ROLE postgres CREATEDB;
    ALTER ROLE

If the "postgres" role is using a password different from the default "postgres", we'll get this error.

    $ mix ecto.create
    ** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  password authentication failed for user "postgres"

To fix this, we can change the password in the environment specific configuration file. For the development environment the password used can be found at the bottom of the `config/dev.exs` file.

Finally, if we happen to have another repo called `OurCustom.Repo` that we want to create the database for, we can run this.

    $ mix ecto.create -r OurCustom.Repo
    The database for OurCustom.Repo has been created.

### `mix ecto.drop`

This task will drop the database specified in our repo. By default it will look for the repo named after our application (the one generated with our app unless we opted out of Ecto). It will not prompt us to check if we're sure we want to drop the database, so do exercise caution.

    $ mix ecto.drop
    The database for Hello.Repo has been dropped.

### `mix ecto.gen.migration`

Migrations are a programmatic, repeatable way to affect changes to a database schema. Phoenix generators take care of generating migrations for us whenever we create a new context or schema, but if you want to generate a migration from scratch, `mix ecto.gen.migration` has our back. Let's see an example.

We simply need to invoke the task with a `snake_case` version of the module name that we want. Preferably, the name will describe what we want the migration to do.

    $ mix ecto.gen.migration add_comments_table
    * creating priv/repo/migrations
    * creating priv/repo/migrations/20150318001628_add_comments_table.exs

Notice that the migration's filename begins with a string representation of the date and time the file was created.

Let's take a look at the file `ecto.gen.migration` has generated for us at `priv/repo/migrations/20150318001628_add_comments_table.exs`.

    defmodule Hello.Repo.Migrations.AddCommentsTable do
      use Ecto.Migration

      def change do
      end
    end

Notice that there is a single function `change/0` which will handle both forward migrations and rollbacks. We'll define the schema changes that we want using Ecto's handy DSL, and Ecto will figure out what to do depending on whether we are rolling forward or rolling back. Very nice indeed.

What we want to do is create a `comments` table with a `body` column, a `word_count` column, and timestamp columns for `inserted_at` and `updated_at`.

    ...
    def change do
      create table(:comments) do
        add :body, :string
        add :word_count, :integer

        timestamps(type: :utc_datetime)
      end
    end
    ...

For more information on how to modify your database schema please refer to the Ecto's migration DSL docs. For example, to alter an existing schema see the documentation on Ecto’s `alter/2` function.

That's it! We're ready to run our migration.

### `mix ecto.migrate`

Once we have our migration module ready, we can simply run `mix ecto.migrate` to have our changes applied to the database. We have already used it earlier in this chapter, but let's take it for a spin once more for our newly generated migration.

    $ mix ecto.migrate
    [info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward
    [info] create table comments
    [info] == Migrated in 0.1s

When we first run `ecto.migrate`, it will create a table for us called `schema_migrations`. This will keep track of all the migrations which we run by storing the timestamp portion of the migration's filename.

Here's what the `schema_migrations` table looks like.

    hello_dev=# select * from schema_migrations;
    version        |     inserted_at
    ---------------+---------------------
    20250317170448 | 2025-03-17 21:07:26
    20250318001628 | 2025-03-18 01:45:00
    (2 rows)

When we roll back a migration, `mix ecto.rollback`, to be discussed next, we will remove the record representing this migration from `schema_migrations`.

By default, `ecto.migrate` will execute all pending migrations. We can exercise more control over which migrations we run by specifying some options when we run the task.

We can specify the number of pending migrations we would like to run with the `-n` or `--step` options.

    $ mix ecto.migrate -n 2
    [info] == Running Hello.Repo.Migrations.CreatePost.change/0 forward
    [info] create table posts
    [info] == Migrated in 0.0s
    [info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward
    [info] create table comments
    [info] == Migrated in 0.0s

The `--step` option will behave the same way.

    $ mix ecto.migrate --step 2

The `--to` option will run all migrations up to and including given version.

    $ mix ecto.migrate --to 20150317170448

### `mix ecto.rollback`

The `mix ecto.rollback` task will reverse the last migration we have run, undoing the schema changes. `ecto.migrate` and `ecto.rollback` are mirror images of each other.

    $ mix ecto.rollback
    [info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 backward
    [info] drop table comments
    [info] == Migrated in 0.0s

`ecto.rollback` will handle the same options as `ecto.migrate`, so `-n`, `--step`, `-v`, and `--to` will behave as they do for `ecto.migrate`.

← Previous Page Components and HEEx
Next Page → JSON and APIs