headless CMS

Contentful for Publishing Teams: A Practical Overview

Contentful is one of the oldest and most established headless CMS platforms. Launched in 2013, it pioneered the API-first CMS model that the broader industry has since adopted. It is a mature, well-documented platform with a large ecosystem of integrations, a capable editorial interface, and a content delivery infrastructure built for scale.

For publishing teams evaluating headless CMS options, Contentful warrants serious consideration — with a clear-eyed understanding of where it excels and where its costs and complexity create friction.

The Core Model

Contentful organizes content around Content Types — schemas you define in the web interface or via the Management API. Each content type has fields: short text, long text, rich text, number, date, boolean, media (image, video, file), reference (link to another entry), and arrays of any of the above.

An article content type might have: title (short text), slug (short text), body (rich text), publishedDate (date), author (reference to an Author content type), tags (array of short text), heroImage (media), and excerpt (long text).

Once a content type is defined, editors create entries — individual pieces of content — through Contentful’s web app. Entries are stored in Contentful’s hosted database and delivered through the Content Delivery API (CDA) to your front end.

The Editorial Interface

Contentful’s entry editor is a form-based interface. Each field renders its appropriate input type — text fields get text inputs, rich text gets a WYSIWYG editor, references get a picker that searches existing entries, media gets an asset browser.

The rich text editor supports headings, paragraphs, bold/italic/underline, links, lists, horizontal rules, and embedded entries and assets. Embedded entries are particularly powerful — you can define a “callout block” or “pullquote” content type and embed instances of it inline within article body copy, with each embedded entry rendered by its own component in your front end.

The editorial experience is polished and generally well-received by non-technical users. The interface is more refined than Sanity’s Studio out of the box (though Sanity’s Studio is more customizable).

The Content Delivery API

Contentful’s CDA is a REST API that serves published content from its CDN. Queries use URL parameters:

const contentful = require('contentful');

const client = contentful.createClient({
  space: process.env.CONTENTFUL_SPACE_ID,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});

// Fetch recent posts
const entries = await client.getEntries({
  content_type: 'post',
  order: '-fields.publishedDate',
  limit: 10,
  include: 2,  // Resolve linked entries up to 2 levels deep
});

const posts = entries.items.map(item => ({
  id: item.sys.id,
  title: item.fields.title,
  slug: item.fields.slug,
  excerpt: item.fields.excerpt,
  author: item.fields.author?.fields?.name,
  heroImage: item.fields.heroImage?.fields?.file?.url,
}));

The include parameter controls reference resolution depth — how many levels of linked entries are resolved inline. This eliminates additional API calls for common relationship patterns.

GraphQL API

Contentful also provides a GraphQL API that is schema-aware and type-safe based on your content model. Enable it in the Contentful web app under API keys → GraphQL Playground.

query GetRecentPosts {
  postCollection(order: publishedDate_DESC, limit: 10) {
    items {
      title
      slug
      excerpt
      publishedDate
      author {
        name
        bio
        photo {
          url
          width
          height
        }
      }
      heroImage {
        url
        description
      }
      tagsCollection {
        items {
          name
          slug
        }
      }
    }
  }
}

The GraphQL API generates its schema from your content types automatically. It is strongly typed, which means your editor autocompletes available fields and query errors surface at development time rather than runtime.

Rendering Rich Text

Contentful’s rich text field stores content as a structured JSON document (similar in concept to Portable Text). Use the @contentful/rich-text-react-renderer package to render it in React:

import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { BLOCKS, INLINES } from '@contentful/rich-text-types';

const options = {
  renderNode: {
    [BLOCKS.EMBEDDED_ENTRY]: (node) => {
      const entry = node.data.target;
      if (entry.sys.contentType.sys.id === 'callout') {
        return <Callout type={entry.fields.type}>{entry.fields.body}</Callout>;
      }
    },
    [BLOCKS.EMBEDDED_ASSET]: (node) => {
      const { url, description, width, height } = node.data.target.fields.file;
      return <img src={`https:${url}`} alt={description} width={width} height={height} />;
    },
  },
};

export default function PostBody({ body }) {
  return <div>{documentToReactComponents(body, options)}</div>;
}

Custom embedded entry types (callouts, pullquotes, code blocks) are rendered by checking the content type ID and returning the appropriate component.

Webhooks and Build Triggers

For statically generated sites, Contentful can trigger rebuilds when content changes via webhooks. Configure a webhook in the Contentful web app under Settings → Webhooks, pointing at your Netlify, Vercel, or Cloudflare Pages build hook URL.

Set the webhook to fire on entry publish and unpublish events for your content types. When an editor publishes an article, the build triggers within seconds and the site rebuilds.

Environments and Staging

Contentful’s Environments feature provides isolated content environments within a single space — typically a master (production) environment and a staging environment. Content changes can be made in staging, tested against a staging front end, and then promoted to master.

This is a meaningful operational feature for publishing teams that need to separate in-progress content from live content at the infrastructure level, not just by publish status.

Pricing

Contentful’s pricing is the most significant consideration for independent publishers and small teams. The free tier allows one space, two users, and 25,000 API calls per month — limited but sufficient for development and small sites.

Paid plans start at around $300/month for the Basic tier, which is a significant step up. Medium and large publishing operations that need multiple users, multiple locales, or high API volumes need to evaluate whether Contentful’s features justify the cost compared to Sanity (generally cheaper) or self-hosted alternatives (Strapi, Directus) with no per-user or per-request fees.

For enterprise publishers with content teams, multiple environments, and high traffic, Contentful’s reliability and ecosystem make the cost justifiable. For smaller independent publishers, Sanity or a self-hosted option is likely more cost-effective.