Creating a Strapi Analytics Plugin

Strapi - Jul 7 '22 - - Dev Community

In this tutorial, you’ll learn how to create a plugin in Strapi by creating an analytics plugin.

Author: Girish Venkatachalam

In today's Jamstack model of web development, a headless content management system (CMS) can be a great choice to get an edge over the competition.

Strapi is an open source, headless CMS with a community of plug-in developers that helps avoid vendor and technology lock-in. It’s also database-independent and schema-independent, which simplifies content management and enables anyone with simple data entry skills to manage the content, eliminating the need for a database administrator or coding skills.

Strapi focuses on defining content types and relationships, and abstracting the storage that you can host on your own or on your cloud. If Strapi itself doesn't provide the functionality, you need to manage your data; you can usually find a pre-made plugin that does, and you can easily add such plug-ins to your setup.

There may also be times when you’re using a proprietary tool or library that may need to talk to the content in Strapi in unique ways. In these cases, developing your own plugin is the best option.

This article will show you how to develop a Strapi plugin for your personal use, or for distribution and integration with the Strapi marketplace.

The process can seem a little overwhelming due to the vast amounts of available information, lack of the process documentation, and underdeveloped code in GitHub repos. If you follow these instructions, however, you’ll be able to create an analytics plug-in that showcases how Strapi can be used for graphing and visualizing CMS content.

To build this plugin, you’ll need good Node.js skills and a knowledge of Express, Hapi or Koa routing, models, controllers, and middleware.

What is Strapi?

Strapi is used for managing your website content, including text, audio, video, and images. Strapi is headless, meaning there’s no frontend to display on a website.

You can store any form of content for the web, like audio podcasts, video podcasts, product-related videos or slideshows, blog images, and block diagrams, by creating adequate content types and storing them under different heads. As Strapi uses a backend database, schemas, etc., it hides all these details from end users.

You can use Strapi’s web interface to add, delete, or modify content, which can be served by using a REST API or GraphQL query. Once you extract the content, you’re free to display it using Angular, Vue, React, or any technology or frontend theming to render the content in the Strapi CMS. Think of Strapi as logic built around data and content. Strapi works seamlessly with the Jamstack methodology of website building and takes the hassle out of dealing with data.

Strapi can also be useful if you use an open source static site generator like Hugo, or you could use it to handle content abstraction while you focus on the JavaScript logic of your website. This can come in particularly handy in a world where nearly seventy percent of websites run on a monolith content management solution like Wordpress.

What Are Strapi Plugins?

Plugins extend functionality and add new features to software. For Strapi, plugins enhance the abstraction for databases, the relationships between data, the API endpoints, and provide components that can be used for your web application, mostly on the frontend.

The plugin architecture in Strapi is easy to explain, but much harder to code. A headless CMS means you have a repository and logic built around content, but you’re free to choose how you consume it, as API endpoints are available for you to integrate with your unique use case.

A Strapi plugin adds more logic to manipulate the data you choose to store. For example, you could create a plugin that analyzes content and visualizes it for computing averages, standard deviations, etc., instead of coding this into your frontend logic. A Strapi plugin can handle content-specific code, allowing you to focus on business logic and web design on your frontend.

There’s a wide variety of available Strapi plugins, but, as mentioned, it’s also possible to create your own. When creating your own plugins for Strapi, you should follow the Strapi Design System, which is a design specification that ensures uniformity and cohesiveness between plugins.

Building a Strapi Analytics Plugin

This article focuses on creating a data analytics plugin, which provides a method of analyzing and visualizing the various attributes of the content you populate in Strapi. Before you create this plugin, you need some understanding of Strapi's internal file structure and how things work inside Strapi.

Plugins reside inside the sec/plugins project directory, so it’s vital to know the file names, directory names, and other conventions.

Here’s an example of the file structure of a plugin named analytics:

