Contentful

Published: Feb 7, 2023 by Isaac Johnson

Following our post from last week on Hypgraph, I wanted to dig into other leading CMS providers out there. I decided to check out Contentful next, the 11-year-old veteran in the space.

Today we will setup an account then a Contentful blog site. We’ll add some content to it and preview as a web page. Lastly, we will access it with NodeJS before exploring deployments and cost.

Company Information

Contentful is a content management system (CMS) and a headless CMS that was founded in 2012 by Sascha Konietzke and Paolo Negri. Konietzke, who initially developed the system would be the CEO until 2019 when he stepped aside to be the Chief Strategy Officer allowing Steve Sloan to be brought in by the Bessemer VC firm.

The company was based in Berlin, Germany and has offices in San Francisco and New York City. Contentful was strong performer in the 2021 Forrester Wave report on CMSes.

/content/images/2023/02/contentful-22b.png

I found it interesting Sascha Konietzke has a Contentful blog but hasn’t updated it since October. That said, he hasn’t posted to twitter since November though is still active on there.

Contentful which powers 30k websites for over 400k members, allows developers to build and manage digital products. These include as websites, mobile apps, and other digital experiences using a single source of content.

Contentful is used by companies such as Lufthansa, Jack in the Box, and Spotify to create and manage their digital content. The company has raised over $47.5 million in funding from investors such as Benchmark, General Catalyst, and Salesforce Ventures.

They have around 750 employees their 2019 revenue was $10.57m. Interestingly, more recent details show less employees (under 500), HQ in San Francisco and revenue in the US$100-500m range (source - which matches the update from 2021 in Wikipedia)

While we saw it in the Forrester Wave report from 2021, I did not see it show up in the Gartner magic quadrant

/content/images/2023/02/contentful-23b.png

Signup

We can click “Sign up free” from the main site to create a new account

/content/images/2023/02/contentful-01.png

I generally use an existing IdP like Google

/content/images/2023/02/contentful-02.png

They’ll have you fill out a form to get started

/content/images/2023/02/contentful-03.png

We can start from scratch or pick from a template.

/content/images/2023/02/contentful-04.png

I’ll try the Blog template first

This will then “Install” the template

/content/images/2023/02/contentful-05.png

When complete, I end up on the “Home Page” editor with a sample featured article

/content/images/2023/02/contentful-06.png

Clicking Preview on the right shows a rather complete Blog with lots of sample content

/content/images/2023/02/contentful-07.png

Adding Content

Let’s add ourselves as a new Author

Author

/content/images/2023/02/contentful-08.png

Where we pick “component - Author”

/content/images/2023/02/contentful-09.png

I’ll want an Icon which needs a Asset. I’ll upload my general Blog Avatar this time as my “Hero Image”

/content/images/2023/02/contentful-10.png

I can now finish the Create Author page

/content/images/2023/02/contentful-11.png

Posts

Next, I’ll click “Add Entry” and select the Post type

/content/images/2023/02/contentful-12.png

For Author, we’ll want to pick our Existing Content to select the Author we made

/content/images/2023/02/contentful-13.png

I can pick the Author entry I had created earlier

/content/images/2023/02/contentful-14.png

Once written, I should have a basic Contentful blog post

/content/images/2023/02/contentful-15.png

Now when I preview, I can see my latest post

/content/images/2023/02/contentful-16.png

I should note that clicking the link doesn’t actually work - this is just a preview of the homepage

/content/images/2023/02/contentful-17.png

Contentful with NodeJS

Before we can get going on accessing Contentful from the outside, we’ll need to get an API Key.

We can get those from settings

/content/images/2023/02/contentful-18.png

What is rather nice with the tokens is they have one for “preview”, that is unpublished content, and “delivery” for published content.

/content/images/2023/02/contentful-19.png

In order to access Contentful using Node.js, we’ll want to first create a directory and init it. I’ll use Node 14 for this example

builder@DESKTOP-QADGF36:~/Workspaces$ mkdir contentfulTest
builder@DESKTOP-QADGF36:~/Workspaces$ cd contentfulTest/
builder@DESKTOP-QADGF36:~/Workspaces/contentfulTest$ nvm list
->     v10.22.1
      v12.22.11
       v14.18.1
        v17.6.0
