Was this page helpful?

Building your first app

Here you will learn step-by-step how to build your first Contentful app, plus the underlying technology.

Table of contents

Getting up and running

An organization management role is required Most of the actions described in this tutorial can only be performed by organization owners, admins or developers.

In the first part of this tutorial, we setup a basic app in your Contentful organization without explaining the details of the implementation. In the next part we take a look under the hood.

The app we'll be using is called "Daily Animal". It will display a random image of a selected animal in the entry editor to make content editing even more enjoyable.

Daily Animal app: entry editor view

Clone and host the example app

The code of the example app is hosted on Glitch. To create your own copy, click on Remix to Edit in the window below:

Create the AppDefinition for your app

To make an app available in your organization, create a new AppDefinition entity. You'll need:

Once you have all of the above items, issue the following API call with curl:

curl -X POST \
  -H'Content-Type: application/json' \
  -H'Authorization: Bearer YOUR-CMA-TOKEN-GOES-HERE' \
  -d'{"name": "Daily Animal", "src": "YOUR-GLITCH-URL-GOES-HERE", "locations": [{"location": "app-config"}, {"location": "entry-sidebar"}]}' \
  https://api.contentful.com/organizations/YOUR-ORG-ID-GOES-HERE/app_definitions

Use your app in the web app

Navigate to the Apps view in Contentful web app. Your app will be listed under available apps and marked by a PRIVATE tag:

Daily Animal app: listing

Select your created app to open its configuration screen. Pick a kind of animal and click "Install".

Daily Animal app: configuration

Voilà! Your app is now installed!

Code breakdown

The entire Daily Animal app fits in one file and is around 80 lines of code. Let's analyze how the app is implemented.

Libraries used

  • React: we use React to render views of the app
  • UI extensions SDK: because the app operates in the Contentful web app, we can use the UI extensions SDK and all of the methods it offers
  • Forma 36: to achieve the same look and feel of the Contentful web app, we use Forma 36, the Contentful's design system
import React, { Component } from 'react';
import { render } from 'react-dom';

import { init, locations } from 'contentful-ui-extensions-sdk';
import '@contentful/forma-36-react-components/dist/styles.css';
import '@contentful/forma-36-fcss/dist/styles.css';
import { Heading, Note, Form, SelectField, Option } from '@contentful/forma-36-react-components';

Initialization and location detection

To begin with, we use the init method of the UI extensions SDK to establish a connection between the Daily Animal app and the Contentful web app. We check if the app is rendered in the apps view with sdk.location.is(locations.LOCATION_APP_CONFIG). If so, we use the Config component, which renders the configuration screen. If we are in another location, we use the AnimalPicture component which shows a picture.

init(sdk => {
  // Select a component depending on a location in which the app is rendered.
  const Component = sdk.location.is(locations.LOCATION_APP_CONFIG) ? Config : AnimalPicture;

  // Render the component, passing the SDK down.
  render(<Component sdk={sdk} />, document.getElementById('root'));

  // Make sure there's always enough of space to render the view.
  sdk.window.startAutoResizer();
});

App configuration screen

Of the components making up the app, the Config component is the most complex one. It is responsible for rendering the configuration screen of the app, modifying its settings and handling the installation flow.

Two important notes about the app-config location:

  1. You must call sdk.app.setReady when you are ready to display your app. In the example below, this method is called after we have loaded some data asynchronously. Failing to call this method will result in your app not rendering!
  2. You should provide a callback to sdk.app.onConfigure. This callback will be invoked when the user has set their desired configuration and clicks the "Install/Save" button. This will allow you to validate and save installation parameters set by the user.

The other method used is getParameters which allows the app to obtain its current parameters.

class Config extends Component {
  constructor (props) {
    super(props);
    this.state = { parameters: {} };

    // `sdk.app` exposes all app-related methods.
    this.app = this.props.sdk.app;

    // `onConfigure` allows to configure a callback to be
    // invoked when a user attempts to install the app or update
    // its configuration.
    this.app.onConfigure(() => this.onConfigure());
  }

  async componentDidMount () {
    // Get current parameters of the app.
    const parameters = await this.app.getParameters();

    this.setState(
      // If the app is not installed, `parameters` will be `null`.
      // We default to an empty object in this case.
      { parameters: parameters || {} },
      () => {
        // Once preparation has finished, call `setReady` to hide
        // the loading screen and present the app to a user.
        this.app.setReady()
      }
    );
  }

  // Renders the UI of the app.
  render () {
    return (
      <Form id="app-config">
        <Heading>Daily Animal app</Heading>
        <Note noteType="primary" title="About the app">
          Make editors in this space a little bit happier with a cute animal picture in the entry editor sidebar.
        </Note>
        <SelectField
          required
          name="animal-selection"
          id="animal-selection"
          labelText="Animal"
          value={this.state.parameters.animal || DEFAULT_ANIMAL}
          onChange={e => this.setState({ parameters: { animal: e.target.value } })}
        >
          <Option value={DEFAULT_ANIMAL}>Cat</Option>
          <Option value="dog">Dog</Option>
          <Option value="owl">Owl</Option>
        </SelectField>
      </Form>
    );
  }

  // This method will be called when a user clicks on "Install"
  // or "Save" in the configuration screen.
  //
  // We want to do two things in here:
  // 1. Persist selected animal as a parameter
  // 2. Put the app to sidebars of all Content Types
  async onConfigure () {
    // Get IDs of all content types in an environment.
    const { items: contentTypes } = await this.props.sdk.space.getContentTypes();
    const contentTypeIds = contentTypes.map(ct => ct.sys.id)

    // Return value of `onConfigure` is used to install
    // or update the configuration.
    return {
      // Parameters to be persisted as the app configuration.
      parameters: this.state.parameters,
      // Transformation of an environment performed in the
      // installation process.
      targetState: {
        EditorInterface: contentTypeIds.reduce((acc, id) => {
          // Insert the app as the first item in sidebars
          // of all content types.
          return { ...acc, [id]: { sidebar: { position: 0 } } }
        }, {})
      }
    };
  }
}

App sidebar widget

This component renders an animal picture. It reads the selected animal from app parameters and uses Unsplash Source to embed a photo.

function AnimalPicture ({ sdk }) {
  const animal = sdk.parameters.installation.animal || DEFAULT_ANIMAL;
  const src = `https://source.unsplash.com/250x250/?${animal}`;

  return <img alt={animal} id="animal-picture" src={src} />
}