Document hooks
When a content editor makes an altering action on a document, you can make programmatic adjustments to your content based on that action, before the change is stored in the content source.
# Object processing methods
There is a single object-processing method that is triggered when a user creates a new document — onContentCreate
. This method returns an object representing the fields to be used when calling the create operations for the document (or related/nested documents).
This makes it possible to set up Visual Editor to create related and nested content when a new document is created. A common example is to use this field for dynamic default values — content created based on content input by the user.
# Example: adding nested section
Suppose a Page
model had an open-ended array of sections
that were mapped to individual nested objects. Every time a page is created, you want to pre-populate the sections
field with a Hero
section that uses the page title as the main heading.
Here's a simple example of what the code for that hook might look like:
// stackbit.config.ts
import { defineStackbitConfig } from "@stackbit/types";
export default defineStackbitConfig({
onContentCreate: options => {
const { object } = options;
// Add a nested placeholder hero section to a page.
if (options.model.name === "Page") {
object.sections = [
{ $$type: "HeroSection", heading: object.title || "[not set]" }
];
}
// Return the adjusted object.
return object;
}
});
# Use $$type
and $$ref
The reserved keys — $$type
and $$ref
are used for creating nested and related content, along with the original document, following these patterns:
These work identical to the matching properties in the Presets API. In general:
- Use
$$type
to create a new nested object for the model of the specified$$type
. $$type
can also be used to create a new document and link it to a reference field.- To link an image or reference field to existing entities, set
$$ref
to the ID value of the object to reference. (Note that Git CMS uses local file paths for ID values.)
Keys can be included alongside these reserved properties, as shown in the usage example above.
# Empty values
If values are removed or not set in the return object, Visual Editor uses the default
setting to attempt to fill in the value.
# Document operation methods
There are four lifecycle methods, available as configuration properties:
# Lifecycle behavior
These methods are triggered between the time an editor makes a change to a document and the time the change is committed to the content source.
In general, the process flows like this:
- Document is changed in Visual Editor.
- Document hook is fired, based on the type of change event.
- Change is committed to the content source, including any alterations made during the hook.
# Method usage
Each hook is a configuration property, where the value is a function that accepts a single options
argument. There are shared properties within options
, which can be used to access other entities within your content system. (More on this below.)
options
also includes two properties unique to each method:
- A function used as the return value for the method (e.g.
createDocument
) - A specific set of options to send to the return value (e.g.
createDocumentOptions
)
Here's the basic shape of the onDocumentCreate
method:
// stackbit.config.ts
import { defineStackbitConfig } from "@stackbit/types";
export default defineStackbitConfig({
onDocumentCreate: options => {
const { createDocumentOptions, createDocument } = options;
// Transform `createDocumentOptions` object ...
// Then pass to `createDocument` as the return value
return createDocument(createDocumentOptions);
}
});
# Basic example: computing a field
A simple example might be computing some field based on the value of another field. For example, suppose you had two required fields on a model — firstName
and lastName
. Rather than needing to stitch these together throughout your front-end code, you could do this by adding a readOnly
field to the model and setting that after a create or update operation.
Here's an example of what the create method might look like.
// stackbit.config.ts
import { defineStackbitConfig } from "@stackbit/types";
export default defineStackbitConfig({
stackbitVersion: "~0.6.0",
contentSources: [
// ...
],
onDocumentCreate: options => {
// Retrieve the first and last name values.
const firstNameField = options.createDocumentOptions.updateOperationFields.firstName;
const lastNameField = options.createDocumentOptions.updateOperationFields.lastName;
let fullName = '';
if (firstNameField && 'value' in firstNameField && lastNameField && 'value' in lastNameField) {
fullName = `${firstName.value} ${lastName.value}`;
}
// Transform createDocument method options
let createOptions: typeof options.createDocumentOptions = {
...options.createDocumentOptions,
updateOperationFields: {
...options.createDocumentOptions.updateOperationFields,
fullName: { type: "string", value: fullName }
}
};
// Return the createDocument function
return options.createDocument(createOptions);
}
});
# Work with documents
There are a number of ways to manipulate the current document being operated upon, or to affect other documents in any one of a project's content sources.
These operations are performed through the shared options
properties or those properties unique to the individual method.
# Filter behavior by model
As an example, suppose you wanted to perform an operation only for a document of a specific type. You can target the model name of the current document to add an exit condition to the method.
On create, the model name is separated from the fields being modified.
// stackbit.config.ts
import { defineStackbitConfig } from "@stackbit/types";
export default defineStackbitConfig({
stackbitVersion: "~0.6.0",
contentSources: [
// ...
],
onDocumentCreate: options => {
if (options.createDocumentOptions.model.name !== "Page") {
return options.createDocument(options.createDocumentOptions);
}
// Transform document ...
}
});
When the document already exists, the modelName
property is available on the current document.
// stackbit.config.ts
import { defineStackbitConfig } from "@stackbit/types";
export default defineStackbitConfig({
stackbitVersion: "~0.6.0",
contentSources: [
// ...
],
onDocumentUpdate: options => {
if (options.updateDocumentOptions.document.modelName !== "Page") {
return options.updateDocument(options.updateDocumentOptions);
}
// Transform document ...
}
});
# Adhere to the schema
With this approach, you can manipulate documents within your content system in any way, as long as your operations are valid within the appropriate content schema.
If you have questions about what's possible within these methods, contact us.
Did you find this doc useful?
Your feedback helps us improve our docs.