default -> 10.22.1 (-> v10.22.1)
iojs -> N/A (default)
unstable -> N/A (default)
node -> stable (-> v17.6.0) (default)
stable -> 17.6 (-> v17.6.0) (default)
lts/* -> lts/gallium (-> N/A)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.24.1 (-> N/A)
lts/erbium -> v12.22.12 (-> N/A)
lts/fermium -> v14.19.1 (-> N/A)
lts/gallium -> v16.14.2 (-> N/A)
builder@DESKTOP-QADGF36:~/Workspaces/contentfulTest$ nvm use 14.18.1
Now using node v14.18.1 (npm v6.14.15)
builder@DESKTOP-QADGF36:~/Workspaces/contentfulTest$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (contentfultest)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author: Isaac Johnson
license: (ISC) MIT
About to write to /home/builder/Workspaces/contentfulTest/package.json:

{
  "name": "contentfultest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Isaac Johnson",
  "license": "MIT"
}


Is this OK? (yes)

We will first need to install the Contentful client library for Node.js, called contentful

builder@DESKTOP-QADGF36:~/Workspaces/contentfulTest$ npm install contentful --save

> contentful@9.3.0 postinstall /home/builder/Workspaces/contentfulTest/node_modules/contentful
> node print-beta-v10-message.js

  ---------------------------------------------------------------------------------------------
  contentful.js - the contentful delivery API (library)

  🚨 We have just released contentful.js v10 in Beta with enhanced  TypeScript  support! 🚨
  You can check it out on npm under the beta-v10 tag (go to the "Versions" tab to find it).
  The migration guide and updated v10 README and can be found on the beta-v10 branch.

  README: https://github.com/contentful/contentful.js/blob/beta-v10/README.md
  MIGRATION GUIDE: https://github.com/contentful/contentful.js/blob/beta-v10/MIGRATION.md
  BETA BRANCH: https://github.com/contentful/contentful.js/tree/beta-v10
  ---------------------------------------------------------------------------------------------
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN contentfultest@1.0.0 No description
npm WARN contentfultest@1.0.0 No repository field.

+ contentful@9.3.0
added 25 packages from 29 contributors and audited 25 packages in 2.007s

8 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Once we have the library installed, we can use it to access our Contentful space.

Here is an example of how one can use the library to retrieve all entries from a specific Content Type:

const contentful = require('contentful')

const client = contentful.createClient({
  space: 'YOUR_SPACE_ID',
  accessToken: 'YOUR_ACCESS_TOKEN'
})

client.getEntries({
  content_type: 'YOUR_CONTENT_TYPE_ID'
})
.then(entries => console.log(entries.items))
.catch(console.error)

In this example, one would need to replace YOUR_SPACE_ID and YOUR_ACCESS_TOKEN with the appropriate values for the space. One also need to replace ‘YOUR_CONTENT_TYPE_ID’ with the id of the content type one wishes to retrieve.

The result looks like this:

builder@DESKTOP-QADGF36:~/Workspaces/contentfulTest$ cat index.js
const contentful = require('contentful')

const client = contentful.createClient({
  space: 'ukkohuhcdmei',
  accessToken: 'asdfsadfasdfasdfasdfasdfasdfsadfasdf'
})

client.getEntries({
  content_type: 'pageBlogPost'
})
.then(entries => console.log(entries.items))
.catch(console.error)

This code will retrieve all the entries from the specified Content Type and log them to the console. We can then use the entries.items array to access the individual entries and display them in our application.

To test, we’ll add a “dev” option to package.json

builder@DESKTOP-QADGF36:~/Workspaces/contentfulTest$ cat package.json
{
  "name": "contentfultest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Isaac Johnson",
  "license": "MIT",
  "dependencies": {
    "contentful": "^9.3.0"
  }
}

Now when we run, we see our blog posts:

$ npm run dev

> contentfultest@1.0.0 dev /home/builder/Workspaces/contentfulTest
> node index.js

[
  {
    metadata: { tags: [] },
    sys: {
      space: [Object],
      id: '2zEF00tNfEUqaJf7LQY3MU',
      type: 'Entry',
      createdAt: '2023-01-30T13:29:48.811Z',
      updatedAt: '2023-01-30T13:29:48.811Z',
      environment: [Object],
      revision: 1,
      contentType: [Object],
      locale: 'en-US'
    },
    fields: {
      internalName: 'Contentful, a simple powerful CRM',
      seoFields: [Object],
      slug: 'contentful-a-powerful-cms',
      author: [Object],
      publishedDate: '2023-01-30',
      title: 'Contentful, A Powerful CMS',
      shortDescription: 'Contentful, A Powerful CMS',
      featuredImage: [Object],
      content: [Object]
    }
  },
  {
    metadata: { tags: [] },
    sys: {
      space: [Object],
      id: 'KaA7yfli9UsX38W5YB8PI',
      type: 'Entry',
      createdAt: '2023-01-30T13:09:45.658Z',
      updatedAt: '2023-01-30T13:09:45.658Z',
      environment: [Object],
      revision: 1,
      contentType: [Object],
      locale: 'en-US'
    },
    fields: {
      internalName: 'The rise of robots: how advanced technology is changing',
      seoFields: [Object],
      slug: 'the-rise-of-robots-how-advanced-technology-is-changing',
      author: [Object],
      publishedDate: '2022-12-04',
      title: 'The rise of robots: how advanced technology is changing',
      shortDescription: 'Robots set to take on more complex tasks in the future',
      featuredImage: [Object],
      content: [Object],
      relatedBlogPosts: [Array]
    }
  },
  {
    metadata: { tags: [] },
    sys: {
      space: [Object],
      id: '4YpnxRJ6o0uhD0pxY7hHRF',
      type: 'Entry',
      createdAt: '2023-01-30T13:09:45.648Z',
      updatedAt: '2023-01-30T13:09:45.648Z',
      environment: [Object],
      revision: 1,
      contentType: [Object],
      locale: 'en-US'
    },
    fields: {
      internalName: 'Creating sustainable cities and the role of green urbanism',
      seoFields: [Object],
      slug: 'creating-sustainable-cities-and-the-role-of-green-urbanism',
      author: [Object],
      publishedDate: '2022-12-04',
      title: 'Creating sustainable cities and the role of green urbanism',
      shortDescription: 'Green urbanism: a key strategy for creating sustainable cities ',
      featuredImage: [Object],
      content: [Object],
      relatedBlogPosts: [Array]
    }
  },
  {
    metadata: { tags: [] },
    sys: {
      space: [Object],
      id: '5MKo3CM9iXNJxQ0zN5LM0D',
      type: 'Entry',
      createdAt: '2023-01-30T13:09:44.635Z',
      updatedAt: '2023-01-30T13:09:44.635Z',
      environment: [Object],
      revision: 1,
      contentType: [Object],
      locale: 'en-US'
    },
    fields: {
      internalName: 'Exploring the intersection of technology and art',
      seoFields: [Object],
      slug: 'exploring-the-intersection-of-technology-and-art',
      author: [Object],
      publishedDate: '2022-12-04',
      title: 'Exploring the intersection of technology and art',
      shortDescription: 'How technology is transforming the world of art',
      featuredImage: [Object],
      content: [Object],
      relatedBlogPosts: [Array]
    }
  },
  {
    metadata: { tags: [] },
    sys: {
      space: [Object],
      id: '4kOIaTkVH2SQRt51bT1evW',
      type: 'Entry',
      createdAt: '2023-01-30T13:09:44.628Z',
      updatedAt: '2023-01-30T13:09:44.628Z',
      environment: [Object],
      revision: 1,
      contentType: [Object],
      locale: 'en-US'
    },
    fields: {
      internalName: 'Quantum computing: how it works and what it means for the future',
      seoFields: [Object],
      slug: 'quantum-computing-how-it-works-and-what-it-means-for-the-future',
      author: [Object],
      publishedDate: '2022-12-04',
      title: 'Quantum computing: how it works and what it means for the future',
      shortDescription: 'Quantum computing set to revolutionize the world of computing ',
      featuredImage: [Object],
      content: [Object],
      relatedBlogPosts: [Array]
    }
  },
  {
    metadata: { tags: [] },
    sys: {
      space: [Object],
      id: '24K074hJ0Wjoh9Lq4cyWdd',
      type: 'Entry',
      createdAt: '2023-01-30T13:09:44.609Z',
      updatedAt: '2023-01-30T13:09:44.609Z',
      environment: [Object],
      revision: 1,
      contentType: [Object],
      locale: 'en-US'
    },
    fields: {
      internalName: 'Humanoids take the stage: the growing role of human-like robots in society',
      seoFields: [Object],
      slug: 'humanoids-take-the-stage-the-growing-role-of-human-like-robots-in-society',
      author: [Object],
      publishedDate: '2022-12-04',
      title: 'Humanoids take the stage: the growing role of human-like robots in society',
      shortDescription: 'Humanoid robots poised to revolutionize the way we live and work',
      featuredImage: [Object],
      content: [Object],
      relatedBlogPosts: [Array]
    }
  },
  {
    metadata: { tags: [] },
    sys: {
      space: [Object],
      id: '4sXctNPW227iM9L4y2MqhE',
      type: 'Entry',
      createdAt: '2023-01-30T13:09:41.600Z',
      updatedAt: '2023-01-30T13:09:41.600Z',
      environment: [Object],
      revision: 1,
      contentType: [Object],
      locale: 'en-US'
    },
    fields: {
      internalName: 'Introducing the latest and greatest tools for developers',
      seoFields: [Object],
      slug: 'introducing-the-latest-and-greatest-tools-for-developers',
      author: [Object],
      publishedDate: '2022-12-04',
      title: 'Introducing the latest and greatest tools for developers',
      shortDescription: 'New developer tools empower to build the next generation of applications',
      featuredImage: [Object],
      content: [Object],
      relatedBlogPosts: [Array]
    }
  },
  {
    metadata: { tags: [] },
    sys: {
      space: [Object],
      id: '5Ffob3XoJGrQuKE5uRUugR',
      type: 'Entry',
      createdAt: '2023-01-30T13:09:39.559Z',
      updatedAt: '2023-01-30T13:09:39.559Z',
      environment: [Object],
      revision: 1,
      contentType: [Object],
      locale: 'en-US'
    },
    fields: {
      internalName: 'How AR will transform our lives in 2050',
      seoFields: [Object],
      slug: 'how-ar-will-transform-our-lives-in-2050',
      author: [Object],
      publishedDate: '2022-12-04',
      title: 'How AR will transform our lives in 2050',
      shortDescription: 'Augmented Reality set to revolutionize daily life in 2050',
      featuredImage: [Object],
      content: [Object],
      relatedBlogPosts: [Array]
    }
  }
]

And if we wanted to do Python.

We can install the library

$ pip install contentful_management
Collecting contentful_management
  Downloading contentful_management-2.11.0.tar.gz (38 kB)
Requirement already satisfied: python-dateutil in /usr/lib/python3/dist-packages (from contentful_management) (2.7.3)
Requirement already satisfied: requests<3.0,>=2.20.0 in /usr/lib/python3/dist-packages (from contentful_management) (2.22.0)
Building wheels for collected packages: contentful-management
  Building wheel for contentful-management (setup.py) ... done
  Created wheel for contentful-management: filename=contentful_management-2.11.0-py3-none-any.whl size=71725 sha256=cd750b9cc42f7dcaae4ecd62b5b9ea52f88e2238c34538935bfaeea6ee4b38c9
  Stored in directory: /home/builder/.cache/pip/wheels/00/cc/2b/c8d92b042eafa41d358a214cd49cb4b8ed01250b000488cb23
Successfully built contentful-management
Installing collected packages: contentful-management
Successfully installed contentful-management-2.11.0

Then set some env vars

builder@DESKTOP-72D2D9T:~/Workspaces/contentfulPython$ export CONTENTFUL_SPACE_ID=ukkohuhcdmei
builder@DESKTOP-72D2D9T:~/Workspaces/contentfulPython$ export CONTENTFUL_ACCESS_TOKEN=asdfasdfsdfasdfasdfasdfasdf
builder@DESKTOP-72D2D9T:~/Workspaces/contentfulPython$ export CONTENTFUL_CONTENT_TYPE=pageBlogPost

Then some python to fech posts

$ cat contentful.py
import os
from contentful_management import Client

client = Client(os.environ.get('CONTENTFUL_ACCESS_TOKEN'))

space = client.spaces().find(os.environ.get('CONTENTFUL_SPACE_ID'))

entries = space.entries({'content_type': os.environ.get('CONTENTFUL_CONTENT_TYPE')})

for entry in entries:
    print(entry.fields)

I decided to pivot and just get blog titles

$ cat contentfulTest2.py
import os
from contentful import Client

client = Client(os.environ.get('CONTENTFUL_SPACE_ID'), os.environ.get('CONTENTFUL_ACCESS_TOKEN'))

entries = client.entries({
    'content_type': 'pageBlogPost',
    'limit': 2
})

for entry in entries:
    print(entry.title)
    print('-' * 50)

which will show a couple posts without issues

$ python3 contentfulTest2.py
Contentful, A Powerful CMS
--------------------------------------------------
The rise of robots: how advanced technology is changing
--------------------------------------------------

Deployment of Static Site

I tried to follow the few guides I found, such as their own “build a static site” one. They generally want you to tie in with Gatsby.

I was not successful, however, below are the steps and how far I managed to get before getting blocked.

(Spoiler: Stay tuned for Thursday’s post where I will cover this last mile using Hugo)

Gatsby

Create a Gatsby Account

/content/images/2023/02/contentful-22.png

I filled in some details

/content/images/2023/02/contentful-23.png

Then I had a Gatsby account

/content/images/2023/02/contentful-24.png

I clicked “Add a site +”.

First, I picked a destination

/content/images/2023/02/contentful-26.png

The selected Contentful Homepage

/content/images/2023/02/contentful-25.png

It would pop up an auth page

/content/images/2023/02/contentful-27.png

Then just get stuck. I tried multiple times and multiple browsers

/content/images/2023/02/contentful-28.png

Usage

At any point, under Settings, we can view usage

/content/images/2023/02/contentful-20.png

They are definitely geared at larger players. The next level up from free is US$490/mo

/content/images/2023/02/contentful-21.png

Pricing

Contentful offers several pricing plans for its customers, including a free plan, a developer plan, a team plan, and a business plan.

The free (community) plan includes basic features such as a limited amount of content, a limited number of users, and basic support.

/content/images/2023/02/contentful-29.png

The former plans were a developer at $49/mo, a team at $249/mo and the a “business plan” at “talk to us” pricing. The developer plan was designed for small teams and included more advanced features such as additional users, more content, and access to webhooks and APIs. The team plan was designed for larger teams and included even more advanced features such as custom roles, custom validation, and access to the Contentful App Framework. Lastly, the business plan was designed for enterprise customers and included additional features such as dedicated support, custom integrations, and compliance features.

That said, it appears the model has changed - quite literally during the week I was writing this post. Gone are the simple “developer” and “team” plans and now we “Basic” and “Premium” for $300/mo and “talk to us” pricing.

/content/images/2023/02/contentful-30.png

Even more fun, is the basic license has just one “space”.. Each new space is an added $350/mo

/content/images/2023/02/contentful-31.png

It’s worth noting that all plans include access to all of Contentful’s features and functionality, however each plan may have different limits on the number of users, amount of content, and level of support.

Contentful vs Hygraph

Contentful and Hygraph are two different technologies with different purposes. Contentful is a headless CMS (Content Management System) that provides a platform for managing and delivering content. It provides a flexible and scalable solution for organizing and delivering content to multiple platforms and channels.

Hygraph, on the other hand, is a graph database that provides a way to store and manage graph-based data. It provides a flexible and scalable solution for organizing and querying graph data.

Basically, the main difference between Contentful and Hygraph is that Contentful is focused on content management and delivery, while Hygraph is focused on graph data management. Contentful can be used as a back-end for a website or application to manage and deliver content, while Hygraph can be used as a back-end for a graph-based application to manage and query graph data. With some work, GraphCMS/Hypgraph can also be used for blogging, but that isn’t its main raison d’être.

Summary

The backend of Contentful is great. I found it pretty easy to poke about and build things. Hitting it with various tests and UIs, I barely sipped my 2M/mo quota in the free tier

/content/images/2023/02/contentful-32.png

I could see this being a fantastic backend for websites, blogs and the like. Anything that has regular created content would be a fit. I find it a bit said they did away with the lower cost options of “developer” and “teams”. These changes happened as recently as last week. The positive is they upped the “free” tier plan, but then eliminated the low end ones in favour of a more expensive “basic” plan.

I get it, though; consider a company that has just a small blog. They have 2-3 writers and a small cadence. The free tier would cover just fine. They could even prove out the model in small scale and when ready to “go big” or if they grow, US$350/mo (US$4200/year) is essentially the cost of a very nice laptop, 23 premium seats of JIRA or just over 4 Adobe Creative Cloud seats.

Thoughts on Blogging

/content/images/2023/02/contentful-34.png

I’ve been deep in the weeds at my place of business discussing blogging, the hows and whats, and one thing that has become apparent is the need for more user friendly tools.

I last used blogger in 2008. I logged in to find a “recent” post from 2008 when I was building a blog for my Grandfather who passed away a few years later. But Blogger/Blogspot still is a great entry level way to get started.

I’m old enough that my journey started with Geocities before hosting my own with LAMP stacks or AOLPress around 1997. Then Perl-driven news sites (zebulun.org/netizennews.com) followed by Blogs. The blogs in the early 2000s were created with Macromedia Dreamweaver then later with other tools. In the last 5 years I went to Ghost then Jekyll where I am today.

Each stage marked some form of growth. If a blog grows out of being a single voice into a slew of authors, or desires content from less software-technical creators, there becomes a real need for CMS tooling like Contentful. Moreover, my scaling today works as it’s statically created and published by someone who really likes markdown. Should one build a dynamic backend or an Android/iOS app, then having a rich and robust content backend makes even more sense.

cms contentful graphql

Have something to add? Feedback? Try our new forums

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes