question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Upcoming changes to the GraphQL Engine, API, and Fields

See original GitHub issue

The purpose of this issue is to surface some work we’re doing on the internals of Keystone Next in preparation for its launch as Keystone 6.

Now that we’ve completed our transition to Prisma and flattened out a lot of the internal complexity related to supporting multiple database adapters, we are re-working how Fields work internally to make them more streamlined and consistent.

Because of how Fields are at the heart of Keystone, this also effectively means re-working (and simplifying, and making more consistent) how we generate Keystone’s GraphQL Schema.

We’re also going to be making some breaking changes to the GraphQL API that Keystone generates - for very good reasons, but this is a big deal, and we’ve held off doing anything like this for years.

So it’s important for us to start broadcasting what’s coming, why, and how to manage the transition. Here’s what’s happening:

1. We’re building a new Field Type system that will dramatically simplify fields

Right now, many of Keystone’s internals work by coincidence, by design. This means that you have to express the filters a field supports; then implement those same filters in the query resolver. You have to express the GraphQL schema a field implements; then write input and output resolvers that implement behaviour to handle those same properties.

Here’s an example of what I mean:

  gqlOutputFields() {
    return [`${this.path}: ${this.getTypeName()}`];
  }
  gqlOutputFieldResolvers() {
    return { [`${this.path}`]: (item: Record<P, any>) => item[this.path] };
  }

That’s a small example; many fields are much more complex, which has a cost in consistency and effort required to ship new features. We also have a lot of magic for dealing with Relationships, or things like the _supportsUnique getter. This is all going away.

In the new system, you’ll only have to express the structure of a Field (similar to how you’d define a type in a type system) and Keystone itself will do the implementation for you.

Here’s a (current) example of what this looks like: the new implementation of a text field

export const text = <TGeneratedListTypes extends BaseGeneratedListTypes>({
  index,
  ...config
}: TextFieldConfig<TGeneratedListTypes> = {}): FieldTypeFunc => () =>
  fieldType({ kind: 'scalar', mode: 'optional', scalar: 'String', index })({
    ...config,
    input: {
      uniqueWhere: index === 'unique' ? { arg: types.arg({ type: types.String }) } : undefined,
      create: { arg: types.arg({ type: types.String }) },
      update: { arg: types.arg({ type: types.String }) },
      orderBy: { arg: types.arg({ type: sortDirectionEnum }) },
    },
    output: types.field({
      type: types.String,
    }),
    views: resolveView('text/views'),
    getAdminMeta() {
      return { displayMode: config.ui?.displayMode ?? 'input' };
    },
  });

If you’re wondering “hang on, where’s the rest?” that’s the point! I didn’t include the type definition for the config, but that’s the whole implementation right there.

It even implements a new feature: a uniqueWhere filter if the index on the field is unique!

2. We’re breaking / improving the public GraphQL Schema that Keystone generates

OK, this is the big one. A consequence of the changes to Fields is that we have to nest everything about a Field - the input and output values, and the filters - under a single key, which is the field’s path.

For output types, this is fine, and we won’t change the schema for any of the fields already in Keystone Next. The input types for most fields will stay the same as well. But we will be changing the filtering syntax and that will need to be updated in front-end apps built against a Keystone Next back-end.

Our hope is that this change isn’t a hard one to keep up with, and we’re not changing the actual functionality. On the up-side, this will make the GraphQL Schemas Keystone generates much smaller and easier to document and use.

Basically, a query previously written like this:

query {
  allUsers(where: { name_eq: 'Jed' }) { id }
}

… will after this update look like this:

query {
  allUsers(where: { name: { eq: 'Jed' } }) { id }
}

Since this is the first breaking change for Keystone’s public GraphQL API in years, we’re going to clean up a couple of other things that are inconsistent or should be better.

For example, at the moment we have this weird meta query for when you want to count items in a list:

query {
  _allUsersMeta(where: { name_startsWith: 'J' }) { count }
}

Confusingly, this accepts all the same input arguments as the main allUsers query, including arguments like skip which don’t make sense for getting a count. After this change, there’ll be a new query specific to getting a count, which only accepts the input types that make sense:

query {
  countUsers(where: { name: { startsWith: 'J' } }) { count }
}

… and we may add others for aggregation, grouping, etc. over time.

We’re genuinely sorry for the work this will create on the front-end of apps built on Keystone Next, but we have to rip the bandaid off and can only promise that the long term benefits of cleaning all this up will be worth it for everyone.

You can expect that, after this change lands, and especially after we close this “next” phase and launch it as Keystone 6, we’re done making breaking changes and the APIs that Keystone generates will be stable for a long time going forward.

Full documentation about how to adapt to these changes will be published when we’ve finalised them, and you’ll also see the diffs in the schemas that are generated when you upgrade.

3. We’re going to be adding a lot more functionality to the built-in fields

Through the Keystone Next transition, we’ve been building on the core GraphQL engine and Field Types that Keystone 5 had. Because it’s been challenging to make deep changes to that system, we’ve focused on rebuilding (all the) other parts of Keystone first; with our new fields system, a lot of improvements we’ve wanted to make are now on the table.

High on our list are improving how null values are handled, and options for controlling that on a per-field level; as well as better handling of required fields, validation, and passing configuration like @map through to the Prisma schema for custom table and column names in the database.

This will also include surfacing better error and validation messages, improving things like showing whether fields are required or invalid in the Admin UI, etc.

As in the text example above, we’ll also be adding support for unique fields to be used as selectors (so you can query or mutate a post by its slug, or a use by their email, etc) and features like multi-column indexes in the database.


As mentioned at the top of the issue, getting our Fields and GraphQL engine refactored (followed by some cleanup and settling-in) is the last major thing on our list for releasing Keystone Next as Keystone 6.

More than anything, we’re excited to get this done so we can switch our focus from internal changes we’ve needed to make for ages, to iterating more quickly on user-facing (and developer-facing) improvements to Keystone! And while we don’t take lightly the fact that we’re making breaking changes with this update, many features we’ve been wanting to build will go from “too hard” to “very doable” with this change.

As an aside, custom fields have always been one of the most powerful features of Keystone, but with the current infrastructure they’re a bit too hard to build; we’re hoping that with this new system, they’ll become much more accessible for everyone, and more useful as a result!

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:16
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
VinayaSathyanarayanacommented, May 16, 2021

Will the GraphQL API generated by Keystone 6 be compatible with other open source tools / frameworks like

Assume it will be compatible with JsonLogic

1reaction
MurzNNcommented, May 7, 2021

Great improvements! Can you also take into consideration my PR https://github.com/keystonejs/keystone/pull/4826 that allow pass the exact idField value when creating items? This is very popular feature when we must control value of creating id field on frontend. Especially when we importing data from some other external database.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Updating to Hasura GraphQL engine v2
Updating to Hasura GraphQL engine v2. ... A detailed changelog with all the new features introduced in Hasura v2 is available on the...
Read more >
Hasura Announces Updates to its GraphQL Engine
Hasura has provided updates to its GraphQL engine that are aimed at simplifying data access across disparate sources. These updates include ...
Read more >
GraphQL | A query language for your API
Add new fields and types to your GraphQL API without impacting existing queries. Aging fields can be deprecated and hidden from tools.
Read more >
API Changes | Contentful
Starting with Monday, August 29th 2022, when the schema generation fails, our GraphQL API returns a 422 HTTP status code instead of a...
Read more >
Latest News - Keystone 6 Documentation - KeystoneJS
After months of work deep in the codebase, Keystone 6 now has a new core. This unblocks a bunch of roadmap features like...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found