Visual editing /Visual Editor /Content sources /

Use Contentful with Visual Editor

Visual Editor has first-class support for two-way syncing content between Contentful and Visual Editor.

# Configure Contentful as a content source

Visual Editor has a tight integration to support Contentful as one or more of your content sources. This guide covers what you need to know about configuring Visual Editor to provide a two-way data sync between your Contentful spaces.

Here is an example configuration that uses Next.js as the site framework and uses a page content type to represent pages:

// stackbit.config.ts
import { defineStackbitConfig } from "@stackbit/types";
import { ContentfulContentSource } from "@stackbit/cms-contentful";

export default defineStackbitConfig({
  stackbitVersion: "~0.6.0",
  ssgName: "nextjs",
  nodeVersion: "16",
  contentSources: [
    new ContentfulContentSource({
      spaceId: process.env.CONTENTFUL_SPACE_ID!,
      environment: process.env.CONTENTFUL_ENVIRONMENT!,
      previewToken: process.env.CONTENTFUL_PREVIEW_TOKEN!,
      accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN!,
      useWebhookForContentUpdates: true
  models: {
    page: { type: "page", urlPath: "/{slug}" }

Notice the following:

  • ContentfulContentSource is being loaded from the @stackbit/cms-contentful which must be installed first. This package is not needed in your production site.
  • Contentful is being configured using local environment variables. Visual Editor will automatically load a .env file in the root of your project.
  • There is a content type with an ID of page that is being used to store page data in Contentful. That content type has a field with name slug that determines the URL path at which the page is made available.

# Prerequisites

To be able to work with Contentful, you must first have the following:

  • A Contentful space with content.

  • The proper API keys that can access your content. (See options below.)

  • Installed @stackbit/cms-contentful package as a development dependency. (We also recommend @stackbit/types to help with configuration.)

    npm install -D @stackbit/types @stackbit/cms-contentful

# Usage

import { ContentfulContentSource } from "@stackbit/cms-contentful";

new ContentfulContentSource({
  spaceId: "...",
  environment: "...",
  previewToken: "...",
  accessToken: "...",
  useWebhookForContentUpdates: true

# Options

The following are all required options, unless noted:

  • accessToken: the personal access token for your authenticated user. This is sometimes called a management token. It is not the delivery API key for your space.
  • environment: name of the environment in which the content is stored in Contentful.
  • previewToken: a preview API key for the space and environment you're using.
  • spaceId: ID for the space, which can be found in the URL of the space.
  • useWebhookForContentUpdates: (optional, default: false) whether or not to rely on a Contentful webhook for reacting to content changes.
    • When set to false (default), Visual Editor polls the Contentful Sync API every second for new and updated content, but still relies on webhooks for deleted entries.
    • When set to true, Visual Editor minimizes the number of API calls and relies on webhooks for all content changes.
# Store sensitive values

Sensitive values can be stored in a .env file, which will then be available when Visual Editor configuration file is loaded.

# .env

# Content type inference

The Contentful CSI module infers all Contentful content types to be Visual Editor data models, unless otherwise specified.

Therefore, page models must be defined using the modelExtensions property.

# Use multiple spaces or environments

The contentSources property is an array of content sources that are all pulled together with Visual Editor. You can add another content source by instantiating another class as another item in the array, while using separate environment variables for the options.

// stackbit.config.ts
import { ContentfulContentSource } from "@stackbit/cms-contentful";

export default {
  contentSources: [
    new ContentfulContentSource({
      spaceId: process.env.CONTENTFUL_SPACE_ID_01,
      environment: process.env.CONTENTFUL_ENVIRONMENT_01,
      previewToken: process.env.CONTENTFUL_PREVIEW_TOKEN_01,
      accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN_01
    new ContentfulContentSource({
      spaceId: process.env.CONTENTFUL_SPACE_ID_02,
      environment: process.env.CONTENTFUL_ENVIRONMENT_02,
      previewToken: process.env.CONTENTFUL_PREVIEW_TOKEN_02,
      accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN_02
  // ...

# Conflicting model names

Note that if there are conflicting model names (for example, IDs), the model that was loaded last (later in the contentSources array) will override those defined earlier.

# Handle Contentful presets

In Contentful, all relationships among objects are references — you can't embed entries inside another entry. By default, Visual Editor uses existing references when creating new content from a preset.

Consider a scenario in which a page model has two fields — components and author — both of which hold references to other entries in the CMS.

If you then create a new page from a preset, Visual Editor will use the existing components and author entries. The only new content created will be the page entry.

You can override this behavior using the presetReferenceBehavior setting in your configuration file. See below for examples.

Visual representation of presetReferenceBehavior setting.


Note that preset configuration changes only apply to newly-created presets. This is because preset configurations are stored as files and Visual Editor will not adjust existing files in your repository.

# Duplicate references

If you'd prefer for Visual Editor to duplicate references by default rather than using existing references, you can set that behavior in your configuration file.

// stackbit.config.js
export default {
  presetReferenceBehavior: "duplicateContents"
  // ...

# Add models as exceptions

There will likely be exceptions to that rule. Again, using the example above, you may want buttons to be duplicated, but author references to be copied.

# Specify exceptions to copyReference

In that case, we can leave the default presetReferenceBehavior set to copyReference and set the models that should duplicate the content instead (button).

// stackbit.config.js
export default {
  duplicatableModels: ["button"]
  // ...

Visual representation of duplicatableModels setting.

# Specify exceptions to duplicateContents

The option changes if you've overridden the default behavior. In that case, you'd use nonDuplicatableModels:

// stackbit.config.js
export default {
  presetReferenceBehavior: "duplicateContents",
  nonDuplicatableModels: ["author"]
  // ...

Visual representation of nonDuplicatableModels setting.

# Provision Contentful


This is only relevant when creating a Visual Editor project from a template.

You don't need Contentful provisioning if you have already set up a Contentful space for your project, or if you plan to manually create and configure a space.

Visual Editor will handle provisioning a new Contentful space, along with the initial payload of models and content when creating a new project from a template.

# Export content from Contentful

In most cases, projects that can be provisioned with Contentful as a content source have a Contentful space as the source of truth for duplicated projects.

The first step in preparing your project to be duplicated is to export the content schema and the initial site contents from your Contentful space.

Be sure to configure the exports to include content types, entries, and to download assets. This will place your exported data in a new directory located wherever you ran the export command. Place this directory and its contents in your project and commit the files to Git.

# Import configuration

Once you've exported the content (and committed the data files), all that is left to do is add configuration for importing the content during the provisioning process.

The following properties should be added to an import property in your Visual Editor configuration file. All properties are required and are strings, unless otherwise noted.

  • assetsDirectory: set this to the directory containing your exported content, not the path to exported assets. The contentFile contains paths relative to this directory, which Visual Editor will use to upload assets into the new space. Note: This property can be ignored if uploadAssets is set to false.
  • contentFile: path to the export.json file provided by the Contentful export.
  • deliveryTokenEnvVar: a string representing the name of the environment variable that contains the API delivery token that can be used to retrieve content from Contentful. This is not the token itself, but the name of the environment variable.
  • previewTokenEnvVar: a string representing the name of the environment variable that contains the API preview token that can be used to retrieve content from Contentful. This is not the token itself, but the name of the environment variable.
  • spaceIdEnvVar: a string representing the name of the environment variable that contains the Contentful space ID. This is not the space ID itself, but the name of the environment variable.
  • type: set to contentful.
  • uploadAssets: (boolean) whether exported assets should be uploaded. This is typically true when you're base space included asset entries.
// stackbit.config.js
export default {
  contentSources: [
    new ContentfulContentSource({
      spaceId: process.env.CONTENTFUL_SPACE_ID,
      environment: process.env.CONTENTFUL_ENVIRONMENT,
      previewToken: process.env.CONTENTFUL_PREVIEW_TOKEN,
      accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN
  models: {
    // page definitions and model decorations ...
  import: {
    type: "contentful",
    contentFile: "contentful/export.json",
    uploadAssets: true,
    assetsDirectory: "contentful",
    spaceIdEnvVar: "CONTENTFUL_SPACE_ID",
    deliveryTokenEnvVar: "CONTENTFUL_DELIVERY_TOKEN",
    previewTokenEnvVar: "CONTENTFUL_PREVIEW_TOKEN"
  // other properties ...