Library / SDK
phoenixframework/phoenix_ecto avatar
phoenixframework/phoenix_ecto

phoenix_ecto: the protocol glue between Phoenix and Ecto

Phoenix and Ecto integration with support for concurrent acceptance testing

580 stars127 forksElixirMIT

At a glance

What is it?
phoenix_ecto is the small integration library that lets a Phoenix application treat Ecto changesets as form data, render Decimal values as safe HTML, and turn Ecto exceptions into Plug exceptions, and it adds a plug that lets headless browser acceptance tests run against the Ecto SQL sandbox concurrently. It is MIT licensed and depends on PostgreSQL for the sandbox part.
Who is it for?
phoenix_ecto fits a Phoenix application that needs changeset forms and Decimal rendering to work without hand written glue, and especially a team running browser driven acceptance tests that want the Ecto SQL sandbox so those tests can run in parallel. It does not fit an application on a database other than PostgreSQL, since the sandbox plug depends on it, and it does not replace a browser driver, which you still have to choose.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 38 days ago.
What is it written in?
Mainly Elixir, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the library actually provides

phoenix_ecto is a project that integrates Phoenix with Ecto, implementing all relevant protocols.

The mechanism is Elixir's protocol dispatch. The integration between the two frameworks is, in the documentation's phrasing, simply a matter of implementing a handful of protocols, and the library provides three implementations.

The Phoenix.HTML.FormData protocol is implemented for Ecto.Changeset, which is what makes a changeset usable as form data in a Phoenix form without you writing a conversion by hand.

The Phoenix.HTML.Safe protocol is implemented for Decimal, so a Decimal value in a template is rendered as safe HTML rather than needing an explicit conversion.

The Plug.Exception protocol is implemented for the relevant Ecto exceptions, so an Ecto error raised during a request surfaces through the Plug error handling path instead of bubbling up as an unrelated exception.

That last one is the most operationally interesting, because it also has a configuration knob. The Plug.Exception implementations for Ecto exceptions may be disabled by including the error in the mix configuration, which is useful when you want a specific error handled by your own code path rather than by the default Plug rendering.

The users are Phoenix developers whose applications use Ecto, and the second half of the library, the sandbox plug, is aimed at teams running browser driven acceptance tests.

Installing the dependency and the protocol implementations in use

Installation is a dependency line in mix.exs:

elixir
def deps do
  [{:phoenix_ecto, "~> 4.0"}]
end

The version requirement of 4.0 in the major line tells you the shape of the library, since the sandbox plug and the protocol implementations are the whole of it, and there is nothing else to configure at the application level.

The only configuration knob is for the exception handling. To keep a specific Ecto error out of the Plug exception path, name it in the mix configuration:

elixir
config :phoenix_ecto,
  exclude_ecto_exceptions_from_plug: [Ecto.NoResultsError]

The repository itself is minimal, which is consistent with a protocol library. There is a lib/ directory with the implementations, a test/ directory, a mix.exs and mix.lock, a .formatter.exs, a CHANGELOG.md, a LICENSE and a .github/ directory with the CI workflow. The CI badge in the documentation points at a workflow named CI.

The project is MIT licensed, and the copyright line reads 2015, Chris McCord. That date is worth noticing: this is a small, long-lived library whose API surface is the protocol implementations, and a change to those implementations shows up as a change in how your forms render rather than as an obvious upgrade to review.

The sandbox plug that makes concurrent browser tests possible

The second half of the library is a plug called Phoenix.Ecto.SQL.Sandbox, and it exists so that acceptance tests powered by headless browsers such as ChromeDriver and Selenium can run concurrently.

The problem it solves is a database one. A browser test drives a real browser against your application, and the application writes to the database. If the test suite shares one database connection, two tests running at once will see each other's data. The Ecto SQL sandbox solves this by giving each test its own transaction that is rolled back at the end, and the plug in this library makes that work with a browser session rather than a plain process.

The requirement is specific: to enable concurrent acceptance tests, make sure you are using PostgreSQL.

The setup has two steps. First, set a flag to enable the sandbox in config/test.exs:

elixir
    config :your_app, sql_sandbox: true

Second, use the flag to conditionally add the plug in lib/your_app/endpoint.ex:

elixir
    if Application.get_env(:your_app, :sql_sandbox) do
      plug Phoenix.Ecto.SQL.Sandbox
    end

The ordering constraint is the part that bites. The documentation is explicit that this must be placed before the line plug YourApp.Router, or any other plug that may access the database. A plug that opens a sandbox connection has to do it before anything else can touch the database, and getting it wrong produces a connection that is not sandboxed and flaky parallel tests rather than a clear error.

If you are not familiar with Ecto's SQL sandbox, the documentation recommends getting acquainted with it by reading the Ecto.Adapters.SQL.Sandbox documentation first, since the plug here is a thin layer over a mechanism you need to understand.

Running the sandbox with Hound

Once the plug is in place, you check out a sandboxed connection and pass the connection information to an acceptance testing tool. The documentation names Hound and Wallaby as the two supported tools.

For Hound, the dependency goes in mix.exs:

elixir
{:hound, "~> 1.0"}

And it has to be started at the top of your test/test_helper.exs:

elixir
{:ok, _} = Application.ensure_all_started(:hound)

Then the test case or case template needs a setup block that starts the sandbox owner, registers a cleanup, and hands the session the metadata:

elixir
use Hound.Helpers

setup tags do
  pid = Ecto.Adapters.SQL.Sandbox.start_owner!(YourApp.Repo, shared: not tags[:async])
  on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
  metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(YourApp.Repo, pid)
  Hound.start_session(metadata: metadata)
  :ok
end

The metadata_for call is the piece of this library doing the work. The browser session needs to be told which sandboxed connection to talk to, and the metadata carries that information, so a request the browser makes lands in the test's transaction rather than in the shared database.

The shared option is worth understanding, because it is where the concurrency is negotiated. It is set to not tags[:async], so a test tagged async gets its own isolated connection, while a test that is not async asks for a shared one. That is the standard Ecto pattern for tests that cannot run in parallel, such as ones that change application configuration.

There is one driver limitation to know. Hound supports multiple drivers like Chrome and Firefox, but it does not support concurrent tests under PhantomJS, which is the default. If you leave the default driver in place, the concurrency this whole mechanism exists for will not happen.

Running the sandbox with Wallaby

Wallaby takes a slightly different route, because it can set the Ecto Sandbox up for you if you use Wallaby.Feature in your test module.

The dependency:

elixir
{:wallaby, "~> 0.25", only: :test}

And a feature module:

elixir
defmodule MyAppWeb.PageFeature do
  use ExUnit.Case, async: true
  use Wallaby.Feature

  feature "shows some text", %{session: session} do
    session
    |> visit("/home")
    |> assert_text("Hello world!")
  end
end

Note the async: true on the ExUnit case. That is the whole reason for the sandbox plug, because a case that is not async runs on its own and cannot be parallelised with the others.

If you do not use Wallaby.Feature, you can set the sandbox up by hand in your test case or case template:

elixir
use Wallaby.DSL

setup tags do
  pid = Ecto.Adapters.SQL.Sandbox.start_owner!(YourApp.Repo, shared: not tags[:async])
  on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
  metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(YourApp.Repo, pid)
  {:ok, session} = Wallaby.start_session(metadata: metadata)
end

The shape is the same as the Hound version, with the difference that Wallaby.start_session takes the metadata and returns the session in a match, while Hound.start_session returns :ok and the session is held by the Hound helpers.

On driver coverage, Wallaby currently supports ChromeDriver and Selenium, allowing testing in almost any browser. That is a wider driver matrix than Hound as described here, and it is the practical tie-breaker if your test suite has to run against a browser that is not Chrome or Firefox.

Where phoenix_ecto is the wrong choice

The first limit is the PostgreSQL requirement. The concurrent acceptance test capability depends on the Ecto SQL sandbox, and the documentation states plainly that to enable concurrent acceptance tests you must be using PostgreSQL. On another database the protocol implementations still work, but the part of the library that people actually reach for is not available.

The second is that this is a glue library, not a framework. It implements three protocols and provides one plug. If your application has a form layer that already handles changesets, or a templating helper that already renders Decimal, the protocol implementations are conveniences rather than architecture. The value scales with how much of your Phoenix surface touches Ecto types directly.

The third is the driver dependency, which is easy to overlook. Neither Hound nor Wallaby is included, and both bring their own Selenium or ChromeDriver setup into your test environment. The library makes concurrent database isolation work, and the hard part of browser testing, which is a reliable driver and a stable environment, is still yours.

