Creating the Cosmic Contentful Importer

Community Articles
Community Articles Creating the Cosmic Contentful Importer

The climb is worth it. Photo by Jerry Zhang on Unsplash.


Install the Contentful Importer Extension
View the codebase on GitHub

Creating a Cosmic extension was an easy and enjoyable experience. I made an extension to import data from Contentful to Cosmic in a matter of hours. My extension takes the JSON file the Contentful CLI gives you and transforms it to Cosmic data objects. It even transfers media from Contentful to Cosmic.

For an overview of how to use the extension check out the other article I wrote, Importing Data from Contentful to Cosmic.

When creating a Cosmic extension you have two options, making a page to be served in an iframe or a Jamstack app. After some consideration I choose a Jamstack app. It is essentially a React component and some data services. If I made a server-side extension I’d need to worry about hosting, server maintenance, etc. In my case the only data API needed is Cosmic’s. JavaScript’s File API made parsing Contentful’s JSON right in the browser very easy.

Because Cosmic provides the information you need for accessing the API as query parameters my page is very simple. It is just some text, a file input, and a button. Progress and output are piped to the React component from my services.

To get started I created a react workspace:

$ npx create-react-app cosmic-contentful-importer

Next, Cosmic offers a template ( ) that includes an HTML file and a JSON file you need for uploading your extension to your app. Either download or clone the repo and add the files to the root of your react workspace.

Off the bat you can delete these two files:


Next, change the extension.json file to specify the app's title, icon, logo and repository URL:

  "title": "Contentful Importer",
  "font_awesome_class": "fa-download",
  "image_url": "",
  "repo_url": ""

Now that we're done with initial configuration we can get to coding. 

We'll add 3 services in the "src" folder: 

contentful.service.js // For contentful
cosmic.service.js // For cosmic 
importer.service.js // For combining cosmic + contentful

Now we'll head to index.js to create a react component to consume the services. 

First we need a way to get the slug and keys from the URL so define this function: 

const getParam = param => {
  var urlParams = new URLSearchParams(;
  return urlParams.get(param);

Now when we define the constructor for our component we can set the state based on the params:

constructor() {

    this.state = {
      file: null,
      slug: getParam("bucket_slug"),
      read_key: getParam("read_key"),
      write_key: getParam("write_key"),
      errorMessage: false,
      progress: false,
      loading: false,
      messages: []

For the sake of brevity and readability we'll skip creating the render method but you can find It here (). The most important elements on the page are the file input and run button which run setFile and parseFile respectively.

When the file Input changes, it updates the state with the file:

setFile(e) {
    const file =[0];

    this.setState({ ...this.state, file });

When the button is clicked the file is retrieved from state and passed to the importer service: 

  parseFile() {
    try {
        messages: [],
        loading: true

      const { file } = this.state;

      if (!file) {
        throw new Error("No file provided");

        m => this.progressCallback(m),
        e => this.errorCallback(e),
        () => this.completeCallback(),
        m => this.messageCallback(m)
    } catch (e) {

In the importer service the loadContentfulContent method uses the FileReader API to parse the JSON file then passes off the contents to another method:

loadContentfulContent(file, onProgress, onError, onComplete, onMessage) {
    const reader = new FileReader();

    reader.readAsText(file, 'UTF-8');

    reader.onload = e => {
      try {
        const content =;

        const json = JSON.parse(content);

        this._parseContent(json, onProgress, onError, onComplete, onMessage);
      } catch(e) {

The parseContent method will consume the Contentful and Cosmic services we created above to first format the contentful data then send it over to cosmic. Along the way it will pass progress messages to the callback methods provided to display progress in the UI:

  async _parseContent(content, onProgress, onError, onComplete, onMessage) {
      // Checks object properties to ensure compatibility 
      onProgress('Content valid. Parsing...');

      // Parses contentful object types to cosmic object types 
      const fields = this.contentful.toCosmicObjectTypes(content.contentTypes);

      onProgress('Successfully parsed content types');
      // Creates object types in cosmic
      const cosmicObjectTypes = await this.cosmic.addObjectTypes(fields.contentTypes);

      onProgress('Successfully created content types');

      // Parses contentful media objects to cosmic media objects
      const media = await this.contentful.toCosmicMedia(content.assets, content.locales);

      onProgress('Successfully parsed media');

      onProgress('Uploading media to Cosmic...');

      const cosmicMedia = await this.cosmic.addMediaObjects(media);

      // Check for failed uploads, usually due to file sizes and alert user
      cosmicMedia.forEach(media => {
        if (media.failed) {
          onMessage(`Failed to upload image: ${media.file.metadata.title} - ${media.file.metadata.originalUrl}`);

      onProgress('Successfully created media');
      // Parse posts and other objects to cosmic objects
      const parsedObjects = this.contentful.toCosmicObjects(

      onProgress('Successfully parsed entries');

      // Create posts and other objects in cosmic
      const cosmicObjects = await this.cosmic.addObjects(parsedObjects);

      onProgress('Successfully created objects');

    } catch(e) {

There isn't a 1:1 conversion of Contentful objects to Cosmic objects so we need some logic in contentful.service.js to determine the correct cosmic type for an object:

function getMetafieldType(type) {
  if (
    type === "Symbol" ||
    type === "Boolean" ||
    type === "Object" ||
    type === "Location"
  ) {
    return "text";
  } else if (type === "RichText") {
    return "html-textarea";
  } else if (type === "Text") {
    return "markdown";
  } else if (type === "Number" || type === "Integer" || type === "Decimal") {
    return "number";
  } else if (type === "Date") {
    return "date";
  } else if (type === "Asset") {
    return "file";
  } else if (type === "Link") {
    return "object";
  } else if (type === "Array") {
    return "objects";

There is no concept of "Symbol", "Boolean", "Object" or "Location" (a lat/lng JSON object) in Cosmic so they will be stored as stringified versions. Both markdown and text can be stored in "Text" so we'll set those to markdown to be safe. Types of Link and Array are stored as objects. The remaining field types have a close mapping as seen above.

Finally, to bundle the react app into a Cosmic app, we'll write a small build command and add it to "scripts" in package.json:

"deploy": "npm run build && cp extension.json build/extension.json && zip -r build",

I hope you enjoyed this tutorial about how to add an Extension to your Cosmic Bucket. If you have any questions. Connect with the Cosmic community on Slack and on Twitter.

You may also like

In this tutorial, I'll show you how to deploy your Gatsby Blog to Netlify and trigger automatic rebuilds on content edits using Cosmic Webhooks.
A primary benefit of managing content API-first is that your content is transformed into portable and scalable content objects, available via API endpoint to create workflows with the third-party applications that your team uses.
In this article I’m going demonstrate how to publish new Objects containing survey responses in Cosmic as they are completed in Typeform. Click here to connect Typeform to Cosmic using Zapier.
Cosmic makes it easy to manage content for your Bootstrap applications. In this blog we'll quickstart a Bootstrap Landing Page using the Cosmic CLI. This single page website landing page is built using Node.js and a minimal theme from Start Bootstrap.
Build a documentation app in a afternoon leveraging the powered of Gatsby and Cosmic.
5 Website Boilerplates: React, Node.js, Vue, Nuxt.js and Next.js

Get Started with Cosmic

Build personal projects for free. Add your team at unbeatable prices.
Start Building Talk to Sales