Help! My terminal speaks Contentful

I work at, which is the largest not-for-profit exclusively for young people and social change. Our digital platform activates millions of young people to create offline impact in every US area code and in 131 countries. We run social good campaigns (350+!) where our members can learn about a social issue, take some practical actions to help solve that issue and report back to us to verify their social action. Through these campaigns, DoSomething members have clothed half of America’s youth in homeless shelters. They’ve cleaned up 3.7 million cigarette butts. They’ve run the world’s largest youth-led sports equipment drive. And much more!

To support our campaign experience, we had previously used Drupal as our monolithic web application framework. We decided to modernize the way we run our web systems and chose Contentful as our CMS, which complements our service-oriented architecture well. So far, their features have done nothing but impress us.

In this article, I will run through some of the ways using Contentful has made our content operations easier to run and manage, along with the creative solutions we came up with to address the complex nature of our campaigns and content migration.

Everything ties together as follows: our web platform, Phoenix, runs on Laravel (a PHP-based web app framework), uses ReactJS on the frontend, and has its content hosted on Contentful.

Screen Shot 2019-04-12 at 15.06.55

When we built our platform, our goal was to generalize the campaign model. That way, we could replicate it across campaigns while customizing elements such as actions.

Screen Shot 2019-04-11 at 11.26.14

Our campaign pages typically consist of blocks of content, each sourced by a different Contentful entry on the backend (with the entries themselves being from a range of various content types). Within our React application, we use a component called ContentfulEntry as a one-stop shop rendering service for these blocks. The component accepts the JSON block for any type of entry and based on its content type, renders the appropriate component. For instance, if the ContentfulEntry component is served an entry of the CampaignUpdateBlock content type, it will, in turn, feed the JSON to the CampaignUpdateBlock component which renders the markup for a campaign update unto the page.

Screen Shot 2019-04-12 at 15.09.07Screen Shot 2019-04-11 at 11.32.29Screen Shot 2019-04-11 at 11.32.46

How we use Contentful CLI Tools

The Contentful Migration CLI is useful to document and script out any content type schema changes. It’s a great way to have replicable schema migrations on your environments.

Screen Shot 2019-04-12 at 15.13.11

We can create migration scripts for anything from a simple field change to adding new content types. We’ll take the creation of the quiz content type as an example. The script exports a function which yields a migration object. You can then invoke special functions on that object –– in this case, it would be creating the content type and, subsequently, adding fields to the content type.

It’s sparse at this point, but we wanted to experiment with the quiz content type before fully speccing it out. The beauty of this is that it allowed us to go live and run another migration later on once we had the full specifications for the fields. All of that is also fully replicable for other environments.

To run the migration, we first had to install the Contentful CLI package manager. This is where you can control your spaces and environments right from the command line interface. It’s also where you’ll set your content management access tokens, specify default spaces, environments and more.

Content transformations with Contentful

Screen Shot 2019-04-12 at 15.15.58

Campaigns play a huge role in our business model so thus they were our primary content type. We added settings for campaigns as fields within that content type but it was messy and resulted in clutter. So we extracted these settings into its own content type named “campaign settings” which is linked as a reference field to campaigns.

In order to maximize member engagement with our campaigns, we run A/B tests on our designs and content. A/B testing is used to verify hypotheses on which designs and content work better. We can toggle A/B testing functionality using one of the available campaign settings defined. Some of our campaigns are coordinated with a corporate partner that carefully reviews and signs off on all content before its launch. We wouldn’t want to allow any A/B tests to run for these particular campaigns given the careful curation of the content. We added a field for toggling a setting to false so that A/B testing can be disabled for a given campaign.

We came to a time when we wanted to pause A/B testing for all campaigns on the site. The easiest way to do that would have been to toggle the allowExperiments field to false. However, doing that manually across over 260+ campaigns would have been a nightmare.

Screen Shot 2019-04-12 at 15.17.49

Thankfully Contentful had us covered. The transformation functionality with the Migration CLI enabled us to batch run transformations across a certain content type (campaignSettings) and the fields affected (allowExperiments). After that, we ran a function to change the field allowExperiments to an updated value of false.

By running that script, the whole transformation was completed within seconds. That’s just one of the ways working with such a developer-friendly product helped save my team time and preserved our sanity.

Content Management API and Content Migration

Screen Shot 2019-04-12 at 15.19.59

Generally, our campaigns have three action steps: knowing the social issue, coming up with a plan to help and carrying out the plan. These action steps represent the core content of a campaign. In the process of replicating the domain model in Contentful, we equipped the campaign content type with a multi-reference field called actionSteps to contain these steps as a list of blocks.

Screen Shot 2019-04-12 at 15.21.38

You can see an example of this field in action (excuse the pun), in a tire safety campaign called Pump It Up. We have a list of content blocks referenced in the actionSteps field which are then rendered out unto the ‘Action’ page as action steps.

A campaign would generally have an additional ‘Community’ page which would contain useful informational updates for the campaign, highlighted member stories and more.
We added a similar field to the Campaign content type called activityFeed which is also a multi-reference field for a list of content blocks which get rendered out unto the ‘Community’ page.

Having two unique fields with their own rendering logic on the frontend and processing on the backend to do very similar things didn’t feel like a very sustainable approach. If we were to add a new kind of page to the ‘Action’ and ‘Community’ pages and source its content with a new multi-reference field, we would need to add more processing logic to address that new field and render those blocks unto the new page! We clearly needed to update our approach with a more replicable solution.

After some brainstorming, we came up with a more abstracted solution for how a page’s content should be rendered. This more general way of thinking about pages would eliminate bloat and improve the replicability of content across different campaign pages. We’d create a new ‘Page’ content type, which would encompass both ‘Action’ and ‘Community’ pages, (and whichever future sorts pages might be added to campaigns). The Page would have a general blocks field to contain the list of content blocks to be rendered within the page.

Our course of action was to now be rid of the existing disparate Campaign page fields and their associated logic, and pipe the existing blocks from those fields unto individual new ‘Page’ entries. This transformation process would have to be run on all of our existing campaigns, across their respective actionSteps and activityFeed fields – extracting the blocks in those fields, placing them onto a new ‘Page’, and finally, publishing that page. This is quite a lot to ask for from the migration tool (compared to a simple field transformation, for instance) so we needed to find a better way to script and run this process.

Fortunately, we were able to utilize the Content Management API (specifically the contentful-management.js library in our case) to address our needs. With a few simple lines of Javascript to initialize the client (via an accessToken) and specify the proper space and environment, we could do pretty much anything we needed to our content. We were able to fetch all of our campaign entries and process them in any way we desired.

We then added a process which would grab the actionSteps and activityFeed fields from each campaign, create an ‘Action’ and ‘Community’ page using the ‘Page’ content type providing them with a front-facing ‘title’ and ‘slug’, and place the existing content blocks unto those new pages.

Screen Shot 2019-04-12 at 15.36.03Screen Shot 2019-04-12 at 15.36.38

We had the script set up so that a log was created once successfully publishing the new ‘Action’ and ‘Community’ pages, and that the newly-published pages were linked from the new multi-reference pages field of the parent campaign.

Wrapping up

Because of Contentful, we’re able to organize, manage and display our content in a way that’s superior to how we did it in our previous implementation. There are so many cool ways we could customize and develop so that Contentful works with our organization and campaigns, instead of us having to work around a rigid CMS. I hope you learned something and if you have any questions, please feel free to reach out to me.

About the author

Don't miss the latest

Get updates in your inbox
Discover how to build better digital experiences with Contentful.
add-circle arrow-right remove style-two-pin-marker subtract-circle remove