Skip to main content
Version: 4.12.0

Migrating from PostGraphQL v3

Despite the fact that PostGraphile v4 has rewritten the entire GraphQL schema generation from scratch it is still broadly compatible with version 3. This document aims to outline solutions to issues you might face whilst upgrading. If you face issues that you cannot solve, please do reach out!

Short list of breaking changes

These are things that can't be easily solved by just adding a command-line switch or configuration parameter - if these are a problem for you then please get in touch and we'll discuss how one might go about fixing them (please read the below first though!)

  • Per-function returns setof <table> connection names have been removed in favour of a shared connection with the tables themselves - only affects you if you have referenced the type name in queries/fragments
  • What's nullable and what isn't has changed slightly - shouldn't cause an issue in most cases (please read Why is it nullable? for an explanation of why each thing is nullable)
  • JWTs now have an audience of 'postgraphile' rather than 'postgraphql', but you can change this with the --jwt-verify-audience option
  • If you have any tables ending with _input, _patch, Input or Patch they will be renamed (see bottom of this article)

Aside: project financial status

If your business uses PostGraphile, please invest in our Patreon - you'll benefit from faster fixes, more features, and better performance. If you need a more commercially justifiable way of funding the project then please get in touch.


One-to-one backward relations

These are now supported, so the previous one-to-many approximation for them has been deprecated. If you can do so we encourage you to remove the old API via --legacy-relations=omit (or legacyRelations: 'omit' in the library version); however by default we include both relations. If for some reason you don't want the deprecation and you don't want the new behaviour (why?! It's so much better!) then you can enable the old behaviour only via --legacy-relations=only.


create table foo (
id serial primary key

create table bar (
foo_id int not null primary key references foo,
name text
fooById(id: 1) {
# Old interface - deprecated
# Note: this connection can only return at most one result so doesn't
# deserve a connection
barsByFooId {
edges {
node {

# New interface - no more indirection!
barByFooId {

Breakdown of issues you might face

New minimum requirements

Problem: the system fails to start (or even install) because the requirements are not met.

Solution: upgrade your software

  • Node.js v8.6+ required
  • PostgreSQL 9.4+ required (v9.6+ recommended)
  • GraphQL v0.9+ required

Reasoning: Node.js v8 is the current active LTS, v6 will be leaving active LTS status for maintenance mode in April 2018 (next month at time of writing), and supporting 8.6+ as the base level gives us longevity whilst also allowing us to leverage native support for some of the powerful features of ES2017 and ES2018.

Type 'Json' or 'Uuid' is not recognized

Problem: Json and Uuid have been renamed to JSON and UUID respectively

Solution: either modify your queries, or if you cannot do that then use the --legacy-json-uuid command line flag (or legacyJsonUuid: true library option) to change back to the old naming.

Reasoning: purely correctness/aesthetic. Sorry. 😅

Connection orderBy is now an array

Problem: orderBy in connection fields now allows an array of order specifications, so you can order by multiple things (like in SQL). However certain clients might have a problem with this?

Solution: in most cases this should not cause an issue for existing queries - according to 3.1.7 in the GraphQL specification:

If the value passed as an input to a list type is not a list and not the null value, it should be coerced as though the input was a list of size one, where the value passed is the only item in the list.

So any spec-compliant client should not have an issue with this.

Reasoning: people want to sort by multiple columns and since we could do it with a non-breaking change (according to the above) we did!

Expected 'UUID' but received 'String'

Problem: 'UUID' is enabled by default

Solution: raise an issue and we can add a toggle to not enable it by default

Reasoning: named types are helpful

JWTs now have audience 'postgraphile' / issuer 'postgraphile'

Problem: we changed the audience with the rename of the library

Solution: if you can't update your config you can make sure your token objects returned in the DB have aud: 'postgraphql', iss: 'postgraphql' (solution untested - if you used this please let me know whether it worked or not)

Reasoning: we renamed the library, it'd be confusing for new users to reference the old name.

Functions now use table connections where possible (again!)

Problem: functions that return setof a table type now use the same connection class as the tables themselves do (just like PostGraphQL v2 did)

Solution: if this is a problem for you and you're unable to fix the queries, please do get in touch!

Reasoning: performing pagination against multiple sources of the same table type is much more complex if the connection types differ.

Potential future solution: have the connections implement a shared interface.

Issues with nullables

Problem: some things are nullable that weren't, some things aren't nullable that were.

Solution: you can enable the --no-setof-functions-contain-nulls CLI option (or setofFunctionsContainNulls: false library option) to reduce the nullables in the generated schema.


Functions like this can exist:

create function c.badly_behaved_function() returns setof c.person as $$
return query select * from c.person order by id asc limit 1;
return next null;
return query select * from c.person order by id desc limit 1;
$$ language plpgsql stable;

This function returns an array of [{person}, null, {person}] where {person} is a Person object. In PostGraphile because it is a setof function we treat it as a connection. As explained above, we now (like in version 2) use the same connections for functions as for tables - as such these connections now have to support that the entries within them are nullable.

If you don't like the nulls everywhere, I encourage you to use the -N / --no-setof-functions-contain-nulls option mentioned above. Enabling this is a non-breaking change, but disabling it is a breaking change - hence why it is not the default behaviour.

Fields in create mutations now respect defaults

In v3, omitting a field from a create mutation would cause it to be set to NULL, ignoring the column default. We fixed this in v4, now if you want to set the column null you must specify NULL in the mutation (rather than omitting the key).

Query procedures that returns setof <scalar> no longer have pageInfo nor totalCount

Problem: as heading.

Solution: it's possibly to re-introduce support via a plugin - get in touch if you need this

Reasoning: I did not feel it was particularly necessary and I've only got limited time to work on the project...

Watch schema has changed

Note that changes to the watch schema are NOT deemed to be breaking changes.

Problem: watch schema has changed to fix issues with dropping objects

Solution: most people shouldn't be affected by this (though they might want to drop the old watch schema) because --watch is only intended for development use and the new schema should install itself just fine. But if you had to manually install the old watch schema, you'll need to manually install the new one in the same way

Reasoning: the old schema did not detect certain DROP commands and so adding tables, columns, functions, etc. to your schema was fine, but removing them did not result in a refresh.

Other changes that may affect you

These are things that I doubt affect many people (if any) but I want to raise so you can check your own applications.

Introspection query has changed

And will probably continue to change. Changes to the introspection query are not deemed to be breaking changes. The introspection query is now programatically generated so that we can support PG10 and PG11 features.

Field descriptions have changed a little

Field description changes are not deemed to be breaking changes.

Case changing library has changed

We replaced the library but this doesn't affect any of the tests. If this causes you pain please submit an issue so we can add your fields/table names/etc to the tests to ensure this doesn't happen again.

Workaround: you can change the inflection engine back to the old one with a plugin - see makeAddInflectorsPlugin.

Procedures that only supported orderBy: NATURAL now do not have orderBy at all.

Though technically a breaking change, I'm not deeming it as such because I don't understand why you'd explicitly set the orderBy for a field that only has one value and that value is enabled by default.

If this is a problem for you please get in touch and we can add support back via a simple plugin.

Using a return type PostGraphile user can't access

security definer mutations that return a type from a schema that the requesting PostgreSQL user is not allowed to view may now result in permission denied for schema xxxx.

Solution: don't do that 😉

Conflicting tables names - *_input, *_patch

Tables that end in _input or _patch such as foo_input will no longer be exported as FooInput but as FooInputRecord - this prevents collisions with mutation types on tables that share the prefix (e.g. table foo/foos requires FooInput for its create mutation).

Even if they don't clash right now there's a risk that they will clash in future; e.g. if you create the table foo_bar_input then creating FooBarInput and FooBarInputInput and FooBarInputPatch is fine... But if you later add the table foo_bar then FooBarInput will clash. So renaming the tables up front means that whether or not you add that table later it'll still be fine without breaking existing functionality.

You can rename tables directly, or if you prefer not to change your database layout you can use smart comments or write a custom inflector.

Very large numbers

Large integers (over 4 bytes) are still referred to as BigInt, large floats (those beyond IEEE754) are now called BigFloat. The specific boundaries in which these new types kick in may have changed - particularly for DECIMAL/NUMERIC types. DECIMAL/NUMERIC are likely to change in future (so that smaller versions might be represented as int/float rather than BigInt/BigFloat).