Skip to Page NavigationSkip to Page NavigationSkip to Content
Keystone 6 is in Community Preview! For Keystone 5 docs visit v5.keystonejs.com

Release: 5th October 2021

We're nearly at Gold Master status for the Keystone 6 GA release! Here's what's new:

  • Fields Overhaul with lots of tweaks and additions 🚀
  • Hook Updates bringing consolidation and clearer naming 🪝
  • Removal of unused return types and unused values 🚫
  • Renaming of options for consistency 📛
  • Apollo Server Upgrade to version 3 👩‍🚀
  • Improved Error Messaging 📟
  • Performance updates for a faster Admin UI 🏃‍♀️
  • REST API Example using the new createContext inside extendExpressApp 👩‍🏫
  • Other Notable Changes to be aware of 🛎️
  • Prisma Update from 2.x to 3.x 🗃

We’ve got further improvements to error messaging and performance to come in the next release!

This release contains breaking changes, please see below!

"@keystone-next/auth": "33.0.0",
"@keystone-next/cloudinary": "8.0.0",
"@keystone-next/fields-document": "10.0.0",
"@keystone-next/keystone": "26.0.1",
"@keystone-next/session-store-redis": "5.0.0",

Upgrade Guide 👇

Be sure to read the entire release notes below for everything you need to know about this new release.

The main things to keep in mind are:

  • defaultValue config is now static, if you have dynamic defaults, use the resolveInput hook
  • isRequired for fields is now validation: { isRequired } and we have new validation options such as min and max for some fields
  • We've made it clearer which fields are nullable in the database and tweaked the defaults, you now have more control but may need to migrate your database (more details below)
  • The hooks API has new arguments, and we’ve consolidated update and delete events into beforeOperation and afterOperation
  • context.lists has been renamed to context.query

Fields Overhaul 🚀

Keystone’s field types have been given a big overhaul - including several breaking changes, read on to understand what has changed.

Some of these API changes are breaking and you will be required to update your project.

text

  • defaultValue is now a static value
  • isRequired has moved to validation.isRequired
  • Now requires that the value has a length of at least one
  • New validation.length.min, validation.length.max and validation.match options

float

  • defaultValue is now a static number
  • isRequired has moved to validation.isRequired
  • New validation.min and validation.max options

integer

  • defaultValue is now a static number or { kind: 'autoincrement' }
  • isRequired has moved to validation.isRequired
  • New validation.min and validation.max options

The autoIncrement field has also been removed, use the integer field with a defaultValue of { kind: 'autoincrement' }.

decimal

  • defaultValue is now a static number written as a string
  • isRequired has moved to validation.isRequired
  • Now requires the input isn't NaN
  • New validation.min and validation.max options

timestamp

  • defaultValue is now a static date time value in an ISO8601 string or { kind: 'now' }
  • isRequired has moved to validation.isRequired

The field can also be automatically set to the current time on a create/update by setting db.updatedAt: true, this will add Prisma's @updatedAt attribute to the field.

The timestamp field also now uses a custom GraphQL scalar type named DateTime which requires inputs as full ISO8601 date-time strings such as "2021-01-30T00:00:00.000Z". Using new Date().toISOString() will give you a string in the correct format.

select

  • dataType has been renamed to type
  • defaultValue is now a static value
  • isRequired has moved to validation.isRequired

The select can now also be cleared in the Admin UI when ui.displayMode is segmented-control.

password

  • defaultValue has been removed
  • isRequired has moved to validation.isRequired
  • rejectCommon has moved to validation.rejectCommon
  • minLength has moved to validation.length.min
  • New validation.length.max and validation.match options

validation.length.min also must be 1 or above, though it still defaults to 8.

If workFactor is outside of the range of 6 to 31, an error will now be thrown instead of the previous behaviour of clamping the value to 4 to 31 and warning if it's below 6.

image

  • Removed isRequired
  • Removed defaultValue

If you were using these options, the same behaviour can be re-created with the validateInput and resolveInput hooks respectively.

