Skip to Page NavigationSkip to Page NavigationSkip to Content

Lesson 5: Document field

Learn how to implement a powerful and customisable Rich Text editing experience with Keystone’s document field.

Where we left off

We have a working backend for a collection of interconnected posts and authors (in the User list), and thanks to our efforts in lesson 4 our app is now secured so that only registered users can access the Admin UI to write and edit posts.

//keystone.ts
import { list, config } from '@keystone-6/core';
import { password, text, timestamp, select, relationship } from '@keystone-6/core/fields';
import { withAuth, session } from './auth';
const lists = {
User: list({
fields: {
name: text({ validation: { isRequired: true } }),
email: text({ validation: { isRequired: true }, isIndexed: 'unique' }),
posts: relationship({ ref: 'Post.author', many: true }),
password: password({ validation: { isRequired: true } })
},
}),
Post: list({
fields: {
title: text(),
publishedAt: timestamp(),
status: select({
options: [
{ label: 'Published', value: 'published' },
{ label: 'Draft', value: 'draft' },
],
defaultValue: 'draft',
ui: { displayMode: 'segmented-control' },
}),
author: relationship({ ref: 'User.posts' }),
},
}),
};
export default config(
withAuth({
db: {
provider: 'sqlite',
url: 'file:./keystone.db',
},
lists,
session,
ui: {
isAccessAllowed: (context) => !!context.session?.data,
},
})
);

Back in Lesson 2 we setup a post type, but we skipped the all important "content" part. Let’s go ahead and fill that gap.

Add the Document field

Keystone’s document field is a highly customisable Rich Text editor that lets content creators quickly and easily edit content in your system.

To implement the document field we start by adding the package to our project:

npm install @keystone-6/fields-document

Next, we add the document field to our post list:

// keystone.ts
import { list, config } from '@keystone-6/core';
import { password, text, timestamp, select, relationship } from '@keystone-6/core/fields';
import { document } from '@keystone-6/fields-document';
import { withAuth, session } from './auth';
const lists = {
}),
Post: list({
fields: {
title: text(),
publishedAt: timestamp(),
status: select({
options: [
{ label: 'Published', value: 'published' },
{ label: 'Draft', value: 'draft' },
],
defaultValue: 'draft',
}),
author: relationship({ ref: 'User.posts' }),
content: document()
},
}),

Let’s startup Keystone to see the document field in a post:

Create user screen in Admin UI showing name, email, and password fields

We have a place to write Rich Text, but the control header needs configuring to make this a good editor experience.

Customise the Document field

Let’s start by adding four formatting options available in the document field’s config:

  • formatting (bold, underline, italics)
  • links
  • dividers
  • layouts
const lists = {
}),
Post: list({
content: document({
formatting: true,
links: true,
dividers: true,
layouts: [
[1, 1],
[1, 1, 1],
[2, 1],
[1, 2],
[1, 2, 1],
],
}),
},
}),

And take a look at how they can be used in Admin UI:

Screenshot of the admin UI with the content field visible

For a full guide on using the document field, including rendering this in your own frontend app, check out the full guide on how to use document fields

What we have now

// keystone.ts
import { list, config } from '@keystone-6/core';
import { password, text, timestamp, select, relationship } from '@keystone-6/core/fields';
import { document } from '@keystone-6/fields-document';
import { withAuth, session } from './auth';
const lists = {
User: list({
fields: {
name: text({ validation: { isRequired: true } }),
email: text({ validation: { isRequired: true }, isIndexed: 'unique' }),
posts: relationship({ ref: 'Post.author', many: true }),
password: password({ validation: { isRequired: true } })
},
}),
Post: list({
fields: {
title: text(),
publishedAt: timestamp(),
status: select({
options: [
{ label: 'Published', value: 'published' },
{ label: 'Draft', value: 'draft' },
],
defaultValue: 'draft',
}),
author: relationship({ ref: 'User.posts' }),
content: document({
formatting: true,
links: true,
dividers: true,
layouts: [
[1, 1],
[1, 1, 1],
[2, 1],
[1, 2],
[1, 2, 1],
],
}),
},
}),
};
export default config(
withAuth({
db: {
provider: 'sqlite',
url: 'file:./keystone.db',
},
lists,
session,
ui: { isAccessAllowed: (context) => !!context.session?.data },
})
);

Next steps

Congratulations! You've now built a Keystone app from an empty folder and have the basics in place for a functional blog that you can could connect to a frontend of your choice.

This lesson marks the end of this learning series. To dive deeper into Keystone's capabilities take a look at the following: