

Magento 2: Models, Resource Models, and Collections


Listen Full Blog Here
Key Takeaways
- »Magento's Model system has three parts: Model, Resource Model, and Collection, each with a distinct job.
- »Every entity or database table should have its own Model class, which extends AbstractModel and gives access to setData and getData.
- »The Resource Model handles all actual database operations and must extend AbstractDb; every Model needs one.
- »Collections let you fetch multiple rows at once, with support for joins, filters, GROUP BY, and ORDER BY.
- »This Model, ResourceModel, Collection pattern is the standard, recommended way to build custom modules in Magento 2.
Models in Magento are an inherent part of the MVC (Model-View-Controller) architecture something every Adobe Commerce developer works with regularly. Models are used to do data operations, namely Create, Read, Update and Delete, on a database. Magento’s “Model system” is divided into three parts – models, resource models, and collections. In this tutorial, we will be discussing each component individually, and then use all three to make a simple module.
If you wanna follow along, first you’ll have to create a simple hello world module, and then create a table called “my_cars” containing the columns “car_id”, “manufacturer”, and “model”, using an Install Schema setup script.
Model
Models are like a black box which provides a layer of abstraction on top of the resource models. The fetching, extraction, and manipulation of data occur through models. As a rule of thumb, every entity we create (i.e. every table we create in our database) should have its own model class. Every model extends the Magento\Framework\Model\AbstractModelclass, which inherits the \Magento\Framework\DataObjectclass, hence, we can call the setDataand getData functions on our model, to get or set the data of a model respectively. To understand how data objects work, you can read the tutorial on data objects here.
app/code/Jayanka/HelloWorld/Model/Car.php
<?php
namespace Jayanka\HelloWorld\Model;
use Magento\Framework\Model\AbstractModel;
use Jayanka\HelloWorld\Model\ResourceModel\Car as ResourceModel;
class Car extends AbstractModel
{
protected function _construct()
{
$this->_init(ResourceModel::class);
}
}
The Carclass only has one method, _construct(), when we call the _init()method, and pass the resource model’s name as its parameter. But what is a resource model?
Resource Model
All of the actual database operations are executed by the resource model. Every model must have a resource model, since all of the methods of a resource model expects a model as its first parameter. All resource models must extend theMagento\Framework\Model\ResourceModel\Db\AbstractDbclass.
app/code/Jayanka/HelloWorld/Model/ResourceModel/Car.php
<?php
namespace Jayanka\HelloWorld\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class Car extends AbstractDb
{
protected function _construct()
{
$this->_init('my_cars', 'car_id');
}
}
As we can see, the <Carclass> here also has one method, <__construct>, where we call the <_initmethod>, and pass two parameters to it. The name of the table in the database, and the name of the primary column in that table.
Collection
Collections are used when we want to fetch multiple rows from our table. Meaning collections are a group of models. Collections can be used when we want to
- Fetch multiple rows from a table
- Join tables with our primary table
- Select specific columns
- Apply a WHERE clause to our query
- Use GROUP BY or ORDER BY in our query
app/code/Jayanka/HelloWorld/Model/ResourceModel/Car/Collection.php
<?php
namespace Jayanka\HelloWorld\Model\ResourceModel\Car;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Jayanka\HelloWorld\Model\Car as Model;
use Jayanka\HelloWorld\Model\ResourceModel\Car as ResourceModel;
class Collection extends AbstractCollection
{
protected function _construct()
{
$this->_init(Model::class, ResourceModel::class);
}
}
All collections must inherit the <Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection> class. Our collection class also has one method, the <_construct>, where we call the <_init>method and pass two arguments, the name of the model class, and the resource model class.
Using our Model
Now that our Model, ResourceModel, and Collection classes are ready, let’s use them.
app/code/Jayanka/HelloWorld/Block/Hello.php
<?php
/**
*
* @package magento2
* @author Jayanka Ghosh
* @license https://opensource.org/licenses/OSL-3.0 Open Software License v. 3.0 (OSL-3.0)
* @link http://jayanka.me/
*/
namespace Jayanka\HelloWorld\Block;
use Magento\Framework\View\Element\Template;
use Jayanka\HelloWorld\Model\ResourceModel\Car\Collection;
class Hello extends Template
{
/**
* @var Collection
*/
private $collection;
/**
* Hello constructor.
* @param Template\Context $context
* @param Collection $collection
* @param array $data
*/
public function __construct(
Template\Context $context,
Collection $collection,
array $data = []
)
{
parent::__construct($context, $data);
$this->collection = $collection;
}
public function getAllCars() {
return $this->collection;
}
public function getAddCarPostUrl() {
return $this->getUrl('helloworld/car/add');
}
}
and in our template file we add a form to add new cars, and a table, listing all the existing cars in the database
app/code/Jayanka/HelloWorld/view/frontend/templates/hello.phtml
<?php
/* @var Jayanka\HelloWorld\Block\Hello $block */
$cars = $block->getAllCars();
?>
<form method="post" action="<?= $block->getAddCarPostUrl() ?>">
<fieldset>
<div>
<label for="manufacturer"><?= __("Manufacturer") ?></label>
<input type="text" name="manufacturer" id="manufacturer" />
</div>
<div>
<label for="model"><?= __("Model") ?>
<input type="text" name="model" id="model" />
</div>
<input type="submit" value="<?= __("Add Car") ?>" />
</fieldset>
</form>
<hr />
<?php if (count($cars) > 0): ?>
<table border="1">
<tr>
<th><?= __("ID") ?></th>
<th><?= __("Manufacturer") ?>
<?= __("Model") ?></th>
</tr>
<?php foreach ($cars as $car): ?>
<tr>
<td><?= $car->getId() ?>
<td><?= $car->getManufacturer() ?>
<td><?= $car->getModel() ?>
</tr>
<?php endforeach; ?>
</table>
<?php else: ?>
<h1><?= __("You have no cars yet :(") ?></h1>
<?php endif; ?>
Now finally we have to make a controller to save the car information submitted from the form
app/code/Jayanka/HelloWorld/Controller/Car/Add.php
<?php
/**
*
* @package magento2
* @author Codilar Technologies
* @license https://opensource.org/licenses/OSL-3.0 Open Software License v. 3.0 (OSL-3.0)
* @link https://www.codilar.com/
*/
namespace Jayanka\HelloWorld\Controller\Car;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\ResponseInterface;
use Jayanka\HelloWorld\Model\Car;
use Jayanka\HelloWorld\Model\ResourceModel\Car as CarResource;
class Add extends Action
{
/**
* @var Car
*/
private $car;
/**
* @var CarResource
*/
private $carResource;
/**
* Add constructor.
* @param Context $context
* @param Car $car
* @param CarResource $carResource
*/
public function __construct(
Context $context,
Car $car,
CarResource $carResource
)
{
parent::__construct($context);
$this->car = $car;
$this->carResource = $carResource;
}
/**
* Execute action based on request and return result
*
* Note: Request will be added as operation argument in future
*
* @return \Magento\Framework\Controller\ResultInterface|ResponseInterface
* @throws \Magento\Framework\Exception\NotFoundException
*/
public function execute()
{
/* Get the post data */
$data = $this->getRequest()->getParams();
/* Set the data in the model */
$carModel = $this->car;
$carModel->setData($data);
try {
/* Use the resource model to save the model in the DB */
$this->carResource->save($carModel);
$this->messageManager->addSuccessMessage("Car saved successfully!");
} catch (\Exception $exception) {
$this->messageManager->addErrorMessage(__("Error saving car"));
}
/* Redirect back to cars page */
$redirect = $this->resultRedirectFactory->create();
$redirect->setPath('helloworld');
return $redirect;
}
}
Now, when we open our favorite browser, and hit the URL http://mywebsite.com/helloworld/, we should be able to see our car list.
Putting It All Together
That's the core pattern behind every custom Magento 2 module: a Model to hold the data, a Resource Model to talk to the database, and a Collection when you need more than one row at a time. Once these three click for you, most other Magento data-handling code starts making a lot more sense.
Building something bigger than a simple car list? The Magento developers at Codilar can help you plan the structure before you write a single line of code, so you're not refactoring six months in.
FAQs
Yes. Every model must have a corresponding resource model, since the resource model handles the actual database read and write operations. Skipping this breaks the model's ability to save or load data.
A model represents a single row or entity (like one car), while a collection represents multiple rows fetched together, often with filters, joins, or sorting applied. Think of a collection as a group of models.
Yes. Collections support adding WHERE clauses, JOINs, GROUP BY, and ORDER BY, so you can shape the exact dataset you need before looping through it in your template or block.
Your resource model won't have access to core database methods like save, load, and delete, and Magento will likely throw errors when trying to interact with your custom table.
Yes, this Model, ResourceModel, Collection pattern is the standard MVC approach in Magento 2. If you're building something more complex, the developers at Codilar can help you structure it correctly from the start to avoid rework later.

eRetail Growth
in Mind?
Get tailored technology solutions to scale your retail business online
Request A Quote
Subscribe to
Stay in Know
Stay ahead with insights, trends, and brand success stories from the world of Digital Commerce.






