What the CRUD? Create, Read, Update and Destroy with Magento2

If you’re used to PHP MVC frameworks like Laravel or Symphony you may have noticed already that performing these relatively simple actions in Magento2 is more complicated than it first appears...

Just a quick note, at the time of writing this article the version of Magento 2 being used is 2.2.2

So you’ve decided to deep dive into the complex and rich world of Magento 2 and want to create an entity and perform basic CRUD actions on it. If you’re used to PHP MVC frameworks like Laravel or Symphony you may have noticed already that performing these relatively simple actions in Magento 2 is more complicated than it first appears. In this post I’ll go though the basic setup of a module in Magento 2 and create a custom database model so we can see how its done.

Assumed knowledge:

  • OO-PHP
  • How to create and register a basic module in Magento 2
  • How to create a basic route, and a new page in Magento 2

What we’ll cover in this tutorial step by step:

  • Create and register a Magento 2 module
  • Create an install script to install a table for the model we want to CRUD
  • Create the Model class (more specifically classes) for Magento 2 to interact with
  • Create a basic Create controller to handle a post from an imaginary front end page

I just want the code!

No worries, here’s a link to the github repo for what I’m about to go through:

Step 1 – Setup our module:

Firstly lets create a basic module in Magento 2 and register it. For the demo we’ll be calling our module CRUD, under a namespace C3. Once done you should have a folder structure that looks like the following:

Module Structure

Basic Module structure

Don’t forget to populate your module.xml file with the relevant config script before proceeding. It should look something like this:

<?xml version="1.0"?>
<config xmlns:xsi="" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="C3_CRUD" setup_version="1.0.0">

Step 2 – Install Script to create Database:

Now that we have our module setup let’s start by creating a new entity in our database so we have something to work with. Create a new folder called ‘Setup’ under your module namespace, and create a new file called InstallSchema.php inside it.

We’re going to call our model Notes. So open ‘InstallSchema.php’ and we’ll start building the install script that will setup this new table for us.

namespace C3\CRUD\Setup;
use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
class InstallSchema implements InstallSchemaInterface
     * Installs DB schema for a module
     * @param SchemaSetupInterface $setup
     * @param ModuleContextInterface $context
     * @return void
     * @throws \Zend_Db_Exception
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
        $dbConnection            = $setup->getConnection();
        $validatedNotesTableName = $setup->getTable('notes');
        $notesTable = $dbConnection->newTable($validatedNotesTableName)
                                    // ID
                                   ->addColumn('note_id', Table::TYPE_INTEGER, null, [
                                       'identity' => true,
                                       'unsigned' => true,
                                       'nullable' => false,
                                       'primary'  => true
                                   ], 'Notes ID')
                                    // Notes title
                                   ->addColumn('title', Table::TYPE_TEXT, 250, ['nullable' => false], 'Note Title')
                                    // Notes Content
                                   ->addColumn('content', Table::TYPE_TEXT, 1000, ['nullable' => false], 'Note Content')
                                    // Created at
                                   ->addColumn('created_at', Table::TYPE_TIMESTAMP, null, [
                                       'default'  => Table::TIMESTAMP_INIT,
                                       'nullable' => false
                                   ], 'Created At')
                                    // Updated At
                                   ->addColumn('updated_at', Table::TYPE_TIMESTAMP, null, [
                                       'default'  => Table::TIMESTAMP_INIT_UPDATE,
                                       'nullable' => false
                                   ], 'Updated At');

As you can see here, we’re creating a table called’ notes’ where we can store a notes title, and content. Once we’re happy with how our table will look,  run ‘php/bin magento setup:upgrade‘ in terminal under your project root to register your module and install our table. Magento2 will run any scripts contained within the Setup directory in your module and build our table for us.

Step 3 – Build our Model (All of the Abstraction):

Now it’s time to set up our database model. A model entity in Magento is broken up into three separate classes.

  • Firstly, a Resource Model which interfaces directly with the database, allowing for CRUD operations.
  • Secondly, a basic business logic Model (which extends Magento\Framework\Model\AbstractModel) it is in this model that we place our main business logic.
  • Finally, a Collection Model, which is responsible for handling a collection of Resource models. Whilst this model isn’t strictly necessary, as we can retrieve database results or our Notes model via the Resource Model, we do however get to treat each result as an object with the use of the Collection model instead.

Let’s start by creating a’ Model’ directory in our module, then a’ ResourceModel’ subdirectory, and declare our first model class, the resource model. As you’ll see this model is where we specify which database table to link to, and which column to use for its id.

namespace C3\CRUD\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class Note extends AbstractDb
     *  Link our model to the notes table, and which column is the id reference.
    protected function _construct()
        $this->_init('notes', 'note_id');

Now we move on to our Collection Model, create yet another subdirectory under the’ ResourceModel’ directory and call it’ Notes.
In here we’re going to place our Collection.php class. You may have noticed we did not call this Notes, or NotesCollection. This is because Magento expects to find the relevant collection class from the namespace for the resource model it links to, and expects to find a folder under the resource model name, and the collection class within.  As you’ll see this model is where we specify which database table to link to, and which column to use for its id.

namespace C3\CRUD\Model\ResourceModel\Note;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
class Collection extends AbstractCollection
    // Link our model, and resource model to this collection class
    protected function _construct()
        $this->_init('C3\CRUD\Model\Notes', 'C3\CRUD\Model\ResourceModel\Note');

