Web Development

An Introduction to Core Concepts of Building a Craft CMS Plugin

Building craft cms plugin

Building a Craft CMS plugin for the very first time can be a daunting task. This is especially true if you're new to modern web development, with tasks such as using Composer and understanding MVC frameworks.

And while there are many excellent plugins in the Craft CMS store, sometimes a custom solution is the only route possible to extend your website.

Craft's plugin documentation is a great introduction, but there's much more to learn. Let's first get into why you might want a custom plugin solution for your website and some key discussion points.

Modules vs Plugins

First, we need to understand the primary differences between a Craft Module and a Craft Plugin. In short, a module will only be used on the current website, while a plugin is redistributable.

Rest assured, both modules and plugins can access the full Craft CMS APIs, helpers, event handlers, and more.

The other major difference between these two options is that a plugin can have migrations while a module cannot. I'm sure you know that migrations are a way to build database interactivity by defining the table and row structure that you'd like to add in addition to the existing framework.

I do not have any plugins in the Craft Plugin store (yet), and typically, a module gets the job done to spin up a contact form or basic stuff, like adding Twig extensions.

So, if you're thinking about building a new plugin or simply want to experiment with building something new using the power of Craft, let's dive in!

Creating the Plugin

First up is creating the plugin, which is fairly straightforward. Craft has a Generator built into Craft v4.3.5 and above, which simplifies getting started.

Open up your favorite local development terminal -- mine is the built-in Mac terminal. Type the below from your project's root folder:

php craft make new-plugin --with-docblocks

Using --with-docblocks will add helpful comments in the generated code, and in the beginning, it's best to enable the feature. Update new-plugin in the code above to the desired name of your new plugin.

You'll get to a section that asks, "Make this a private plugin?"

If you choose no, your plugin will require a GitHub repo. If you choose yes, then a repo will not be required, and your plugin will have an underscore as the first character of its name. I suggest starting with a private plugin, as you can always "rm -rf" on your plugins folder and start over!

I'm not going to get into Git commands, as there are many articles on that topic.

Creating a Controller

What's a plugin (or module) without a controller? The controller is where you define all the primary logic, such as events, services, and more.

The Craft Generator provides a seamless way to create your new controller using the code below:

php craft make controller --plugin=new-plugin --with-docblocks

As you can see, we again use the docblocks switch to have comments added to our controller. Defining the --plugin is key so that the folder structure stays maintained, and our controller is assigned to our new plugin. To keep things simple, let's call our controller logic (which will then become LogicController.php in your folder structure). Change the name as you wish.

And, if you need more controllers, say one for Forms and another for PDF creation, just run the command again and choose a new controller name. It's a good idea to separate your logic. However, you can keep all your logic within the same controller if you'd like.

Creating a Record

Here is where we start to separate from the module paradigm and get into database interactivity.

An active record class is typically used for defining database table schemas, representing rows in the database, and performing operations such as finding, altering, and deleting rows (CRUD operations).

php craft make record --plugin=new-plugin --with-docblocks

By using records, you can efficiently handle data storage and retrieval. If you're not distributing your plugin, having a database is the primary driver (for me) to use a plugin over a module. Name your new record as you'd like, which you'll get to do after running the command. However, use Forms as the name if you'd like to follow along in the discussion below.

Creating a Model

Of course, you'll also want a Model class for defining data structures and validation rules.

During the plugin creation process, you'll be asked about whether your plugin should have settings. If you choose yes, then your first model will be created. Otherwise, you can follow the same syntax as the controller creation process.

php craft make model --plugin=new-plugin --with-docblocks

Models often act as validation layers of your application, such as between a form submission and database storage. Again, after running the above command, you'll have the opportunity to name your model and let's stick with the name form for discussion purposes.

So, you should now have a Logic controller to define the primary logic. The Form model can be used to validate the data and return it back to the controller.

Creating a Migration

This is where it all comes together for the database component, as migrations are a crucial component of plugin development.

Migrations are used to manage the database schema: they allow you to create, modify, and delete database tables and columns in a controlled
and versioned manner.

First, you'll want to create the install migration, as it will be used when your plugin is installed or uninstalled.

php craft migrate/create install --plugin=plugin-name

The docblocks switch is not available in the migration creation process.

A sample migration might look like this:

use Craft;
use craft\db\Migration;

class Install extends Migration
 public function safeUp()
 // Create a new table
 $this->createTable('{{%new_plugin}}', [
 // your custom table columns
 'id' => $this->primaryKey(),
 'title' => $this->string()->notNull(),
 'description' => $this->text(),

 // columns recommended for Craft CMS
 'dateCreated' => $this->dateTime()->notNull(),
 'dateUpdated' => $this->dateTime()->notNull(),
 'uid' => $this->uid()->notNull(),

 public function safeDown()
 // Drop the table

Now, when you Install the new plugin, the table schema above will be created in your database. How awesome is that!

We're almost at installation -- let's cover a few more topics before we wrap up.

Creating a Sidebar

You'll most likely want a menu item for your plugin in the Control Panel.

You can enable that with one quick setting in your plugin class by adding and setting the variable below to true.

public bool $hasCpSection = true;

For a single Control Panel page, the above is all that's required. Sub-navs require a bit more, and you can read about it here.

Finally, create an index.twig file in your Templates folder (within the plugin) with the following:

{% extends "_layouts/cp" %}
{% set title = 'Page Title' %}

{% block content %}

// your custom design here

{% endblock %}

That will get you started. Configuring it further will depend on your plugin's needs.

Enabling the Plugin!

Finally, we get to see all our great efforts.

Step one: add your new plugin to your project. The Composer command (specific to your plugin name), will be along these lines:

composer require your-name/craft-newplugin

Step two: Navigate to Settings->Plugins with the Control Panel. Click the "cog" to select "Install" to enable your new plugin!

Pending all went smoothly, you will have your plugin's name visible in the sidebar, and now it's time to dive deeper into your code to customize your plugin to your specific needs.


I wish a plugin primer similar to the above existed when I created my first plugin. However, back then, the primary resource was a tool called Plugin Factory (which no longer exists), and I strongly believe Craft did a fantastic thing by creating their Generator to keep the ecosystem streamlined.

If you're looking for additional support, Craft's Discord is an excellent place to start and get more involved in the community.

Building a plugin can be a complex task, depending on the functionality you aim to achieve. Start with a simple project, and gradually increase complexity as you become more comfortable with Craft CMS's architecture and features.

Or, you can always hire Zadro Web for your web development needs!