file

  • Removed isRequired
  • Removed defaultValue

If you were using these options, the same behaviour can be re-created with the validateInput and resolveInput hooks respectively.

cloudinaryImage

  • Removed isRequired
  • Removed defaultValue

If you were using these options, the same behaviour can be re-created with the validateInput and resolveInput hooks respectively.

json

  • Removed isRequired
  • defaultValue can no longer be dynamic in the json field

If you were using isRequired, the same behaviour can be re-created with the validateInput hook.

relationship

  • Removed defaultValue
  • Removed undocumented withMeta option

To re-create defaultValue, you can use resolveInput though note that if you're using autoincrement ids, you need to return the id as number, not a string like you would provide to GraphQL, e.g. { connect: { id: 1 } } rather than { connect: { id: "1" } }.

If you were using withMeta: false, please open an issue with your use case.

checkbox

The checkbox field is now non-nullable in the database, if you need three states, you should use the select field.

The field no longer accepts dynamic default values and it will default to false unless a different defaultValue is specified.

If you're using SQLite, Prisma will generate a migration that makes the column non-nullable and sets any rows that have null values to the defaultValue.

If you're using PostgreSQL, Prisma will generate a migration but you'll need to modify it if you have nulls in a checkbox field. Keystone will say that the migration cannot be executed:

<Emoji symbol="✨" alt="Sparkles" /> Starting Keystone
<Emoji symbol="⭐️" alt="star" /> Dev Server Ready on http://localhost:3000
<Emoji symbol="✨" alt="Sparkles" /> Generating GraphQL and Prisma schemas
<Emoji symbol="✨" alt="Sparkles" /> There has been a change to your Keystone schema that requires a migration
<Emoji symbol="⚠️" alt="Warning" /> We found changes that cannot be executed:
Made the column `isAdmin` on table `User` required, but there are 1 existing NULL values.
<Emoji symbol="✔" alt="Tick" /> Name of migration … make-is-admin-non-null
<Emoji symbol="✨" alt="Sparkles" /> A migration has been created at migrations/20210906053141_make_is_admin_non_null
Please edit the migration and run keystone-next dev again to apply the migration

The generated migration will look like this:

/*
Warnings:
- Made the column `isAdmin` on table `User` required. This step will fail if there are existing NULL values in that column.
*/
-- AlterTable
ALTER TABLE "User" ALTER COLUMN "isAdmin" SET NOT NULL,
ALTER COLUMN "isAdmin" SET DEFAULT false;

To make it set any null values to false in your database, you need to modify it so that it looks like this but with the table and column names replaced.

ALTER TABLE "User" ALTER COLUMN "isAdmin" SET DEFAULT false;
UPDATE "User" SET "isAdmin" = DEFAULT WHERE "isAdmin" IS NULL;
ALTER TABLE "User" ALTER COLUMN "isAdmin" SET NOT NULL;

document

The document field is now non-nullable in the database. The field no longer has defaultValue or isRequired options.

The same behaviour can be re-created with the validateInput and resolveInput hooks respectively.

The field will default to [{ "type": "paragraph", "children": [{ "text": "" }] }]. The output type has also been renamed to ListKey_fieldKey_Document

If you're using SQLite, Prisma will generate a migration that makes the column non-nullable and sets any rows that have null values to an empty paragraph.

If you're using PostgreSQL, Prisma will generate a migration but you'll need to modify it if you have nulls in a document field.

Keystone will say that the migration cannot be executed:

<Emoji symbol="✨" alt="Sparkles" /> Starting Keystone
<Emoji symbol="⭐️" alt="star" /> Dev Server Ready on http://localhost:3000
<Emoji symbol="✨" alt="Sparkles" /> Generating GraphQL and Prisma schemas
<Emoji symbol="✨" alt="Sparkles" /> There has been a change to your Keystone schema that requires a migration
<Emoji symbol="⚠️" alt="Warning" /> We found changes that cannot be executed:
Made the column `content` on table `Post` required, but there are 1 existing NULL values.
<Emoji symbol="✔" alt="Tick" /> Name of migration … make_document_field_non_null
<Emoji symbol="✨" alt="Sparkles" /> A migration has been created at migrations/20210915050920_make_document_field_non_null
Please edit the migration and run keystone-next dev again to apply the migration