Finally we’re going to create your business logic model directly within the Model directory we created. As you’ll see here it too references the database table it links to, but also the resource model it has a relationship with. Think of this class as the place all your business specific logic is placed. For example, we may want to manipulate the data passed to this model in a particular way, before we use our resource class to save it.

namespace C3\CRUD\Model;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\DataObject\IdentityInterface;
use C3\CRUD\Model\ResourceModel\Note as ResourceNote;
class Note extends AbstractModel implements IdentityInterface
    //The table name for our notes model.
    const CACHE_TAG = 'notes';
    // Specify which resource model this model links to.
    protected function _construct()
     * Return unique ID(s) for each object in system
     * @return string[]
    public function getIdentities()
        return [self::CACHE_TAG . '_' . $this->getId()];

Our updated file structure should now look like this once all three models have been created:

(Its not immediately obvious by the screenshot, but the bottom Note.php file is located under the Model directory, and the upper is located under the ResourceModel directory).


Why expand our database model into three separate classes?

Magento2 has opted to extract database models into three separate classes to allow for the greatest flexibility from within your application. It allows for you to apply only database specific logic to say the resource model, and business specific logic to your abstract model, and collection behaviour to (you guessed it) your collection model.

Step 4 – Set Up A Route: Telling Magento How to Find Your Controllers

Time to create a controller to handle CRUD operations on our module. We’ll start with setting up a basic route in our module. Create a ‘frontend’ directory under your modules ‘etc’ directory. In here create your ‘routes.xml’ file.

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="crud" frontName="crud">
            <module name="C3_CRUD"/>

Here we’ve basically set our route path to start with

Step 5 – CRUD via Controllers:

Now we’ll create our controller directory in the root directory of our module with a subdirectory of ‘Notes’: C3\CRUD\Controller\Note. Now we need to create controller classes for each CRUD action we want. For the purposes of length, I will show a simple ‘Create’ class only, however once you see how its done it will be pretty simple to build your own read, update and destroy classes.

One note about Magento2 controllers you must create a whole new controller class per action you want to perform. So for instance, if we were to go ahead and build out our Read, Update and Destroy classes too our Controller directory should look like something like this.

Ok, so let’s make the Create.php class:

namespace C3\CRUD\Controller\Note;
use C3\CRUD\Controller\NoteFactory; // Factory classes are dynamically generated classes by Magento
use C3\CRUD\Model\ResourceModel\Note;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\Request\Http;
use Magento\Framework\Exception\AlreadyExistsException;
class Create extends Action
    protected $helper;
    protected $request;
    protected $note;
    protected $noteResource;
    protected $noteFactory;
    public function __construct(
        Context $context,
        Note $noteResource,
        Http $request,
        NoteFactory $noteFactory
    ) {
        $this->request      = $request;
        $this->noteFactory  = $noteFactory;
        $this->noteResource = $noteResource;
     * Execute action for our controller.
    public function execute()
        $postData = $this->request->getPost();
        if (!empty($postData)) {
            //Probably best do some form of validation here but for tutorial purposes, we wont for now
            $title = $postData['title'];
            $issue = $postData['content'];
            $newNote = $this->note
                  'title' => $title,
                  'issue' => $issue,
            //Try to save the new note
            try {
                $this->messageManager->addSuccessMessage("New Ticket: $title Created");
            } catch (\Exception $e) {
                //Add a error message if we cant save the new note from some reason
                $this->messageManager->addErrorMessage('Unable to save this ticket, please call your service provider');
        } else {
            // Add a helpful error message
            $this->messageManager->addErrorMessage("No data was posted");
        // Return to where we came from, here we're assuming the read notes page.
        return $this->_redirect("*/*/read");

As you’ll notice we’ve referenced dependency in called   ‘C3\CRUD\Model\NoteFactory’. This is a dynamically generated class from Magento for all Abstract Model classes. It allows for the instantiation of new models without the need for you to declare their dependencies as well. Don’t worry if your IDE is upset with you initially when declaring the factory dependency; at the time Magento fires the request for that class, it will automatically generate it for you.

Note: Whilst the use of a Factory class is not strictly required when dealing with Model instantiation , we recommend using it as it provides more control over object creation, and in turn helps avoid object reuse.

Ok But, Where’s the Frontend?

I leave that to you dear reader, to figure out how to post a form to this controller. Here is a helpful link to Alan Storms page in creating a basic module in your Magento2 application to get you started.

The reason why I haven’t included this is this particular tutorial is due to frontend development in Magento 2 is deserving of its own blog post. Also stated at the beginning of this post, we expect you to know how to create a new page before attempting CRUD.  

Step 6 – Profit

There you have it, we created our database model Magento 2 style, made a database script to install our model table in your stores DB, and finally created a quick route, and controller action to demonstrate how to connect all the pieces. A quick recap of the steps are as follows:

  • Create and register your Module
  • Create an install script to make your db table/s
  • Create your three model classes in your modules ‘Model’ directory
  • Use model factories to instantiate and work with your models

I hope this short tutorial was helpful in wrapping your head around how Magento 2 expects your database models to be instantiated and why. I’ll be posting more of these articles in the future, and any constructive feedback is welcome.

A quick note on why we provide these tutorial-style blog posts – they help us and others get on with the interesting part of development. The point is to help grasp what layouts are required quickly, so you can get on with actual coding, rather than piecing together how things are to work in the first place.

About the author

Michael Smith

Mike is our token Aussie and former international rower, he touches on every aspect of web development, from deployment, backend solutions, to frontend implementations of new JavaScript frameworks.