The fourth is a subtlety in the shared mode. When a test is not tagged async, the sandbox is started in shared mode, and the documentation ties that to the standard Ecto convention for tests that must not run in parallel. A suite that mixes async and non-async cases will have its non-async cases serialised, and a suite where nobody tagged anything async will see no benefit at all from having installed this library.

Compare that with the alternative of testing the Ecto layer without a browser. The difference in approach is that a plain Ecto test exercises your queries and changesets directly and runs fast, while a browser test exercises the whole stack including the plug, the router and the templates. The sandbox plug buys you the second, and the cost is a browser in your test loop plus the ordering constraint on your endpoint.

Licence, project state and the checklist before you wire it in

The project is released under the MIT License, and the documentation links the licence file in the repository. The copyright is 2015, Chris McCord, which is a useful signal about the maturity of the API, since a protocol implementation that has not changed shape in a decade is a stable thing to depend on. The MIT terms permit use, modification and redistribution including inside a proprietary application, provided the copyright notice and licence text are kept with copies, and you should read the LICENSE file yourself for the exact wording.

The repository is not archived, and the last push was on 2026-09-15, so the tree is about five weeks old relative to today. It publishes no GitHub releases, so the version history lives in the CHANGELOG.md and in the mix.exs requirement rather than in tags.

The checklist before you wire it into a project is short. Confirm your database is PostgreSQL, because the concurrent test story does not exist otherwise. Set config :your_app, sql_sandbox: true in config/test.exs and add the plug conditionally in your endpoint, placing it before plug YourApp.Router and before any other plug that touches the database. Then decide the driver: Wallaby for the wider ChromeDriver and Selenium coverage, Hound if you are on Chrome or Firefox and are happy to move off the PhantomJS default.

One last practical point from the documentation. If you are unfamiliar with the Ecto SQL sandbox, read the Ecto.Adapters.SQL.Sandbox documentation first. This library is a thin adapter over that mechanism, and the debugging you will do when a concurrent test fails will be in the sandbox, not here.

Editorial conclusion

phoenix_ecto fits a Phoenix application that needs changeset forms and Decimal rendering to work without hand written glue, and especially a team running browser driven acceptance tests that want the Ecto SQL sandbox so those tests can run in parallel. It does not fit an application on a database other than PostgreSQL, since the sandbox plug depends on it, and it does not replace a browser driver, which you still have to choose. Before you adopt it, confirm your database is PostgreSQL, place the sandbox plug before the router plug in your endpoint, and read the changelog, since the release history in this repository is not versioned by GitHub releases.

Frequently asked questions

What does phoenix_ecto provide?

It integrates Phoenix with Ecto by implementing three protocols: Phoenix.HTML.FormData for Ecto.Changeset, Phoenix.HTML.Safe for Decimal, and Plug.Exception for the relevant Ecto exceptions. It also provides the Phoenix.Ecto.SQL.Sandbox plug for concurrent browser driven acceptance tests.

How do I add phoenix_ecto to a Phoenix project?

Add {:phoenix_ecto, "~> 4.0"} to the dependencies list in mix.exs. The only other configuration is the exception exclusion option, config :phoenix_ecto, exclude_ecto_exceptions_from_plug: [Ecto.NoResultsError], which keeps a named Ecto error out of the Plug exception path.

How do I enable concurrent acceptance tests with phoenix_ecto?

You need PostgreSQL. Set config :your_app, sql_sandbox: true in config/test.exs, then add plug Phoenix.Ecto.SQL.Sandbox conditionally in lib/your_app/endpoint.ex, placed before plug YourApp.Router and any other plug that may access the database. Then check out a sandboxed connection and pass the metadata to Hound or Wallaby.

Which browser test tool does phoenix_ecto work with?

Both Hound and Wallaby. Hound supports multiple drivers like Chrome and Firefox but does not support concurrent tests under PhantomJS, its default. Wallaby currently supports ChromeDriver and Selenium, allowing testing in almost any browser, and it can set up the Ecto Sandbox for you when you use Wallaby.Feature.

What licence does phoenix_ecto use?

The project is licensed under the MIT License, with the terms in the LICENSE file at the repository root, and the copyright is 2015, Chris McCord. The repository is not archived, the last push was on 2026-09-15, and it publishes no GitHub releases.

Official sources

  1. Issues
  2. License: MIT
  3. phoenixframework/phoenix_ecto on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/phoenixframework-phoenix-ecto.svg)](https://hysenlabs.com/projects/phoenixframework-phoenix-ecto)

Was this useful?