Strapi plugins exist to make our work easier and our backend more functional. Plugins are convenient, and you can get them from the Strapi marketplace but sometimes, you will need some custom functions for your Strapi backend. Hence, we are going to learn how to make our own custom plugins. We will be making a simple reminder app that will send an email whenever you add a reminder. Let’s get started.
The Strapi documentation says that "Strapi is an open-source headless CMS that gives developers the freedom to choose their favorite tools and frameworks while also allowing editors to manage and distribute their content using their application's admin panel." Through the admin panel, you can make functional plugins.
You can read the Strapi documentation, where it explains in detail creating a boilerplate project using CLI.Let’s go with creating a quickstart project with SQLite database for this project, as it is easy to start with. You can use any you want though. Run the command below:
npx create-strapi-app@latest tutorialdev --quickstart
This will create a Srapi project with the name “tutorialdev”;you can replace it with any name you want.
To run project, run the command below:
npm run develop -- --watch-admin
This will run your strapi server on http://localhost:8000/
To create the basic plugin files, we will use strapi CLI command which is really simple.
Run this command:
npm run strapi generate
And then choose the plugin and name it anything you like. I named it “reminderapp” here. You will get a code in your terminal like this:
1module.exports = {
2 // ...
3 reminderapp: {
4 enabled: true,
5 resolve: './src/plugins/reminderapp
6 },
7 // ...
8}
Now, go to the config folder in the main directory and go to plugins.js. If you can’t find the file, create it and paste the code above. Change reminderapp to the name of the plugin that you put or just copy the code from your terminal and paste it.
Let's understand the basic structure of our project and what files are important to us. In your “src” folder, you will be able to find a “plugins” folder, inside which you will find a folder with the name of the plugin you created.
In our case, we will find “reminderapp”. It has two folders named “admin” and “server”. You will mainly work in the admin folder; when you open it, you will notice a folder named “pages”, inside which you will find “Homepage” folder where your main plugin frontend will be.
In the “server” folder, you will find controllers, routes, content-type, controllers, and services. I hope you are familiar with all terminology. We will be using content-type for storing reminders and the other three for extra functions. Let's get started with creating a backend for plugins like content-type, services for data querying,controllers,routes and other things.
The content type is like a schema for our database; we will create a reminder content type.
Run this command:
npm run strapi generate
Here, then choose content-type. Type the name for content type you want to create and keep clicking enter, below is an example how everything will look like:
Add attributes you want in your schemas, their names, and datatype. Once you are done with adding them, select “Add model to existing plugin” and choose our plugin name. In the picture, I used “todo”, but you have to use “reminder” instead as the picture is only as an example.
It will generate a schema.json; you can find it by going inside your plugins
folder then to your server folder. Move to content type, there, you will see a folder generated with the name of the content type you specified, and inside it you will find schema.json.
You will be bedding four attributes for this tutorial: name, date, datetime, and isdatepassed. Now, let's go with creating routes, controllers, and services.
Routes are basically API endpoints which we will hit. Services refer to where we will write all code for data query and other database operation. Controllers will be linking them.
Let’s make services first:
1'use strict';
2module.exports = ({ strapi }) => ({
3 async getall(query){
4 strapi.db.query('plugin::reminderapp.reminder').updateMany({
5 where:{
6 datetime:{
7 $lt:new Date()
8 }
9 },
10 data:{
11 isdatepassed:true
12 }
13 })
14 const q=strapi.entityService.findMany("plugin::reminderapp.reminder",{
15 filters:{
16 isdatepassed:{
17 $eq:false
18 }
19 }
20 });
21 return await q;
22 } ,
23 async deleteReminder(id) {
24 return await strapi.entityService.delete("plugin::reminderapp.reminder", id);
25 },
26
27 async createReminder(data) {
28 return await strapi.entityService.create("plugin::reminderapp.reminder", data);
29 },
30
31 async updateReminder(id, data) {
32 return await strapi.entityService.update("plugin::reminderapp.reminder", id, data);
33 },
34});
Let's understand what each function is doing here.
Now go to the server/controllers folder and create reminder.js there and add this:
1'use strict';
2
3module.exports={
4 async getall(ctx){
5 try{
6 return await strapi.plugin("reminderapp").service("reminder").getall(ctx.query);
7 }
8 catch(err){
9 ctx.trow(500,err);
10 }
11 },
12
13 async deleteReminder(ctx) {
14 try {
15 ctx.body = await strapi
16 .plugin("reminderapp").service("reminder")
17 .deleteReminder(ctx.params.id);
18 } catch (err) {
19 ctx.throw(500, err);
20 }
21 },
22 async createReminder(ctx) {
23 try {
24 ctx.body = await strapi
25 .plugin("reminderapp").service("reminder")
26 .createReminder(ctx.request.body);
27 } catch (err) {
28 ctx.throw(500, err);
29 }
30 },
31
32 async updateReminder(ctx) {
33 try {
34 ctx.body = await strapi
35 .plugin("reminderapp").service("reminder")
36 .updateReminder(ctx.params.id, ctx.request.body);
37 } catch (err) {
38 ctx.throw(500, err);
39 }
40 },
41};
Here, we simply made our services accessible to our routes. The plugin takes the name of the plugin whose services we are going to use and service() takes the name of the service we will be going to access.
Now go to index.js in the controllers folder:
1'use strict';
2
3const myController = require('./my-controller');
4const reminder =require('./reminder');
5
6module.exports = {
7 myController,
8 reminder,
9};
Now go to index.js inside the routes folder and add the following code:
1module.exports = [
2 {
3 method: 'GET',
4 path: '/',
5 handler: 'myController.index',
6 config: {
7 policies: [],
8 },
9 },
10 {
11 method: 'GET',
12 path: '/getall',
13 handler: 'reminder.getall',
14 config: {
15 policies: [],
16 auth:false,
17 },
18 },
19 {
20 method: "POST",
21 path: "/create",
22 handler: "reminder.createReminder",
23 config: {
24 policies: [],
25 },
26 },
27
28 {
29 method: "DELETE",
30 path: "/delete/:id",
31 handler: "reminder.deleteReminder",
32 config: {
33 policies: [],
34 },
35 },
36 {
37 method: "PUT",
38 path: "/update/:id",
39 handler: "reminder.updateReminder",
40 config: {
41 policies: [],
42 },
43 },
44];
This will create routes for your frontend to access the plugin services that we created. Now, go to the plugin root folder, then to admin/src, and the API folder. Then, create a file named “reminder.js”.
Add this code to the file:
1import { request } from "@strapi/helper-plugin";
2const ReminderApiHandler = {
3 getAllReminders: async () => {
4 return await request("/reminderapp/getall", {
5 method: "GET",
6 });
7 },
8 addReminder: async (data) => {
9 return await request(`/reminderapp/create`, {
10 method: "POST",
11 body: { data: data },
12 });
13 },
14 editReminder: async (id, data) => {
15 return await request(`/reminderapp/update/${id}`, {
16 method: "PUT",
17 body: { data: data },
18 });
19 },
20 deleteReminder: async (id) => {
21 return await request(`/reminderapp/delete/${id}`, {
22 method: "DELETE",
23 });
24 },
25};
26export default ReminderApiHandler;
This way, you will be able to easily send requests to the Strapi backend from your plugin frontend. You are through with setting up the backend and setting up the connection between frontend and backend.
Let’s get started with creating a frontend for our plugin.
Creating the frontend for the plugin is the next job for our development; this will be going to be the easiest part of our project. There are two ways to create a frontend for the plugin: creating CSS files and applying them or using Strapi default stylesheets.
We will be using the Strapi default stylesheets. Click here to view Strapi’s design system. Explore the whole storybook and find out amazing styled components. The best part is that it matches the default style of the admin panel of Strapi so you don’t need to worry for themes.
Let’s get started.
1import React, { memo } from 'react';
2// import PropTypes from 'prop-types';
3import { Layout,BaseHeaderLayout } from '@strapi/design-system/Layout';
4import { Button } from '@strapi/design-system/Button';
5import Plus from '@strapi/icons/Plus';
6import { Box } from '@strapi/design-system/Box';
7import { EmptyStateLayout } from '@strapi/design-system/EmptyStateLayout';
8import Calendar from '@strapi/icons/Calendar';
9import { Table, Thead, Tbody, Tr, Td, Th } from '@strapi/design-system/Table';
10import { Typography } from '@strapi/design-system/Typography';
11import { DatePicker } from '@strapi/design-system/DatePicker';
12import Modal from '../../components/Modal';
13import ReminderApiHandler from '../../api/reminder';
14import { Flex } from '@strapi/design-system/Flex';
15import { IconButton } from '@strapi/design-system/IconButton';
16import Pencil from '@strapi/icons/Pencil';
17import Trash from '@strapi/icons/Trash';
18import EditModal from '../../components/EditModal';
19function HomePage(){
20
21 const [reminderList,setReminder]=React.useState([]);
22const [ModalVisible,SetModalVisible]=React.useState(false);
23 const [isEdit,setIsEdit]=React.useState(false)
24 const [editedVal,setEditedVal]=React.useState({})
25async function FetchReminders(){
26 const reminders=await ReminderApiHandler.getAllReminders();
27 setReminder(reminders)
28}
29
30async function DeleteReminders(id){
31 const deleted=await ReminderApiHandler.deleteReminder(id)
32 FetchReminders()
33}
34
35async function updateReminder(id,data){
36 await ReminderApiHandler.editReminder(id,{"remindername":data.remindername,"date":data.date,'datetime':new Date(data.date)})
37 FetchReminders()
38}
39
40function addReminder(data){
41 ReminderApiHandler.addReminder({"remindername":data.remindername,"date":data.date,"datetime":new Date(data.date),"isdatepassed":false})
42FetchReminders()
43}
44
45
46React.useEffect(()=>{
47 FetchReminders()
48},[])
49 return (<Box>
50 <Layout>
51
52<Box background="neutral100">
53<BaseHeaderLayout primaryAction={<Button startIcon={<Plus />} onClick={SetModalVisible}>Add an reminder</Button>}
54 title="Add reminder" subtitle={`${reminderList.length} reminders found`} as="h2" />
55 </Box>
56 {reminderList.length==0?
57 <Box padding={8} background="neutral100">
58 <EmptyStateLayout icon={<Calendar />} content="You don't have any reminders yet..." />
59 </Box>:
60 <Table>
61 <Thead>
62 <Tr>
63 <Th>
64 <Typography variant="sigma">ID</Typography>
65 </Th>
66 <Th>
67 <Typography variant="sigma">Reminder name</Typography>
68 </Th>
69 <Th>
70 <Typography variant="sigma">date</Typography>
71 </Th>
72
73 </Tr>
74 </Thead>
75 <Tbody>
76 {reminderList.map((k)=>{
77 return(
78 <Tr>
79 <Td>
80 <Typography textColor="neutral800">{k.id}</Typography>
81 </Td>
82 <Td>
83 <Typography textColor="neutral800">{k.remindername}</Typography>
84 </Td>
85 <Td>
86 <DatePicker selectedDate={new Date(k.date)} label="date" name="datepicker" selectedDateLabel={formattedDate => `Date picker, current is ${formattedDate}`} disabled />
87
88 </Td>
89 <Flex>
90 <IconButton onClick={() => {
91 setEditedVal({
92 id:k.id,
93 date:k.date,
94 remindername:k.remindername
95 })
96 setIsEdit(true)
97 }} label="Edit" noBorder icon={<Pencil />} />
98 <Box paddingLeft={1}>
99 <IconButton onClick={() => DeleteReminders(k.id)} label="Delete" noBorder icon={<Trash />} />
100 </Box>
101 </Flex>
102 </Tr>
103
104 )}
105 )}
106 </Tbody>
107 </Table>}
108 </Layout>
109 {ModalVisible&& <Modal setShowModal={SetModalVisible} addReminder={addReminder}/>}
110 {isEdit&& <EditModal setShowModal={setIsEdit} updateReminder={updateReminder} id={editedVal.id} dateGiven={editedVal.date} nameGiven={editedVal.remindername}/>}
111 </Box>
112 )
113};
114export default memo(HomePage);
This frontend simply calls all reminders from getall
endpoint. On clicking “add reminder” button, it will open a modal from where you will be able to add a reminder. On clicking the trash icon, you will be able to delete the reminder. On clicking the edit button, a new modal will open a reminder whose value you are editing.
This will result in:
It will show a list of reminders in empty space. We don’t have any reminders so we will not see any right now but once we had it. We will be able to see them.
Let me show you how will it look:
Go to admin/src/api
and create a Mailer.js. First, download nodemailer library using following command:
npm i nodemailer --save
Now, go to file and put this code:
1/*
2 Video: https://www.youtube.com/watch?v=Va9UKGs1bwI
3 Don't forget to disable less secure app from Gmail: https://myaccount.google.com/lesssecureapps TODO:
4*/
5
6const nodemailer = require('nodemailer');
7const log = console.log;
8
9
10\\Step 1
11const mail=’mailer@gmail.com'
12const mailTo='mailto@gmail.com'
13const password=’password'
14let transporter = nodemailer.createTransport({
15 service: 'gmail',
16 auth: {
17 user:mail, // TODO: your gmail account
18 pass:password// TODO: your gmail password
19 }
20});
21
22// Step 2
23let mailOptions = {
24 from: mail, // TODO: email sender
25 to: mailTo, // TODO: email receiver
26 subject: 'Reminder added',
27 text: 'reminder'
28};
29
30// Step 3
31Export default function Mailing(){
32transporter.sendMail(mailOptions, (err, data) => {
33 if (err) {
34 return log('Error occurs'+err);
35 }
36 return log('Email sent!!!');
37});
38}
Go to My Account > Sign-in & Security > App Passwords, scroll down and select app and choose ‘Other’. Name the app “nodemailer”, click generate and copy-paste long generated password as gmail password.
Now, go to api/reminder.js
and import the above function and call it in create function:
1import SendingMail from './Mailer'
2…..
3 addReminder: async (data) => {
4 SendingMail()
5 return await request(`/reminderapp/create`, {
6 method: "POST",
7 body: { data: data },
8 });
9 },
10….
Now, you are all done with making everything. I hope you now understand how easy it is to make strapi plugins and how you can make them functional using third-party packages.
Here is the result:
Hope you will use your knowledge and make the cool plugin that will really help other Strapi developers and help the Strapi community.
I am a Full Stack Developer, currently, a student worked as part-time in a fintech company. I am a hobbyist 3d artist.