✨ Strapi MCP is now Generally Available - let your agents manage your Strapi content ✨

How tos7 min read

How to create an import content plugin (part 4/4)

December 12, 2019Updated on May 22, 2026
How to create an import content plugin (part 4/4)

This tutorial is part of the « How to create your own plugin »:

Please note: Since we initially published this blog post, we released new versions of Strapi and tutorials may be outdated. Sorry for the inconvenience if it's the case. Please help us by reporting it here.

Table of contents

You can reach the code for this tutorial at this link

HistoryPage

  • Go to containers directory and create a new directory named HistoryPage with an empty index.js file inside:

admin/src/containers/HistoryPage/index.js

Before we dive into writing our HistoryPage, we need to introduce a route to this new page.

  • Go to App directory inside containers directory and add the following route to the index.js file:
...
<Route
 path={`/plugins/${pluginId}/history`}
 component={HistoryPage}
 exact
/>
...

Notice that NotFound is placed after our HistoryPage and don’t forget to import our container at the top of this file:

  • import HistoryPage from "../HistoryPage";

  • Go back to our HistoryPage directory and paste the below code inside index.js:

/* * * HistoryPage * */
import React, { Component } from "react";
import {
  HeaderNav,
  LoadingIndicator,
  PluginHeader,
  request
} from "strapi-helper-plugin";
import pluginId from "../../pluginId";
import Row from "../../components/Row";
import Block from "../../components/Block";

const getUrl = to =>
  to ? `/plugins/${pluginId}/${to}` : `/plugins/${pluginId}`;

class HistoryPage extends Component {}

export default HistoryPage;

We want this page to have a similar look with respect to our HomePage.

  • So we will render something like this:
render() {
    return (
      <div className={"container-fluid"} style={{ padding: "18px 30px" }}>
        <PluginHeader
          title={"Import Content"}
          description={"Import CSV and RSS-Feed into your Content Types"}
        />
        <HeaderNav
            links={[
              {
                name: "Import Data",
                to: getUrl("")
              },
              {
                name: "Import History",
                to: getUrl("history")
              }
            ]}
            style={{ marginTop: "4.4rem" }}
        />
        <div className="row">
          <Block
            title="General"
            description="Manage the Initiated Imports"
            style={{ marginBottom: 12 }}
          />
        </div>
      </div>
    );
}

What we need next, is a table that renders all the “import configs” available.

  • Go to components directory and create a new directory named HistoryTable with an empty index.js file inside:

admin/src/components/HistoryTable/index.js

  • Open the index.js file and paste the below code:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Table, Button } from "@buffetjs/core";
import moment from "moment";
import { LoadingIndicator, PopUpWarning } from "strapi-helper-plugin";
class HistoryTable extends Component {
  state = {
    showDeleteModal: false,
    showUndoModal: false,
    importToDelete: null,
    importToUndo: null
  };
}
HistoryTable.propTypes = {
  configs: PropTypes.array.isRequired,
  deleteImport: PropTypes.func,
  undoImport: PropTypes.func
};
export default HistoryTable;

In our state, we are defining a couple of variables that are responsible for showing the delete & undo dialogs and in case the user confirms the delete or undo dialogs, we must know which “import config” we are supposed to remove or undo.

  • So the following functions will do the job. Add them just after your state:
...
  deleteImport = id => {
    this.setState({ showDeleteModal: true, importToDelete: id });
  };
  undoImport = id => {
    this.setState({ showUndoModal: true, importToUndo: id });
  };
...

It’s time to use the above functions. For our table we are about to use “Buffet js” table with “custom row” just like what we did for MappingTable.

  • Append the below code as our “custom row” to the class definition just after your state:
  CustomRow = ({ row }) => {
    const { id, contentType, importedCount, ongoing, updated_at } = row;
    const updatedAt = moment(updated_at);
    let source;
    switch (row.source) {
      case "upload":
        source = row.options.filename;
        break;
      case "url":
        source = row.options.url;
        break;
      default:
        source = "unknown";
    }
    return (
      <tr style={{ paddingTop: 18 }}>
        <td>{source}</td> <td>{contentType}</td>
        <td>{updatedAt.format("LLL")}</td> <td>{importedCount}</td>
        <td>{ongoing ? <LoadingIndicator /> : <span>Ready</span>} </td>
        <td>
          <div className={"row"}>
            <div
              style={{
                marginRight: 18,
                marginLeft: 18
              }}
              onClick={() => this.undoImport(id)}
            >
              <i className={"fa fa-undo"} role={"button"} />
            </div>
            <div onClick={() => this.deleteImport(id)}>
              <i className={"fa fa-trash"} role={"button"} />
            </div>
          </div>
        </td>
      </tr>
    );
  };

We are rendering the details of each “import config” as a row in our table. Also we are showing some buttons for delete & undo actions as well.

  • Finally, our render method:
render() {
    const { configs } = this.props;
    const props = {
      title: "Import History",
      subtitle: "Manage the Initiated Imports"
    };
    const headers = [
      { name: "Source", value: "source" },
      { name: "Content Type", value: "contentType" },
      { name: "Updated At", value: "updatedAt" },
      { name: "Items", value: "items" },
      { name: "Progress State", value: "progress" },
      { name: "Actions", value: "actions" }
    ];
    const items = [...configs];
    const {
      importToDelete,
      importToUndo,
      showDeleteModal,
      showUndoModal
    } = this.state;
    return (
      <div className={"col-md-12"} style={{ paddingTop: 12 }}>
        <PopUpWarning
          isOpen={showDeleteModal}
          toggleModal={() => this.setState({ showDeleteModal: null })}
          content={{
            title: `Please confirm`,
            message: `Are you sure you want to delete this entry?`
          }}
          popUpWarningType="danger"
          onConfirm={async () => {
            importToDelete && (await this.props.deleteImport(importToDelete));
          }}
        />
        <PopUpWarning
          isOpen={showUndoModal}
          toggleModal={() => this.setState({ showUndoModal: null })}
          content={{
            title: `Please confirm`,
            message: `Are you sure you want to undo this entry?`
          }}
          popUpWarningType="danger"
          onConfirm={async () => {
            importToUndo && (await this.props.undoImport(importToUndo));
          }}
        />
        <Table
          {...props}
          headers={headers}
          rows={items}
          customRow={this.CustomRow}
        />
      </div>
    );
}

Notice how “Strapi Helper Plugin” PopUpWarning made it easy for us to show a beautiful confirm dialog for the delete & undo actions. Our table is ready!

  • Go back to our HistoryPage and add the following import at the top:

import HistoryTable from "../../components/HistoryTable";

  • then, define the state as below:
...
state = {
 loading: false,
 importConfigs: []
};
...

As you know we must pass the list of “import configs” down to our HistoryTable.

  • For fetching the list of “import configs” we use the following method. Add it just after your state:
...
getConfigs = async () => {
    try {
      const response = await request("/import-content", { method: "GET" });
      return response;
    } catch (e) {
      strapi.notification.error(`${e}`);
      return [];
    }
};
...
  • to actually call this method we use the following method. Add it just after your state:
...
importConfigs() {
    if (!this.state.loading) {
      this.getConfigs().then(res => {
        this.setState({ importConfigs: res });
      });
    }
}
...

The reason behind this method is that we are going to periodically fetch the list of “import configs” for having an up to date list. When the user goes to our HistoryPage for the first time, we will fetch the list directly by calling getConfigs method and after that we are going to periodically call the importConfigs method for the rest of our lives!

  • To understand this better, see the following functions and add them to your file one at the time:
...
componentDidMount() {
    this.getConfigs().then(res => {
      this.setState({ importConfigs: res, loading: false });
    });
    setTimeout(() => {
      this.fetchInterval = setInterval(() => this.importConfigs(), 4000);
    }, 200);
}
...
  • We will clean our setInterval function call by the below life cycle hook which is called before unmounting the component:
...
componentWillUnmount() {
    if (this.fetchInterval) {
      clearInterval(this.fetchInterval);
    }
}
...

beside the importConfigs, we will pass 2 methods for delete & undo operations down to our HistoryTable.

  • For the delete operation:
...
deleteImport = async id => {
      this.setState({ loading: true }, async () => {
      try {
        await request(`/import-content/${id}`, { method: "DELETE" });

        this.setState(prevState => ({
          ...prevState,
          importConfigs: prevState.importConfigs.filter(imp => imp.id !== id),
          loading: false
        }));

        strapi.notification.success(`Deleted`);
      } catch (e) {
        this.setState({ loading: false }, () => {
          strapi.notification.error(`${e}`);
          strapi.notification.error(`Delete Failed`);
        });
      }
    });
};
...
  • For the undo operation:
...
undoImport = async id => {
    this.setState({ loading: true }, async () => {
      await request(`/import-content/${id}/undo`, { method: "POST" });
      this.setState({ loading: false }, () => {
        strapi.notification.info(`Undo Started`);
      });
    });
};
...
  • Change the render method output as below:
 		 <Block
            title="General"
            description="Manage the Initiated Imports"
            style={{ marginBottom: 12 }}
          >
            {this.state.loading && <LoadingIndicator />}
            {!this.state.loading && this.state.importConfigs && (
              <Row className={"row"}>
                <HistoryTable
                  undoImport={this.undoImport}
                  deleteImport={this.deleteImport}
                  configs={this.state.importConfigs}
                />
              </Row>
            )}
         </Block>

Before visiting the plugin page, make sure that you have allowed public access on all the endpoints of our plugin:

Congratulations! If you have made it this far, you are ready to do some awesome things with Strapi plugins.

In this tutorial we have covered the basics as much as we could, what has been left is to integrate Redux into our plugin, writing tests for our plugin and some other tiny details. So keep an eye on our blog for the upcoming tutorials. Thanks to all of you guys! and special thanks to:

Soupette for his kind edits, testing's and suggestions on the react side to make the code as robust as possible!

Maxime Castres for his kind edits, testing's, suggestions and rewriting the whole tutorial from scratch in the Markup format. This tutorial would not make it without him!

Joe Beuckman who wrote this amazing plugin at the first place. In fact I learned a lot from his plugin myself and appreciate the effort he put into this plugin so far!

Hail Strapi!

One last thing, we are trying to make the best possible tutorials for you, help us in this mission by answering this short survey https://strapisolutions.typeform.com/to/bwXvhA?channel=xxxxx

Pouya MiralayiSenior Software Engineer @ Ertebat Gostar Khavarmiane

Pouya is an active member of the Strapi community, who has been contributing actively with contributions in the core and plugins.

How tos·12 min read

How To Build A Static Blog Using Jekyll And Strapi

Learn to build a fast Jekyll static blog powered by Strapi v5 CMS. Step-by-step guide covering setup, API integration, and deployment.

·August 3, 2025
Build a Blog with Astro, Strapi, and Tailwind CSS
How tos·22 min read

How to Build a Blog with Astro, Strapi, and Tailwind CSS

In this tutorial, we will learn how to build a blogging application using Strapi as the CMS and Astro powered by React to build the frontend.

·October 20, 2023
Build a CRUD App with Flutter and Strapi
How tos·12 min read

How to Build a Simple CRUD Application Using Flutter & Strapi

In this tutorial, we will learn how to build a Create, Retrieve, Update and Delete (CRUD) application using Flutter and Strapi.

·August 23, 2023