cd src/plugins
tree 
Enter fullscreen mode Exit fullscreen mode
 └── analytics
     ├── admin
     │   └── src
     │       ├── components
     │       │   ├── Initializer
     │       │   │   └── index.js
     │       │   └── PluginIcon
     │       │       └── index.js
     │       ├── index.js
     │       ├── pages
     │       │   ├── App
     │       │   │   └── index.js
     │       │   └── HomePage
     │       │       └── index.js
     │       ├── pluginId.js
     │       ├── translations
     │       │   ├── en.json
     │       │   └── fr.json
     │       └── utils
     │           ├── axiosInstance.js
     │           └── getTrad.js
     ├── package.json
     ├── README.md
     ├── server
     │   ├── bootstrap.js
     │   ├── config
     │   │   └── index.js
     │   ├── content-types
     │   │   └── index.js
     │   ├── controllers
     │   │   ├── index.js
     │   │   └── my-controller.js
     │   ├── destroy.js
     │   ├── index.js
     │   ├── middlewares
     │   │   └── index.js
     │   ├── policies
     │   │   └── index.js
     │   ├── register.js
     │   ├── routes
     │   │   └── index.js
     │   └── services
     │       ├── index.js
     │       └── my-service.js
     ├── strapi-admin.js
     └── strapi-server.js

 19 directories, 27 files
Enter fullscreen mode Exit fullscreen mode

The above directory structure has been included to give you a brief preview on how the naming conventions of directories and source files affect plugin development.

The server and admin directories are meant to hold the backend and frontend of the plugin respectively. Since Strapi is headless, meaning there’s no frontend, we’re talking about the admin interface of Strapi. Your website frontend is totally different and Strapi does not concern itself with it.

Note that strapi-admin.js and strapi-server.js don’t need to be touched or modified. All we need to do is change the admin/pages/HomePage/index.js file for the pie chart and the bar chart.

We also have to change the plugin icon to align with the Strapi Design System; this enhances the look and feel of our plugin, giving it a unique appearance. It also serves as a mnemonic for users to figure out what your plug-in is about.

Step-by-Step Instructions for Building an Analytics Plugin in Strapi

You start by setting up a Strapi project with the command:

yarn create strapi-app strapi-graphing --quickstart
Enter fullscreen mode Exit fullscreen mode

After creating the project, a browser window will open and prompt you to create an admin account. After the admin account is created, you’ll be redirected to the dashboard.

The Strapi app will store some data, such as percentage of population in US states and annual budget spent by countries. The analytics plugin will be used to analyze the data and generate graphs for visualizing. Of course, this is just an example, and you’re free to have whatever data you want in your app.

You need to create two collection types: Budget and Population. Go to Plugins > Content-type builder and click Create new collection type. Enter Budget as the display name and add two fields:

  1. Country: a text field
  2. Spending: a float field

Budget collection type

Save this collection and add a second collection called Population with a text field State and a float field Percentage.

You can check the API endpoint:

curl http://localhost:1337/api/budgets
Enter fullscreen mode Exit fullscreen mode

You’ll encounter a 403 error saying permission denied. This is because, by default, Strapi doesn’t allow unauthenticated REST API access to the data. We need it for our plug-in and this is the standard practice too.

To fix it, go to Settings > User & Permissions Plugin > Roles and edit the Public role. Here, check the boxes next to the find and findOne actions for both Budget and Population.

Fixing permissions for API

Then, you must create a plugin. The one for this tutorial is called analytics.

Execute the command yarn strapi generate in the strapi-graphing directory, and choose the plugin as shown in the screenshot below.

Creating a plug-in

The goal of the analytics plug-in we’re developing is to show how Strapi CMS can be leveraged to do analytics on the content in the CMS. Typically, the content for websites is used to show text and media, but in our case, we want to show some graphs and charts to explain as part of a web page.

You must enable the plugin before it can be used. Create configs/plugins.js:

module.exports = {
  'analytics': {
    enabled: true,
    resolve: './src/plugins/analytics'
  },
}
Enter fullscreen mode Exit fullscreen mode

Changing the Plugin Icon

The src/plugins/analytics/admin/src/components/PluginIcon/index.js file can be used to change the plug-in icon. But first, you’ll need to install the @strapi/icons npm module. Go to the plug-in’s home directory:

cd src/plugins/analytics
Enter fullscreen mode Exit fullscreen mode

and add the module:

yarn add @strapi/icons
Enter fullscreen mode Exit fullscreen mode

There are a wide variety of icons to choose from. In this case, the equalizer icon seemed apt for an analytics plug-in. So you should change admin/src/components/PluginIcon/index.js to the following:

    /**
     *
     * PluginIcon
     *
     */

    import React from 'react';
    import Equalizer from '@strapi/icons/Equalizer';

    const PluginIcon = () => <Equalizer />;

    export default PluginIcon;