The generated migration will look like this:

/*
Warnings:
- Made the column `content` on table `Post` required. This step will fail if there are existing NULL values in that column.
*/
-- AlterTable
ALTER TABLE "Post" ALTER COLUMN "content" SET NOT NULL,
ALTER COLUMN "content" SET DEFAULT E'[{"type":"paragraph","children":[{"text":""}]}]';

To make it set any null values to an empty paragraph in your database, you need to modify it so that it looks like this but with the table and column names replaced.

ALTER TABLE "Post" ALTER COLUMN "content" SET DEFAULT E'[{"type":"paragraph","children":[{"text":""}]}]';
UPDATE "Post" SET "content" = DEFAULT WHERE "content" IS NULL;
ALTER TABLE "Post" ALTER COLUMN "content" SET NOT NULL;

general

text, float, integer, decimal, timestamp, select, password can be made non-nullable at the database-level with the isNullable option which defaults to true, except for text which defaults to false.

All fields above except password can also have:

  • graphql.read.isNonNull set if the field has isNullable: false and you have no read access control and you don't intend to add any in the future, it will make the GraphQL output field non-nullable.

  • graphql.create.isNonNull set if you have no create access control and you don't intend to add any in the future, it will make the GraphQL create input field non-nullable.

Keep in mind, checkbox is now always non-nullable.

Hook Updates 🪝

We've consolidated the beforeChange/beforeDelete and afterChange/afterDelete hooks into beforeOperation and afterOperation.

Additionaly, we've renamed:

  • originalInput for access control functions to inputData

  • originalInput for hook functions to inputData

  • existingItem for all hooks (except afterOperation) to item

  • existingItem for afterOperation to originalItem

  • updatedItem for afterOperation to item

See the Hooks API docs for a complete reference for the updated API!

Removals 🚫

Some unused return types and unused values from enum definitions have been removed:

  • sendUserPasswordResetLink and sendUserMagicAuthLink only ever return null, so now have return types of Boolean.
  • UserAuthenticationWithPasswordFailure no longer has a code value.
  • MagicLinkRedemptionErrorCode and PasswordResetRedemptionErrorCode no longer have the values IDENTITY_NOT_FOUND, MULTIPLE_IDENTITY_MATCHES, TOKEN_NOT_SET, or TOKEN_MISMATCH.

If you were using the createSchema function, you can also remove the call to createSchema and pass the lists directly to the lists property.

Before:

import { config, createSchema, list } from '@keystone-next/keystone';
config({
lists: createSchema({
User: list({ ... }),
}),
})

After:

import { config, list } from '@keystone-next/keystone';
config({
lists: {
User: list({ ... }),
},
})

We've removed the deprecated config.db.adapter option. Please use config.db.provider to indicate the database provider for your system.

Finally, the internal protectIdentities variable which was previously hardcoded to true to protect user data, there are no immediate plans to implement this as a configurable option.

Renames 📇

The API context.lists has been renamed to context.query, and context.db.lists has been renamed to context.db.

The skipAccessControl argument to createContext to sudo for consistency with context.sudo().

When using the experimental option config.experimental.generateNodeAPI, the api module now exports query rather than lists.

Renamed graphQLReturnFragment to ui.query in the virtual field options. The virtual field now checks if ui.query is required for the GraphQL output type, and throws an error if it is missing. If you don't want want the Admin UI to fetch the field, you can set ui.itemView.fieldMode and ui.listView.fieldMode to 'hidden' instead of providing ui.query.

Also, we've moved the graphql export of @keystone-next/keystone/types to @keystone-next/keystone.

