In this Opencart 3 tutorial we are showing how to create Opencart 3 custom modules or extensions, this tutorial is for the developer and to add the custom functionalities in the Opencart. Check our hello world workflow.
- Before you start, watch the following videos tutorial
- Describing files and folder structure of Opencart With this you will get knowledge of how files and folders are structured in Opencart
- OpenCart 3 Library Global objects Methods With this video tutorial you will get where you can find the existing methods and functions to use while writing the code.
- Then go through the Code flow, Request & response, and MVCL pattern in OpenCart, by which you will know how MVCL works for modules and extension creations.
- At last, go to Install, configure, uninstall, remove the OpenCart module video tutorial guide then you will be ready to start your module development.
- Basic twig templating and bootstrap CSS knowledge
- Then start to work in the admin folder for module management
For Opencart 2 follow this post: How to create the controller for the Opencart custom module?
For Opencart 3, it is similar to Opencart 2 with some changes in the view section, which we are showing below.- To create the Controller in OpenCart 3, we need to understand at least four methods (index, install, validate and uninstall)
- To create the language file in OpenCart 3, we need to understand how to create variable and how we can load it in the controller and how it automatically get variable access to view.
- To create the view in Opencart 3, we need to know twig templating.
- Then start to work in the catalog for module show in front or presentation
- It also needed three files language, controller and view
- Before starting, things to consider while creating the modules are as per Daniel are:
- You are not allowed to modify any core DB tables. If you need to store any data for your extension you need to create a new table and use joins. But you can insert it into the setting table as the configuration are added to it
- You are not allowed to overwrite any files.
- All files are only allowed to be written in extension folders, except templates which of course go under the template folder.
- All extension should have an admin page that allows configuration
- Extensions are now prefixed by their category. so paypal_status would become payment_paypal_status
- Here is the files and folders structure of Hello World module

OpenCart identifies existing modules automatically, simply by reading the admin/controller/extension/module folder. Any modules existing in this folder will automatically be shown on the Modules listing page, and on the User Permissions page.
More detail for Opencart 2 are at https://webocreation.com/admin-controller-file-make-hello-world-module-opencart-module-development/
Let's start with Admin Controller
The file to create is helloworld.php and it should be inside admin/controller/extensions/module/ and start with the following boilerplate code:
In OpenCart, Every class name of the module should start with ControllerExtensionModule, here in the hello world module, our folder structure is controller >> extension >> module >> helloworld.php so the class name is ControllerExtensionModuleHelloworld then extends the class with the base class controller. Better to have all the four methods index() will run when the controller is loaded or someone clicks the edit button, validate() checks for the admin access and other data validation, install() method runs when we click the install button on the module list page, and uninstall() method runs when we click the uninstall button.
private $error = array();
$error is just a private array which will hold the values if some error occurs, we assign error if users don't have access, or submitted data are not valid.
Installation method:
This method is called when someone clicks the install + button
public function install()
{
$this->load->model('setting/setting');
$this->model_setting_setting->editSetting('module_helloworld', ['module_helloworld_status' => 1]);
}
Here we load the setting model, you can see the editSetting() method at controller >> model >> setting >> setting.php. When the install button is clicked then it deletes all the settings it had with the same code and installs the all values again. While passing the values don't forget to include the extension in front of the name. Like here our extension is module and name is HelloWorld so it becomes module_helloworld and the same for the variables you are going to save, we are saving the status so module_helloworld_status which need to pass as an array. Let see the setting database table, and you will see how values are stored.

The convention is to add module_ or payment_ or shipping_ or report_ or dashboard_ etc as per your extensions type so it is supported in the cloud base as well.
After installation, we click the edit blue button, which calls the index method whose full code is below which we will describe afterward:
public function index()
{
$this->load->language('extension/module/helloworld');
$this->document->setTitle($this->language->get('heading_title'));
$this->load->model('setting/module');
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
if (!isset($this->request->get['module_id'])) {
$this->model_setting_module->addModule('helloworld', $this->request->post);
} else {
$this->model_setting_module->editModule($this->request->get['module_id'], $this->request->post);
}
$this->session->data['success'] = $this->language->get('text_success');
$this->response->redirect($this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true));
}
if (isset($this->error['warning'])) {
$data['error_warning'] = $this->error['warning'];
} else {
$data['error_warning'] = '';
}
if (isset($this->error['name'])) {
$data['error_name'] = $this->error['name'];
} else {
$data['error_name'] = '';
}
$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_home'),
'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_extension'),
'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true)
);
if (!isset($this->request->get['module_id'])) {
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('extension/module/helloworld', 'user_token=' . $this->session->data['user_token'], true)
);
} else {
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('extension/module/helloworld', 'user_token=' . $this->session->data['user_token'] . '&module_id=' . $this->request->get['module_id'], true)
);
}
if (!isset($this->request->get['module_id'])) {
$data['action'] = $this->url->link('extension/module/helloworld', 'user_token=' . $this->session->data['user_token'], true);
} else {
$data['action'] = $this->url->link('extension/module/helloworld', 'user_token=' . $this->session->data['user_token'] . '&module_id=' . $this->request->get['module_id'], true);
}
$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true);
if (isset($this->request->get['module_id']) && ($this->request->server['REQUEST_METHOD'] != 'POST')) {
$module_info = $this->model_setting_module->getModule($this->request->get['module_id']);
}
if (isset($this->request->post['name'])) {
$data['name'] = $this->request->post['name'];
} elseif (!empty($module_info)) {
$data['name'] = $module_info['name'];
} else {
$data['name'] = '';
}
$this->load->model('localisation/language');
$data['languages'] = $this->model_localisation_language->getLanguages();
if (isset($this->request->post['status'])) {
$data['status'] = $this->request->post['status'];
} elseif (!empty($module_info)) {
$data['status'] = $module_info['status'];
} else {
$data['status'] = '';
}
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');
$this->response->setOutput($this->load->view('extension/module/helloworld', $data));
}
Let start understand in detail:
$this->load->language('extension/module/helloworld');
This is to load the language file admin >> language >> en-gb (or active language) >> extension >> module >> helloworld.php. Let's create the language file and add some variables needed:
Now to get access to heading_title in the controller we do like below:
$this->language->get('heading_title')
To assign that heading_title as Title of the page we pass the $this->document->setTitle, click to know all of the objects' methods of opencart
$this->document->setTitle($this->language->get('heading_title'));
Below is the code of how we define the breadcrumbs array and assign in the data variable and pass it to view.
$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_home'),
'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_extension'),
'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true)
);
if (!isset($this->request->get['module_id'])) {
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('extension/module/helloworld', 'user_token=' . $this->session->data['user_token'], true)
);
} else {
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('extension/module/helloworld', 'user_token=' . $this->session->data['user_token'] . '&module_id=' . $this->request->get['module_id'], true)
);
}
In the code above we use $this->language->get('text_home') so you may got confused how we got text_home variable although we don't define in admin >> language >> en-gb >> extension >> module >> helloworld.php, so the trick is we can get access to all variables of admin >> language >> en-gb >> en-gb.php we don't need to load like other language file. So the breadcrumb here is to create the breadcrumbs array then assign the Home and dashboard URL, then added another link Extension and URL to module page. Then we check if the module is an edited or a new module and show the title and link of respective modules.
Now let's move to another part of the code, the below part of the code is to create the action URL when someone submits or save the form. Here it checks if it is an active module or not and if it is an active module then it passes module_id in the URL
if (!isset($this->request->get['module_id'])) {
$data['action'] = $this->url->link('extension/module/helloworld', 'user_token=' . $this->session->data['user_token'], true);
} else {
$data['action'] = $this->url->link('extension/module/helloworld', 'user_token=' . $this->session->data['user_token'] . '&module_id=' . $this->request->get['module_id'], true);
}
In OpenCart module setting mostly, they have to save button and cancel button so below is the link when someone is redirected when they click the cancel button.
$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true);
The below code is to check if the module exists and if then get the available module information of that module.
if (isset($this->request->get['module_id']) && ($this->request->server['REQUEST_METHOD'] != 'POST')) {
$module_info = $this->model_setting_module->getModule($this->request->get['module_id']);
}
The below code is to set the data of the fields to pass to the view. Here we check if the module name is set or module description and the status like below if nothing is set then we assign the empty.
if (isset($this->request->post['name'])) {
$data['name'] = $this->request->post['name'];
} elseif (!empty($module_info)) {
$data['name'] = $module_info['name'];
} else {
$data['name'] = '';
}
if (isset($this->request->post['module_description'])) {
$data['module_description'] = $this->request->post['module_description'];
} elseif (!empty($module_info)) {
$data['module_description'] = $module_info['module_description'];
} else {
$data['module_description'] = array();
}
if (isset($this->request->post['status'])) {
$data['status'] = $this->request->post['status'];
} elseif (!empty($module_info)) {
$data['status'] = $module_info['status'];
} else {
$data['status'] = '';
}
This below code is to pass the languages to the view, as OpenCart supports multi-language so we are showing you how module_description and status can be set as per the language.
$this->load->model('localisation/language');
$data['languages'] = $this->model_localisation_language->getLanguages();
The below are the data to pass the header, column left and the footer to the view.
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');
The below code is to set the response output to view.
$this->response->setOutput($this->load->view('extension/module/helloworld', $data));
When someone saves the module then following code run and save the module data in an oc_module table in the JSON format.
$this->load->model('setting/module');
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
if (!isset($this->request->get['module_id'])) {
$this->model_setting_module->addModule('helloworld', $this->request->post);
} else {
$this->model_setting_module->editModule($this->request->get['module_id'], $this->request->post);
}
$this->session->data['success'] = $this->language->get('text_success');
$this->response->redirect($this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true));
}

When someone submits the form it validates the data with the validate() method as we call it in index() method like $this->validate(), in validate method we mostly check for the permission and define any other validation needed.
protected function validate()
{
if (!$this->user->hasPermission('modify', 'extension/module/helloworld')) {
$this->error['warning'] = $this->language->get('error_permission');
}
if ((utf8_strlen($this->request->post['name']) < 3) || (utf8_strlen($this->request->post['name']) > 64)) {
$this->error['name'] = $this->language->get('error_name');
}
return !$this->error;
}
Now the last part of the controller is the uninstall method, it uninstalls and removes all the data when someone clicks the uninstall red button.
public function uninstall()
{
$this->load->model('setting/setting');
$this->model_setting_setting->deleteSetting('module_helloworld');
}
Now our last part of admin View or presentation part, as opencart started to use the twig templating so here is the full code of the view part:
{{ header }}{{ column_left }}
{{ footer }}
Here is the output of the view:

The variable we passed from the controller can be accessible in template file which we can show easily with {{variable_name}}, so we can show header, column left and footer like below:
{{ header }}{{ column_left }}{{ footer }}
All other codes are simple in the view part only thing little complicate is the language for loop section and how we add form field name which will be saved in the database. Here heading title and status are language-specific.
Now the front end part so let's start to create a file in catalog/ folder, go to catalog >> controller >> extension >> module and create helloworld.php and paste the following code:
config->get('config_language_id')])) {
$data['html'] = html_entity_decode($setting['module_description'][$this->config->get('config_language_id')]['title'], ENT_QUOTES, 'UTF-8');
return $this->load->view('extension/module/helloworld', $data);
}
}
}
Mostly same conventions to follow as for the admin controller as we define above, here we need to pass the $setting in the index method and all values and data of module is available for your in $setting, if you are confused just do the print_r($setting) and you will get all the details data of the module. Here we check if the module is active and just show the title that we entered in the backend.
View section is also pretty simple in the frontend. Go to catalog >> view >> theme >> default >> template >> extension >> module >> helloworld.twig and enter the code below:
{% if html %}
{{ html }}
{% endif %}
It check if the html is not empty and if not empty then show the html content.
Now all of our code is ready, now you can show it in your desired layout as per requirement. If you are confused about how to install the module and set the configuration then this video helps.
Comment below or let us know if you have any questions or suggestions.