Enter fullscreen mode Exit fullscreen mode

You should now be able to see the plugin in the dashboard.

The plugin in the dashboard

Fix the Plugin Homepage to Show the Graphs and Analytics

Now it’s time to write the core of the plugin. The plugin will create visualizations based on the data in the CMS. First, you need to install the d3 and the react-faux-dom libraries. Make sure you’re in the plug-in home directory and run:

yarn add d3 d3-react react-faux-dom
Enter fullscreen mode Exit fullscreen mode

You’ll need to make some changes in the admin/src/pages/HomePage/index.js file. The first step is to import the modules and create the component:

import PropTypes from 'prop-types';
import pluginId from '../../pluginId';
import * as d3 from "d3";
import React, {
  Component
} from 'react';
import axios from 'axios'
import {
  Element
} from 'react-faux-dom';

class App extends Component {

}

export default App

Enter fullscreen mode Exit fullscreen mode

Inside the component, you’ll have states to hold the data for the charts:


class App extends Component {
  state = {
    pieChartData: [],
    barChartData: [],
  }

}
Enter fullscreen mode Exit fullscreen mode

The plotBarChart method will draw a bar chart based on the Budget type:

plotBarChart(chart, width, height) {
      // create scales!
      const xScale = d3.scaleBand()
          .domain(this.state.barChartData.map(d => d.attributes.Country))
          .range([0, width]);
      const yScale = d3.scaleLinear()
          .domain([0, d3.max(this.state.barChartData, d => d.attributes.Spending)])
          .range([height, 0]);
      const colorScale = d3.scaleOrdinal(d3.schemeCategory10);
      chart.selectAll('.bar')
          .data(this.state.barChartData)
          .enter()
          .append('rect')
          .classed('bar', true)
          .attr('x', d => xScale(d.attributes.Country))
          .attr('y', d => yScale(d.attributes.Spending))
          .attr('height', d => (height - yScale(d.attributes.Spending)))
          .attr('width', d => xScale.bandwidth())
          .style('fill', (d, i) => colorScale(i));

      chart.selectAll('.bar-label')
          .data(this.state.barChartData)
          .enter()
          .append('text')
          .classed('bar-label', true)
          .attr('x', d => xScale(d.attributes.Country) + xScale.bandwidth() / 2)
          .attr('dx', -6)
          .attr('y', d => yScale(d.attributes.Spending))
          .attr('dy', -6)
          .attr("fill", "#fff")
          .text(d => d.attributes.Spending);

      const xAxis = d3.axisBottom()
          .scale(xScale);

      chart.append('g')
          .classed('x axis', true)
          .attr('transform', `translate(0,${height})`)
          .attr("color", "#fff")
          .call(xAxis);

      const yAxis = d3.axisLeft()
          .ticks(5)
          .scale(yScale);

      chart.append('g')
          .classed('y axis', true)
          .attr('transform', 'translate(0,0)')
          .attr("color", "#fff")
          .call(yAxis);

      chart.select('.x.axis')
          .append('text')
          .attr('x', width / 2)
          .attr('y', 60)
          .attr('fill', '#fff')
          .style('font-size', '20px')
          .style('text-anchor', 'middle')
          .text('Country');

      chart.select('.y.axis')
          .append('text')
          .attr('x', 0)
          .attr('y', 0)
          .attr('transform', `translate(-50, ${height/2}) rotate(-90)`)
          .attr('fill', '#fff')
          .style('font-size', '20px')
          .style('text-anchor', 'middle')
          .text('Spending in Billion Dollars');

      const yGridlines = d3.axisLeft()
          .scale(yScale)
          .ticks(5)
          .tickSize(-width, 0, 0)
          .tickFormat('')

      chart.append('g')
          .call(yGridlines)
          .classed('gridline', true);
  }


Enter fullscreen mode Exit fullscreen mode

