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.
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.
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.
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
:
1cd src/plugins
2tree
1 └── analytics
2 ├── admin
3 │ └── src
4 │ ├── components
5 │ │ ├── Initializer
6 │ │ │ └── index.js
7 │ │ └── PluginIcon
8 │ │ └── index.js
9 │ ├── index.js
10 │ ├── pages
11 │ │ ├── App
12 │ │ │ └── index.js
13 │ │ └── HomePage
14 │ │ └── index.js
15 │ ├── pluginId.js
16 │ ├── translations
17 │ │ ├── en.json
18 │ │ └── fr.json
19 │ └── utils
20 │ ├── axiosInstance.js
21 │ └── getTrad.js
22 ├── package.json
23 ├── README.md
24 ├── server
25 │ ├── bootstrap.js
26 │ ├── config
27 │ │ └── index.js
28 │ ├── content-types
29 │ │ └── index.js
30 │ ├── controllers
31 │ │ ├── index.js
32 │ │ └── my-controller.js
33 │ ├── destroy.js
34 │ ├── index.js
35 │ ├── middlewares
36 │ │ └── index.js
37 │ ├── policies
38 │ │ └── index.js
39 │ ├── register.js
40 │ ├── routes
41 │ │ └── index.js
42 │ └── services
43 │ ├── index.js
44 │ └── my-service.js
45 ├── strapi-admin.js
46 └── strapi-server.js
47
48 19 directories, 27 files
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.
You start by setting up a Strapi project with the command:
yarn create strapi-app strapi-graphing --quickstart
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:
Country
: a text fieldSpending
: a float fieldSave 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:
1curl http://localhost:1337/api/budgets
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
.
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.
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
:
1module.exports = {
2 'analytics': {
3 enabled: true,
4 resolve: './src/plugins/analytics'
5 },
6}
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
and add the module:
yarn add @strapi/icons
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:
1 /**
2 *
3 * PluginIcon
4 *
5 */
6
7 import React from 'react';
8 import Equalizer from '@strapi/icons/Equalizer';
9
10 const PluginIcon = () => <Equalizer />;
11
12 export default PluginIcon;
You should now be able to see the plugin in the dashboard.
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
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:
1import PropTypes from 'prop-types';
2import pluginId from '../../pluginId';
3import * as d3 from "d3";
4import React, {
5 Component
6} from 'react';
7import axios from 'axios'
8import {
9 Element
10} from 'react-faux-dom';
11
12class App extends Component {
13
14}
15
16export default App
Inside the component, you’ll have states to hold the data for the charts:
1…
2class App extends Component {
3 state = {
4 pieChartData: [],
5 barChartData: [],
6 }
7
8}
The plotBarChart
method will draw a bar chart based on the Budget
type:
1plotBarChart(chart, width, height) {
2 // create scales!
3 const xScale = d3.scaleBand()
4 .domain(this.state.barChartData.map(d => d.attributes.Country))
5 .range([0, width]);
6 const yScale = d3.scaleLinear()
7 .domain([0, d3.max(this.state.barChartData, d => d.attributes.Spending)])
8 .range([height, 0]);
9 const colorScale = d3.scaleOrdinal(d3.schemeCategory10);
10 chart.selectAll('.bar')
11 .data(this.state.barChartData)
12 .enter()
13 .append('rect')
14 .classed('bar', true)
15 .attr('x', d => xScale(d.attributes.Country))
16 .attr('y', d => yScale(d.attributes.Spending))
17 .attr('height', d => (height - yScale(d.attributes.Spending)))
18 .attr('width', d => xScale.bandwidth())
19 .style('fill', (d, i) => colorScale(i));
20
21 chart.selectAll('.bar-label')
22 .data(this.state.barChartData)
23 .enter()
24 .append('text')
25 .classed('bar-label', true)
26 .attr('x', d => xScale(d.attributes.Country) + xScale.bandwidth() / 2)
27 .attr('dx', -6)
28 .attr('y', d => yScale(d.attributes.Spending))
29 .attr('dy', -6)
30 .attr("fill", "#fff")
31 .text(d => d.attributes.Spending);
32
33 const xAxis = d3.axisBottom()
34 .scale(xScale);
35
36 chart.append('g')
37 .classed('x axis', true)
38 .attr('transform', `translate(0,${height})`)
39 .attr("color", "#fff")
40 .call(xAxis);
41
42 const yAxis = d3.axisLeft()
43 .ticks(5)
44 .scale(yScale);
45
46 chart.append('g')
47 .classed('y axis', true)
48 .attr('transform', 'translate(0,0)')
49 .attr("color", "#fff")
50 .call(yAxis);
51
52 chart.select('.x.axis')
53 .append('text')
54 .attr('x', width / 2)
55 .attr('y', 60)
56 .attr('fill', '#fff')
57 .style('font-size', '20px')
58 .style('text-anchor', 'middle')
59 .text('Country');
60
61 chart.select('.y.axis')
62 .append('text')
63 .attr('x', 0)
64 .attr('y', 0)
65 .attr('transform', `translate(-50, ${height/2}) rotate(-90)`)
66 .attr('fill', '#fff')
67 .style('font-size', '20px')
68 .style('text-anchor', 'middle')
69 .text('Spending in Billion Dollars');
70
71 const yGridlines = d3.axisLeft()
72 .scale(yScale)
73 .ticks(5)
74 .tickSize(-width, 0, 0)
75 .tickFormat('')
76
77 chart.append('g')
78 .call(yGridlines)
79 .classed('gridline', true);
80 }
The plotPieChart
method will draw a pie chart based on the Population
type:
1 plotPieChart(chart, width, height) {
2
3 const radius = Math.min(width, height) / 2;
4
5
6 const g = chart
7 .append("g")
8 .attr("transform", `translate(${width / 2}, ${height / 2})`);
9
10 const color = d3.scaleOrdinal(["gray", "green", "brown"]);
11
12 const pie = d3.pie().value(function(d) {
13 return d.attributes.Percentage;
14 });
15
16 const path = d3
17 .arc()
18 .outerRadius(radius - 10)
19 .innerRadius(0);
20
21 const label = d3
22 .arc()
23 .outerRadius(radius)
24 .innerRadius(radius - 80);
25
26 const arc = g
27 .selectAll(".arc")
28 .data(pie(this.state.pieChartData))
29 .enter()
30 .append("g")
31 .attr("class", "arc");
32
33 arc
34 .append("path")
35 .attr("d", path)
36 .attr("fill", function(d) {
37 return color(d.data.attributes.State);
38 });
39
40 arc
41 .append("text")
42 .attr("fill", "#fff")
43 .attr("font-size", "20px")
44 .attr("transform", function(d) {
45 return `translate(${label.centroid(d)})`;
46 })
47 .text(function(d) {
48 return d.data.attributes.State;
49 });
50
51 chart
52 .append("g")
53 .attr("transform", `translate(${width / 2 - 120},10)`)
54 .append("text")
55 .text("Top populated states in the US")
56 .attr("fill", "#fff")
57 .attr("class", "title");
58 }
The drawChart
method will be a convenience wrapper around the previous two methods:
1drawChart() {
2 const width = 400;
3 const height = 450;
4 const el = new Element('div');
5
6 const margin = {
7 top: 60,
8 bottom: 100,
9 left: 80,
10 right: 40
11 };
12
13 const svg = d3.select(el)
14 .append('svg')
15 .attr('id', 'barchart')
16 .attr('width', width)
17 .attr('height', height);
18
19
20 const barchart = svg.append('g')
21 .classed('display', true)
22 .attr('transform', `translate(${margin.left},${margin.top})`);
23
24 const margin2 = {
25 top: 60,
26 bottom: 100,
27 left: 80,
28 right: 40
29 };
30
31 const svg2 = d3.select(el)
32 .append('svg')
33 .attr('id', 'piechart')
34 .attr('width', width)
35 .attr('height', height);
36
37
38 const piechart = svg2.append('g')
39 .classed('display', true)
40 .attr('transform', `translate(${margin2.left},${margin2.top})`);
41
42
43 const chartWidth = width - margin.left - margin.right;
44 const chartHeight = height - margin.top - margin.bottom
45
46 const chartWidth2 = width - margin2.left - margin2.right;
47 const chartHeight2 = height - margin2.top - margin2.bottom
48
49 this.plotBarChart(barchart, chartWidth, chartHeight);
50 this.plotPieChart(piechart, chartWidth2, chartHeight2);
51 return el.toReact();
52 }
Finally, when the component is mounted, you’ll render the charts:
1componentDidMount() {
2
3 axios.get('http://localhost:1337/api/budgets').then((response) => {
4 console.log(response.data.data);
5 const barChartData = response.data.data;
6 this.setState({
7 barChartData
8 });
9 }, (error) => {
10 console.log("No data seen at endpoint");
11 console.log(error);
12 });
13
14 axios.get('http://localhost:1337/api/populations').then((response) => {
15 console.log(response.data.data);
16 const pieChartData = response.data.data;
17 this.setState({
18 pieChartData
19 });
20 }, (error) => {
21 console.log("No data seen at endpoint");
22 console.log(error);
23 });
24
25 }
26
27
28 render() {
29 return this.drawChart();
30 }
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.
Build the plugin and start the server:
yarn build && yarn develop
From the content manager, add some samples to Budget
and Population
. Then visit the analytics
page to see the charts.
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.
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.
Girish is a seasoned software programmer with 25 years of hands on experience with several cover stories to credit. He has written on wide variety of topics including the Vim editor, mplayer, OpenSSH, UDP hole punching and many more.