Open-source project
elixir-ecto/postgrex avatar
elixir-ecto/postgrex

Postgrex: binary protocol, no implicit casts, and a type table you can extend

PostgreSQL driver for Elixir

1,211 stars313 forksElixirLicense varies

At a glance

What is it?
Postgrex is the PostgreSQL driver for Elixir, speaking the binary protocol and decoding values into Elixir structs, with a documented type mapping table and an extension mechanism for types it does not know. It deliberately performs no automatic casting, which moves type errors from the database to the caller, and the connection layer comes from DBConnection.
Who is it for?
Postgrex suits an Elixir service that wants the full PostgreSQL type system available to the application, including intervals, ranges, multiranges and tsvector, and that is willing to write an extension for anything the driver does not cover.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 2 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the driver actually is: a protocol layer under a connection pool

Postgrex is described in one line as the PostgreSQL driver for Elixir, with documentation hosted on hexdocs.pm. The feature list is four bullets, and the third one is the architecturally important one. The driver supports transactions, prepared queries and multiple pools via DBConnection, which is a separate library. So the division of labour is that Postgrex handles the wire protocol, the encoding and decoding of values, and the type extensions, while DBConnection handles the process model, the pool of connections and the checkout and return cycle. You start a connection with Postgrex.start_link and pass its pid to each query, which is the shape a supervisor and a supervision tree expect.

A first query: start a connection, then use the bang variant

The example in the README is a short IEx session. You call Postgrex.start_link with a hostname, username, password and database, get an ok tuple containing a pid, and then call Postgrex.query! with a SQL string and a parameter list. The result of a select is a Postgrex.Result struct with the command, an empty? flag, the columns, the rows and the size. An insert returns a result with the command, nil columns, nil rows and a num_rows of 1. The bang form raises on failure rather than returning an error tuple, and the non-bang form returns a tagged tuple, which is the distinction that decides how errors are handled in a supervision tree. The rows come back as plain Elixir lists, so a select returning three rows and two columns gives a list of two element lists, with no mapping step required.

elixir
iex> {:ok, pid} = Postgrex.start_link(hostname: "localhost", username: "postgres", password: "postgres", database: "postgres")
{:ok, #PID<0.69.0>}

iex> Postgrex.query!(pid, "SELECT user_id, text FROM comments", [])
%Postgrex.Result{command: :select, empty?: false, columns: ["user_id", "text"], rows: [[3,"hey"],[4,"there"]], size: 2}

Binary format in both directions, and extensions for everything else

The first feature bullet says the driver does automatic decoding and encoding of Elixir values to and from PostgreSQL's binary format. The second says user defined extensions can be provided for encoding and decoding any PostgreSQL type. Put together, those two lines describe the whole design. The common case costs nothing to set up, and the uncommon case is not a limitation because you can write the encoder and decoder yourself. The documentation is explicit about the scope of the built in support: Postgrex does not automatically cast between types, and the example given is that you cannot pass a string where a date is expected. To add type casting, to support new types, or to change how any type is encoded or decoded, you use extensions. So the driver moves the decision to the application, which is a defensible choice for a library used by a query builder, and a cost for anyone who expected the driver to paper over type mismatches.

The type table is the reference you will actually use

The README carries a full data representation table, and it is the part of the documentation worth printing. The basics are unsurprising: NULL becomes nil, bool becomes true or false, int becomes 42, float becomes 42.0, text becomes a string, and bytea becomes a binary such as <<42>>. The interesting rows are the ones where PostgreSQL has no direct Elixir equivalent. numeric becomes a Decimal struct, linked to the ericmj/decimal library, because binary floating point is not an acceptable representation for money. date, time, timestamp and timestamptz become Date, Time, NaiveDateTime and DateTime structs, with a note that timezones are always normalised to UTC or assumed to be UTC when no information is available. interval becomes a Postgrex.Interval struct with months, days, secs and microsecs. A composite type becomes a tuple, an array becomes a list, a range becomes a Postgrex.Range, a multirange becomes a Postgrex.Multirange, hstore becomes a map, uuid becomes a raw 16 byte binary, and a bit or varbit becomes a bitstring.

Two footnotes in the type table change behaviour in ways that surprise people

Two of the numbered footnotes carry real operational weight. The first is about the Duration form of an interval. The %Duration{} struct may only be used with Elixir 1.17 and above, and an interval will only be decoded into a %Duration{} struct if the option interval_decode_type: Duration is passed to Postgrex.Types.define/3. Even then, the decoded Duration keeps the coarse units PostgreSQL returns, so the time part is stored in :second and is not split into :hour and :minute, and one day two hours three minutes four seconds decodes to %Duration{day: 1, second: 7384}. A team that assumes it receives a decomposed duration will write arithmetic that is wrong by a factor of sixty. The second footnote concerns anonymous composite types, which are decoded as tuples but cannot be encoded back to the database, so a read works and a write of the same struct fails.

JSON is opt in, and the library behind it is configurable

Postgrex comes with JSON support out of the box via the Jason library, and to use it you add :jason to your dependencies with the version constraint ~> 1.0. That is a deliberate choice, because a driver that hard codes a JSON library forces every user onto that library, and JSON encoders differ enough in behaviour and performance that a project may already have a preference. If you want a different one, you set the :json_library configuration for the :postgrex application. The reason this is worth reading carefully is that the JSON library is not a direct dependency of the driver in the usual sense, it is a runtime expectation, so a project that forgets to add it gets a failure at the moment a json column is first touched rather than at compile time.

elixir
{:jason, "~> 1.0"}
elixir
config :postgrex, :json_library, SomeOtherLib

Version support, extensions and where the code lives

The driver supports PostgreSQL 8.4, 9.0 through 9.6 and later, with the caveat that hstore is not supported on 8.4. That range is long, and the hstore exception on the oldest version is the kind of detail that only appears in a compatibility table after someone has already hit it. The repository layout is small and conventional for an Elixir library, with lib/ holding the source, test/ the test suite, mix.exs and mix.lock the build definition, a .formatter.exs for the Elixir formatter, a CHANGELOG.md and the .github/ directory for CI. The README shows a CI badge, and the documentation lives on hexdocs.pm/postgrex rather than in the repository. The listed release tags are v0.5.5 on 2014-08-20, v0.6.0 on 2014-09-07 and v0.7.0 on 2015-01-20, and the last push to the repository was on 2026-10-08, so the tag list and the head of the branch are far apart in time. The licence field in the repository metadata is listed as unknown rather than a named licence, so anyone with a compliance requirement should read the terms from the published package rather than from the repository listing.

Editorial conclusion

Postgrex suits an Elixir service that wants the full PostgreSQL type system available to the application, including intervals, ranges, multiranges and tsvector, and that is willing to write an extension for anything the driver does not cover. It is the wrong choice if you want the driver to coerce a string into a date for you, because the documentation states Postgrex does not automatically cast between types, and a poor fit if you need JSON support without adding a JSON library, since the Jason dependency is opt in through the :json_library configuration. Before adopting it, read the data representation table rather than assuming, because the mapping for intervals changed with Elixir 1.17 and the %Duration{} form only decodes when the interval_decode_type option is passed, and check the licence status of the repository, which the metadata lists as unknown rather than a named licence.

Frequently asked questions

How do I run a query with Postgrex?

Start a connection with Postgrex.start_link passing a hostname, username, password and database, then call Postgrex.query! with the SQL string and a parameter list. A select returns a Postgrex.Result struct with the command, columns, rows and size, and an insert returns a result with num_rows set.

Does Postgrex cast values automatically?

No. The documentation states that Postgrex does not automatically cast between types, giving the example that you cannot pass a string where a date is expected. Type casting, new type support and changes to encoding or decoding are handled with extensions.

How are PostgreSQL types represented in Elixir?

NULL is nil, bool is true or false, int is 42, numeric is a Decimal struct, date and timestamp types are Date, Time, NaiveDateTime and DateTime structs, interval is a Postgrex.Interval struct, arrays are lists, ranges are Postgrex.Range structs, hstore is a map, and composite types are tuples. The full table is in the project documentation.

Do I need to add a JSON library to use Postgrex with JSON columns?

Yes. JSON support comes through the Jason library, so you add {:jason, "~> 1.0"} to your dependencies. You can substitute another library with the config :postgrex, :json_library setting.

Which PostgreSQL versions does Postgrex support?

The feature list states support for PostgreSQL 8.4, 9.0 to 9.6 and later, with the note that hstore is not supported on 8.4. The interval type also has an Elixir version constraint, since %Duration{} may only be used with Elixir 1.17 and above.

Official sources

  1. elixir-ecto/postgrex on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
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/elixir-ecto-postgrex.svg)](https://hysenlabs.com/projects/elixir-ecto-postgrex)

Was this useful?