Improved Error Messaging 📟

Error messages have been improved across the board, in summary:

  • On startup, we now output where the GraphQL API is located
  • If access is denied errors are thrown, we now state which operation isn't permitted
  • Bad relationship field inputs are detected and outputed
  • Clearer distinction if a user input is invalid
  • If access control functions return invalid values we state what we got and what we expected
  • Improved the error messages provided from the GraphQL API when extension code (e.g access control functions, hooks, etc) throws exceptions
  • Better formatting of GraphQL error messages resulting from Prisma errors
  • Clearer sign-in errors when logging into the Admin UI
  • Improved error messages when returning bad filters from filter access control

Performance 🚅

Field Mode

We've optimised the itemView field mode fetching - we now only fetch the item once to determine the field modes rather than once per field.

The Admin UI will also skip fetching fields that have a statically set itemView.fieldMode: 'hidden' on the itemView.

The id argument to the KeystoneAdminUIFieldMeta.itemView GraphQL field can now be omitted which will make KeystoneAdminUIFieldMetaItemView.fieldMode return null when there isn't a static field mode.

The itemView also no longer uses a sudo context when fetching the item in the KeystoneAdminUIFieldMetaItemView.fieldMode. Previously, if someone had access to the Admin UI(ui.isAccessAllowed) and a field had a itemView.fieldMode function that used the item argument, someone could bypass access control to determine whether or not an item with a given id exists.

Query Generation Performance

Query generation performance has been improved when querying single relationships without filter-based access control.

When resolving related items, we often have a foreign key available to us that we can use to help optimise the subsequent related item query. If, on top of this, there are no filter-based access control rules in place, then we can use the Prisma findUnique operation, which will group all the operations into a single database query. This solves the GraphQL N+1 query problem in this specific instance.

REST API Example 👩‍🏫

This release also adds createContext to the extendExpressApp function giving you access to the full context API from Keystone.

In a new example we demonstate how to create REST endpoints by extending Keystone's express app and using the Query API to execute queries against the schema.

You can find all of our examples in our examples folder on GitHub.

Other Notable Changes 🛎️

  • The KeystoneAdminUIFieldMeta.isOrderable and KeystoneAdminUIFieldMeta.isFilterable fields are no longer statically resolvable and will now take into account the context/session. This also means isOrderable and isFilterable are no longer accessible on useList().fields[fieldKey].isOrderable/isFilterable, they can be fetched through GraphQL if you need them in the Admin UI.

  • The sendItemMagicAuthLink and sendItemPasswordResetLink mutations now always return true instead of always returning null

  • Added support for dynamic isFilterable and isOrderable field config values. If a function is provided for these config option, it will be dynamically evaluated each time the field is used for filtering and ordering, and an error will be returned if the function returns false.

Apollo Server Upgrade 👩‍🚀

Apollo Server has had a major upgrade to Version 3.

The Apollo documentation contains a full list of breaking changes introduced by this update.

You can configure the Apollo Server provided by Keystone using the graphql.apolloConfig configuration option.

The most prominent change for most users will be that the GraphQL Playground has been replaced by the Apollo Sandbox.

If you prefer to keep the GraphQL Playground, you can configure your server by following these instructions.

Prisma Update 🗃

We've updated our Prisma dependency from 2.30.2 to 3.1.1!

Note that Keystone continues to use the "binary" query engine, rather than the new "node-API" query engine, which is now the Prisma default. We are still performing tests to ensure that the node-API query engine will work well with Keystone.

Check out the Prisma releases page for more details on this major update.

Credits 💫

  • Exported Field types to help in updating contrib packages. Thanks @gautamsi!

  • Fixed remaining windows issue where it creates invalid import path. This removes some duplicate code which caused this. Thanks @gautamsi!

  • Added support for Prisma preview features via the config.db.prismaPreviewFeatures configuration option. Thanks @Nikitoring!

Complete Changelog 📜

You can also view the verbose release notes for this release on GitHub.