The plotPieChart method will draw a pie chart based on the Population type:

 plotPieChart(chart, width, height) {

    const radius = Math.min(width, height) / 2;


    const g = chart
        .append("g")
        .attr("transform", `translate(${width / 2}, ${height / 2})`);

    const color = d3.scaleOrdinal(["gray", "green", "brown"]);

    const pie = d3.pie().value(function(d) {
        return d.attributes.Percentage;
    });

    const path = d3
        .arc()
        .outerRadius(radius - 10)
        .innerRadius(0);

    const label = d3
        .arc()
        .outerRadius(radius)
        .innerRadius(radius - 80);

    const arc = g
        .selectAll(".arc")
        .data(pie(this.state.pieChartData))
        .enter()
        .append("g")
        .attr("class", "arc");

    arc
        .append("path")
        .attr("d", path)
        .attr("fill", function(d) {
            return color(d.data.attributes.State);
        });

    arc
        .append("text")
        .attr("fill", "#fff")
        .attr("font-size", "20px")
        .attr("transform", function(d) {
            return `translate(${label.centroid(d)})`;
        })
        .text(function(d) {
            return d.data.attributes.State;
        });

    chart
        .append("g")
        .attr("transform", `translate(${width / 2 - 120},10)`)
        .append("text")
        .text("Top populated states in the US")
        .attr("fill", "#fff")
        .attr("class", "title");
  }

Enter fullscreen mode Exit fullscreen mode

The drawChart method will be a convenience wrapper around the previous two methods:

drawChart() {
    const width = 400;
    const height = 450;
    const el = new Element('div');

    const margin = {
        top: 60,
        bottom: 100,
        left: 80,
        right: 40
    };

    const svg = d3.select(el)
        .append('svg')
        .attr('id', 'barchart')
        .attr('width', width)
        .attr('height', height);


    const barchart = svg.append('g')
        .classed('display', true)
        .attr('transform', `translate(${margin.left},${margin.top})`);

    const margin2 = {
        top: 60,
        bottom: 100,
        left: 80,
        right: 40
    };

    const svg2 = d3.select(el)
        .append('svg')
        .attr('id', 'piechart')
        .attr('width', width)
        .attr('height', height);


    const piechart = svg2.append('g')
        .classed('display', true)
        .attr('transform', `translate(${margin2.left},${margin2.top})`);


    const chartWidth = width - margin.left - margin.right;
    const chartHeight = height - margin.top - margin.bottom

    const chartWidth2 = width - margin2.left - margin2.right;
    const chartHeight2 = height - margin2.top - margin2.bottom

    this.plotBarChart(barchart, chartWidth, chartHeight);
    this.plotPieChart(piechart, chartWidth2, chartHeight2);
    return el.toReact();
  }
Enter fullscreen mode Exit fullscreen mode

Finally, when the component is mounted, you’ll render the charts:

componentDidMount() {

      axios.get('http://localhost:1337/api/budgets').then((response) => {
          console.log(response.data.data);
          const barChartData = response.data.data;
          this.setState({
              barChartData
          });
      }, (error) => {
          console.log("No data seen at endpoint");
          console.log(error);
      });

      axios.get('http://localhost:1337/api/populations').then((response) => {
          console.log(response.data.data);
          const pieChartData = response.data.data;
          this.setState({
              pieChartData
          });
      }, (error) => {
          console.log("No data seen at endpoint");
          console.log(error);
      });

  }


  render() {
      return this.drawChart();
  }
Enter fullscreen mode Exit fullscreen mode

It’s recommended that you spend some time customizing the style and appearance of the charts, for example, change the text colors if you’re using the light theme.

Plugin Demo

Build the plugin and start the server:

yarn build && yarn develop
Enter fullscreen mode Exit fullscreen mode

From the content manager, add some samples to Budget and Population. Then visit the analytics page to see the charts.

The final result

Developing a Strapi plug-in requires a great deal of background knowledge of how REST API works, what Axios is, React syntax and, in this case, a bit of d3 graphing as well.

Hopefully this exercise will inspire you to create more plug-ins to share with the community.

Conclusion

Strapi is a very useful library to add to your website or web application, as you can leverage its rich content manipulation, storage, and access without worrying about database or API endpoints. The fact that Strapi supports GraphQL and REST endpoints to access content makes it possible to seamlessly integrate it with the rest of your business logic. The plug-in ecosystem adds richness to the mix.

Strapi is a popular headless CMS alternative. This article demonstrated how to leverage Strapi’s plug-in architecture to talk to its backend, as well as the frontend. If you’ve managed to make a plug-in that’s functioning and useful, then you can add it to npm, make it public, and apply for inclusion in the Strapi marketplace. This usually takes a week or so.

Finally, you can find the code we used in this article on Strapi graphing in this GitHub repo.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .