Netlify Create /

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 Netlify Create 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, Netlify Create 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:

  1. Document is changed in Netlify Create.
  2. Document hook is fired, based on the type of change event.
  3. 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.