Was this page helpful?

Create a custom app

Follow the steps below to get your app up and running almost immediately. For detailed explanations on a specific step, click the questionmark icon.

Set up project ?

  1. Run
    npx create-contentful-app my-first-app
  2. Select blank template.
  3. Select JavaScript.
  4. Wait for create-contentful-app to finish generating your project.

Start development server

  1. Run
    cd my-first-app
  2. Run
    npm run start

Open file

  • Open src/locations/Sidebar.jsx in your editor

Replace contents ?

Replace the contents of the file with the following code snippet:

import React, { useState, useEffect } from 'react';
import { List, ListItem, Note } from '@contentful/f36-components';
import { useSDK } from '@contentful/react-apps-toolkit';

const CONTENT_FIELD_ID = 'body';
const WORDS_PER_MINUTE = 200;

const Sidebar = () => {
  const sdk = useSDK();

  const contentField = sdk.entry.fields[CONTENT_FIELD_ID];
  const [blogText, setBlogText] = useState(contentField.getValue());

  // Listen for onChange events and update the value
  useEffect(() => {
    const detach = contentField.onValueChanged((value) => {
      setBlogText(value);
    });
    return () => detach();
  }, [contentField]);

  const readingTime = (text) => {
    const wordCount = text.split(' ').length;
    const minutes = Math.ceil(wordCount / WORDS_PER_MINUTE);
    return {
      words: wordCount,
      text: `${minutes} min read`,
    };
  };

  // Calculate the metrics based on the new value
  const stats = readingTime(blogText || '');

  // Render the metrics with Forma36 components
  return (
    <>
      <Note style={{ marginBottom: '12px' }}>
        Metrics for your blog post:
        <List style={{ marginTop: '12px' }}>
          <ListItem>Word count: {stats.words}</ListItem>
          <ListItem>Reading time: {stats.text}</ListItem>
        </List>
      </Note>
    </>
  );
};

export default Sidebar;

Create your app definition ?

  1. Go to the management view, navigate to "Apps" and click "Create App"
  1. Enter "Blog Post Metrics" in the Name field.
  2. Enter "http://localhost:3000" under "Frontend" area.
  3. Check Entry Sidebar under the "Locations" area.
  4. Click Save.

Install your app to a space ?

  1. In the Contentful web app, navigate to the space in which you want to install the app.
  2. Click the Apps tab and select Custom apps.
  3. Click on the actions menu of your app and select Install. Install app 2
  4. In the "Manage app access" window, click Authorize access.

Assign your app to a content type ?

  1. In the Contentful web app, go to the Content model tab and click the "Blog post" content type.
  2. Go to the Sidebar tab. Install app 5
  3. Click the + button in the "Blog Post Metrics" card.
  4. Click Save to apply changes to the content type.

See your app in action

Your app is built. To see it in action, go to the Content tab and create an entry with the content type Blog post. You can see your app in the entry editor sidebar.

Sidebar Content Type

The example app you are about to build

In this tutorial, we use an example of a custom app called Blog Post Metrics that provides the editors with word count and reading time indicator. These metrics are set to be displayed in the entry editor sidebar for a "Blog post" content type:

The full example app code can be downloaded from"Blog Post Metrics" app reference.

Setup

  • The latest LTS version of Node.js installed on your machine. If you don't have Node.js installed, you can download it here.
  • Be logged in to your Contentful account and have a Contentful space. If you don't have an account, sign up for it.
  • Have a content type "Blog post" set up with fields of types "Text" and "Long text". To learn how to create a content type, refer to Add a content type.
Make sure you name your “Long text” field “Body” so it has a field ID “body”.

Knowledge

  • Read and write JavaScript.

  • Be familiar with basic React.

  • Be familiar with Content modeling.

Create your project

To create a project for your app:

  1. In your terminal, run the following command:
npx create-contentful-app my-first-app
To see all the available options of create-contentful-app CLI tool, check Create Contentful app reference.

After some time the script is ready and will ask you a couple of questions.

  1. Select blank template.

  2. Select the preferred programming language - either TypeScript or JavaScript.

The create-contentful-app CLI tool is initializing your new project.

  1. Navigate to the newly created folder and start the app by running the following command:
cd my-first-app
npm run start

Your app is hosted on http://localhost:3000. But before we can use it we need to connect this to the Contentful web app which we will do in a later step

Retrieve, calculate and display metrics in your app

In this subtopic, we explain how to set up the "Blog Post Metrics" app to retrieve and calculate metrics and display it to users in an entry. Our example "Blog Post Metrics" app is intended to run in the "Entry sidebar" location, this is why we edit src/locations/Sidebar.tsx. This file represents the sidebar location in the Contentful web app.

The app location is defined in Create your app definitions step of this tutorial.
For more details about the app locations, see App locations.

Interact with the data in the Contentful web app

In this step, we set up our "Blog Post Metrics" app to listen to the changes in an entry field of the Contentful web app using the App SDK. To interact with data in Contentful, we use the useSDK hook.

To interact with the Contentful web app, we use a subset of the App SDK methods. To learn about all App SDK methods, see App SDK reference documentation.

To retrieve a value from a field for further metrics calculation:

  1. Open src/locations/Sidebar.tsx
  2. Copy the following code:
import React, { useState } from 'react';
import { Paragraph } from '@contentful/f36-components';
import { useSDK } from '@contentful/react-apps-toolkit';

const CONTENT_FIELD_ID = 'body';

const Sidebar = () => {
  // The sdk allows us to interact with the Contentful web app
  const sdk = useSDK();

  // With the field ID we can reference individual fields from an entry
  const contentField = sdk.entry.fields[CONTENT_FIELD_ID];

  // Get the current value from the blog post field and store it in React state
  const [blogText, setBlogText] = useState(contentField.getValue());

  return <Paragraph>Hello Sidebar Component</Paragraph>;
};

export default Sidebar;
import React, { useState } from 'react';
import { Paragraph } from '@contentful/f36-components';
import { SidebarExtensionSDK } from '@contentful/app-sdk';
import { useSDK } from '@contentful/react-apps-toolkit';

const CONTENT_FIELD_ID = 'body';

const Sidebar = () => {
  // The sdk allows us to interact with the Contentful web app
  const sdk = useSDK<SidebarExtensionSDK>();

  // With the field ID we can reference individual fields from an entry
  const contentField = sdk.entry.fields[CONTENT_FIELD_ID];

  // Get the current value from the blog post field and store it in React state
  const [blogText, setBlogText] = useState(contentField.getValue());

  return <Paragraph>Hello Sidebar Component</Paragraph>;
};

export default Sidebar;

In the component above, the following fields are used:

  • sdk.entry.fields — An object that holds all the fields that are in an entry.
  • fieldId — An individual ID of a field that is defined for the app to listen to. In our example, the field ID is body as we specified it when creating our content type.
    Use the `getValue` method to retrieve the current value from the field.

Additionally, the following methods and/or data can be used:

  • onChange — Listen to the events from the field to calculate the wireframe metrics as soon as a user starts entering text in a "Blog post" entry.
  • field API — Leverage it to listen for value changes the app needs. It is provided by useSDK-hook.
  • onValueChanged — Use this method to listen for any content updates on the landing page.
  • readingTime — Call this method to take the value and calculate our desired metrics. The method is created in Add the reading-time method to our Sidebar component step of this tutorial.

Add metrics calculation to your app

In this step, we set up the blog post to be provided with the word count and the reading time metrics. To calculate those, run the following function:

const readingTime = (text) => {
  const wordCount = text.split(' ').length;
  const minutes = Math.ceil(wordCount / WORDS_PER_MINUTE);
  return {
    words: wordCount,
    text: `${minutes} min read`,
  };
};
const readingTime = (text: string): { words: number; text: string } => {
  const wordCount = text.split(' ').length;
  const minutes = Math.ceil(wordCount / WORDS_PER_MINUTE);
  return {
    words: wordCount,
    text: `${minutes} min read`,
  };
};

Our methodreadingTime uses the value set in WORDS_PER_MINUTE to calculate the amount of minutes one spends to read the text. You can change this value in the Add the reading-time method to our Sidebar component step of the tutorial.

Add the reading-time method to our Sidebar component

In this step, we set the calculated metrics to be displayed in the entry sidebar. For this, we import the following Forma 36 components: List, ListItem and Note.

Use Contentful design system Forma 36 to give your app the look and feel of the Contentful web app.

For the calculated metrics to be displayed in the entry sidebar:

  1. Open src/locations/Sidebar.tsx.
  2. Copy the following code:
import React, { useState, useEffect } from 'react';
import { List, ListItem, Note } from '@contentful/f36-components';
import { useSDK } from '@contentful/react-apps-toolkit';

const CONTENT_FIELD_ID = 'body';
const WORDS_PER_MINUTE = 200;

const Sidebar = () => {
  const sdk = useSDK();

  const contentField = sdk.entry.fields[CONTENT_FIELD_ID];
  const [blogText, setBlogText] = useState(contentField.getValue());

  // Listen for onChange events and update the value
  useEffect(() => {
    const detach = contentField.onValueChanged((value) => {
      setBlogText(value);
    });
    return () => detach();
  }, [contentField]);

  const readingTime = (text) => {
    const wordCount = text.split(' ').length;
    const minutes = Math.ceil(wordCount / WORDS_PER_MINUTE);
    return {
      words: wordCount,
      text: `${minutes} min read`,
    };
  };

  // Calculate the metrics based on the new value
  const stats = readingTime(blogText || '');

  // Render the metrics with Forma36 components
  return (
    <>
      <Note style={{ marginBottom: '12px' }}>
        Metrics for your blog post:
        <List style={{ marginTop: '12px' }}>
          <ListItem>Word count: {stats.words}</ListItem>
          <ListItem>Reading time: {stats.text}</ListItem>
        </List>
      </Note>
    </>
  );
};

export default Sidebar;
import React, { useState, useEffect } from 'react';
import { SidebarExtensionSDK } from '@contentful/app-sdk';

import { List, ListItem, Note } from '@contentful/f36-components';
import { useSDK } from '@contentful/react-apps-toolkit';

const CONTENT_FIELD_ID = 'body';
const WORDS_PER_MINUTE = 200;

const Sidebar = () => {
  const sdk = useSDK<SidebarExtensionSDK>();

  const contentField = sdk.entry.fields[CONTENT_FIELD_ID];
  const [blogText, setBlogText] = useState(contentField.getValue());

  // Listen for onChange events and update the value
  useEffect(() => {
    const detach = contentField.onValueChanged((value) => {
      setBlogText(value);
    });
    return () => detach();
  }, [contentField]);

  // example implementation of a reading time calculator
  const readingTime = (text: string): { words: number; text: string } => {
    const wordCount = text.split(' ').length;
    const minutes = Math.ceil(wordCount / WORDS_PER_MINUTE);
    return {
      words: wordCount,
      text: `${minutes} min read`,
    };
  };

  // Calculate the metrics based on the new value
  const stats = readingTime(blogText || '');

  // Render the metrics with Forma36 components
  return (
    <>
      <Note style={{ marginBottom: '12px' }}>
        Metrics for your blog post:
        <List style={{ marginTop: '12px' }}>
          <ListItem>Word count: {stats.words}</ListItem>
          <ListItem>Reading time: {stats.text}</ListItem>
        </List>
      </Note>
    </>
  );
};

export default Sidebar;

Embed your app in the Contentful web app

To see your app running in the Contentful web app, you must create an app definition to expose the app to Contentful. An app definition is an entity that represents an app in Contentful and stores general information about it.

Create your app definition

To create an app definition:

Make sure your app is running on http://localhost:3000 and you are logged in to the Contentful web app.
  1. Go to the Apps tab under your organization settings:
  1. Click Create app. The "App details" page is displayed.

Create app definition 1

  1. In the Name field, enter a custom name for your app. For our example app, we use Blog Post Metrics as a name.

  2. In the Frontend field, enter URL where your app is hosted. Our example app is hosted locally for now, so the URL for it is http://localhost:3000.

Create app definition 2

  1. Under the Locations area, select a checkbox against a location (one or multiple) for your app. For our example app, we would like the metrics for the blog posts to be displayed in the sidebar of the entry editor, so we select the location Entry sidebar.
App locations define where an app is displayed in the Contentful web app. For more details, see our app location documentation, which explains the benefits of rendering your app in different locations.
  1. Click Save. Your app definition is saved.

Create app definition 3

Install your app to a space

After the app definition is created, install your app to a space:

  1. In the Contentful web app, navigate to the required space.

  2. In the top pane, click Apps and select Custom apps. The “Custom apps” page is displayed.

Install app 1

  1. Go to your newly created app, click on the actions menu icon and select Install.

Install app 2

The “Manage app access” window is displayed.

  1. Click the Environments field and select the checkbox against an environment (one or multiple) in which you would like the app to be installed and click "Authorize Access". Your app is installed to the selected environment (one or multiple).

Install app 3

An app is able to access the data across the environments it is installed in.
To install the app in the environment(s) belonging to a different space, switch to the required space and repeat the installation process.

Assign your app to a content type

After your app is installed, assign it to "Blog post" content type.

To assign an app to a content type:

  1. Go to the Content model tab and click the "Blog post" content type.
  2. Go to the Sidebar tab.
  3. Under the "Available items" area, scroll down to the Blog Post Metrics card and click the + button. The app is added to the sidebar.
  4. Click Save to apply changes to the content type.

See your app in action

You have successfully created your custom app. Now, let's see it in action.

Go to the Content tab and create an entry with the content type Blog post. You should see a sidebar app now in your entry editor.

Sidebar Content Type

Next steps

More custom app examples

Explore more custom app examples and templates on GitHub: Examples apps and Templates using App Framework on GitHub.

Read blog posts about building custom apps with the App Framework:

Additional resources