Modules
- Introduction
- Development Environment
- Create your First Module
- Localization
- Add Menu Items
- Add Custom Permissions
- Mailable Templates
- Notifications
- Module Relations
- Module Archive
Introduction
Ability to create custom modules is added in Concord CRM version 1.5.
Modules are packages of code that expand the core functionality of Concord CRM. By developing your own modules, you can enhance Concord CRM or add entirely new features.
Concord CRM is using the nWidart/laravel-modules Laravel package to provide support for modules.
By default, Concord CRM includes numerous core modules that are essential and cannot be disabled.
Maintenance
You are responsible for maintaining the modules you create, ensuring they remain compatible with the latest version of Concord CRM when major changes occur.
Concord CRM is still a new modular application. With each update, we aim to enhance the code and make it more modular. Due to this, there will be breaking changes, and you will need to adapt your modules accordingly.
Security
Concord CRM is built with the Laravel PHP framework, which follows security practices to ensure reliability.
When creating new modules, you must ensure that your modules are secure and utilize existing functions and methods within the Laravel PHP framework.
Selling Modules on CodeCanyon
If you want to create modules and sell them as an author on CodeCanyon, you are free to do so. However, keep in mind that due to our exclusivity agreement, you can ONLY sell the modules on CodeCanyon.
Selling Concord CRM modules on CodeCanyon means you are responsible for setting the appropriate price of the module, presenting the module, offering support, ensuring compatibility with the latest version changes, and developing your marketing strategy.
You cannot present us as a partner, either as an individual or a company. Publishing a module from your Envato account means you own all the copyright to the module and the code.
At our discretion, we may decide to market your module or recommend it to our customers.
Support for Module Creation
We will not offer support for creating modules. If you are certain that something is not working correctly and believe it to be a bug, you can open a support ticket at Concord CRM Support and provide more information. We will be happy to take a look.
However, please note that we do not provide guidance on how to create specific features. Module development support is not included. You are encouraged to figure things out on your own, or explore the code if you need specific functions.
Development Environment
You should ensure that the module is compatible with the minimum required PHP version for Concord CRM.
Before starting to module(s) development for Concord CRM, you will need a Laravel development environment in place (php, composer, node, npm etc...).
If you are not familiar with Laravel, we highly recommend to first read Laravel Installation Guide
After your development environment is configured, you will need to perform clean installation of Concord CRM.
- Extract the Concord CRM files e.q. in
~/Herd/concordcrm
. - Perform fresh Concord CRM installation.
- Run
composer update
to install the dev dependencies. - Install NPM packages by running
npm install
. - Edit the
.env
file and set theAPP_ENV
config key tolocal
. - Edit the
.env
file and set theAPP_DEBUG
config key totrue
. - Edit the
.env
file and set theMODULE_CACHE_ENABLED
config key tofalse
. - Edit the
.env
file and set theMODEL_CACHE_ENABLED
config key tofalse
. - Edit the
.env
file and set theIDENTIFICATION_KEY
config key tomodule-development
. - Clear cache by running the
php artisan core:clear-cache
command.
Find an example .env
file for development purposes below, of course, you will need to adjust the env variables values as per your development environment:
APP_NAME="Concord CRM"
APP_ENV=local
# Make sure the "APP_KEY" is set.
APP_KEY=base64:...
APP_DEBUG=true
APP_URL=http://concordcrm.test
SANCTUM_STATEFUL_DOMAINS=concordcrm.test,localhost:${APP_PORT}
IDENTIFICATION_KEY=module-development
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=concordcrm
DB_USERNAME=root
DB_PASSWORD=
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
DEBUGBAR_ENABLED=true
MODULE_CACHE_ENABLED=false
MODEL_CACHE_ENABLED=false
Create your First Module
All Concord CRM modules must be placed in the modules
folder located in the root of your Concord CRM installation. Each module must have a unique folder name and a unique name defined in the module.json
file.
To create a module use the following artisan command:
php artisan module:make ModuleName
Please note that the module name must be a studly case.
After the command is executed a module with basic starting code will be created in the modules
directory, the full path of the module will be modules/[ModuleName]
.
Navigate to Settings -> Modules, you will be able to notice that the module is listed in the available modules list.
Module JSON file
In the root of your module, you will find a module.json
file. The most common configuration keys you will need to edit in this file are:
- version: This specifies your module's version.
- description: A short description of your module.
- providers: Your module's Laravel service providers to autoload.
- requires_at_least: The minimum version of Concord CRM required for the module to function properly.
Assets
Please refer to the CSS & Javascript guides.
Module composer.json
File
Your module's composer.json
file is crucial for managing dependencies and ensuring compatibility with Concord CRM. Below are important guidelines to follow when configuring your module's composer.json
.
1. Avoid Redundant Dependencies
Do not install dependencies that already exist in the root composer.json
of Concord CRM. These dependencies should be used directly in your module code becauase they exist in core.
2. Managing Dependencies with Composer
To prevent installing the Laravel framework or related packages redundantly, use the provide
configuration key in your module's composer.json
.
When you require a specific Laravel package in your module, Composer may attempt to install the Laravel framework or other related packages even if they are already present in the root composer.json
. This happens because Composer does not automatically recognize that these dependencies are satisfied by the root project. By using the provide
key, you can explicitly tell Composer that these packages are already provided, preventing duplicate installations.
3. Shipping with Vendor Folder
When uploading a module to Codecanyon, ensure that the module is shipped with the vendor
folder.
Example composer.json
configuration for a module:
{
"name": "vendor/module",
"description": "Example module",
"autoload": {
"psr-4": {
"Modules\ModuleName\": "app/",
"Modules\ModuleName\Database\Factories\": "database/factories/",
"Modules\ModuleName\Database\Seeders\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Modules\ModuleName\Tests\": "tests/"
}
},
"require": {},
"provide": {
"laravel/framework": "*",
"illuminate/support": "*",
...
}
}
Bootstrap Module
To bootstrap module and add hooks like enabling
, enabled
, disabling
, disabled
, deleting
, deleted
you should use the module bootstrap/module.php
file.
use Modules\Core\Facades\Module;
use Illuminate\Contracts\Foundation\Application;
return Module::configure('invoices')
->enabled(function (Application $app) {
// Activate any module services
})
->disabled(function (Application $app) {
// Deactivate any module services
})
->deleted(function (Application $app) {
// Perform clean up
});
Of course, you are free to create your own modules event using Laravel events feature to provide ability developers listen for specific events or merge two or more modules together by communicating with events.
Localization
Concord CRM is fully localizeable using the Laravel localization features with few additions added specifically for Concord CRM requirements, you should ensure your module is fully translatable in different languages by using text via the localization feature helper functions.
This will ensure customer will be able to translate the module content in a different language using Concord CRM built-in translator. In most cases, you will develop the module in the en
locale.
When you create a module using the module:make
command, the scaffold will automatically create the lang
folder in your module root directory and will register the language namespace using the module name as a namespace.
The namespace of the translation will be the module name in lower case, you can use Laravel localization helper function to retrieve translated text:
public function show()
{
abort(Response::HTTP_CONFLICT, __('invoices::group.key'));
}
The group is a file in the locale directory e.q. modules/Invoices/lang/en/[GROUP].php
.
Localizing Vue Components
Please refer to the CSS & Javascript guides.
Add Menu Items
To add menu item from module to the dashboard sidebar menu, in your module service provider add method menu
:
use Modules\Core\Facades\Menu;
use Modules\Core\Menu\MenuItem;
protected function menu() : MenuItem
{
return MenuItem::make(__('modulename::group.key'), '/invoices')
->icon('CurrencyDollar')
->position(15)
->badge(fn () => Model::count())
->badgeVariant('info');
}
The first argument of the MenuItem
class's make
method is the item label, the second argument is the route to redirect to upon clicking, and the third argument is the icon of the menu item.
To add multiple menu items, the menu
method should return an array of items.
The icon must be one of the available icons in the Icons.vue
file, excluding the "Icon" suffix.
Next, you need to add a front-end route via Vue Router to render a Vue component. This component will display the content for the added route when the menu item is clicked.
<!-- modules/[ModuleName]/resources/js/views/InvoicesIndex.vue-->
<template>Hello from InvoicesIndex.vue</template>
<script setup></script>
// modules/[ModuleName]/resources/app.js
import { translate } from "core/i18n";
import InvoicesIndex from "./views/InvoicesIndex.vue";
if (window.Innoclapps) {
Innoclapps.booting(function (app, router) {
router.addRoute({
path: "/invoices",
component: InvoicesIndex,
meta: {
title: translate("modulename::group.key"),
},
});
});
}
Make sure that the development server is started by running the npm run dev
command and visit the route added.
Add Custom Permissions
Concord CRM has roles and permissions features that can be used to authorize certain actions, using the Spatie laravel-permissions package.
You can register custom permissions specific to the module you are developing that administrator can choose when assigning a role.
In the module service provider boot
method you can register permissions as shown below:
Innoclapps::permissions(function ($manager) {
$group = ['name' => 'invoices', 'as' => 'invoices'];
$manager->group($group, function ($manager){
$manager->view('view', [
'as' => __('core::role.capabilities.view'),
'permissions' => [
'view own invoices' => __('core::role.capabilities.owning_only'),
'view all invoices' => __('core::role.capabilities.all', ['resourceName' => 'Invoices']),
'view team invoices' => __('users::team.capabilities.team_only'),
],
]);
$manager->view('edit', [
'as' => __('core::role.capabilities.edit'),
'permissions' => [
'edit own invoices' => __('core::role.capabilities.owning_only'),
'edit all invoices' => __('core::role.capabilities.all', ['resourceName' => 'Invoices']),
'edit team invoices' => __('users::team.capabilities.team_only'),
],
]);
$manager->view('delete', [
'as' => __('core::role.capabilities.delete'),
'revokeable' => true,
'permissions' => [
'delete own invoices' => __('core::role.capabilities.owning_only'),
'delete any invoice' => __('core::role.capabilities.all', ['resourceName' => 'invoices']),
'delete team invoices' => __('users::team.capabilities.team_only'),
],
]);
$manager->view('bulk_delete', [
'permissions' => [
'bulk delete invoices' => __('core::role.capabilities.bulk_delete'),
],
]);
});
});
After navigating to Settings -> Users -> Roles you will be able to see the registered permissions when creating/editing the role.
To use the permissions, you will need use the Laravel authorizations feature, you can either check directly in the code whether the user has permission or use Policy to perform specific authorizations.
To create module related policy use the module:make-policy
artisan command, for example:
php artisan module:make-policy InvoicePolicy Invoices
Executing the command will create a InvoicePolicy.php
file in the module app/Policies
directory:
// modules/Invoices/app/Policies/InvoicePolicy.php
namespace Modules\Invoices\Policies;
use Illuminate\Auth\Access\HandlesAuthorization;
class InvoicePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the invoice.
*/
public function view(User $user, Invoice $invoice): bool
{
if ($user->can('view all invoices')) {
return true;
}
if ((int) $invoice->user_id === (int) $user->id) {
return true;
}
if ($invoice->user_id && $user->can('view team invoices')) {
return $user->managesAnyTeamsOf($invoice->user_id);
}
return false;
}
}
In your controller you can simply authorize the action regularly using Laravel authorization features:
// modules/Invoices/app/Http/Controllers/Api/InvoiceController.php
public function show(string $id)
{
$invoice = Invoice::findOrFail($id);
$this->authorize('view', $invoice);
}
Mailable Templates
Concord CRM has a mailable templates feature that the administrator can edit via the dashboard to suit their requirements. To create a mailable template, use the module:make-mailable-template
command as shown below:
php artisan module:make-mailable-template OrderShipped Invoices
The OrderShipped.php
file will be created in the module app/Mail
directory, you will need to register the template in the module service provider:
use Modules\Core\Facades\SettingsMenu;
use Modules\Core\Settings\SettingsMenuItem;
use Modules\Core\Support\ModuleServiceProvider;
class InvoicesServiceProvider extends ModuleServiceProvider
{
protected array $mailableTemplates = [
\Modules\Invoices\Mail\OrderShipped::class,
];
}
You don't need to set a subject or any content for the mailable because the user will provide this information. However, it's recommended to add default content. This gives the user a starting point and helps them understand what kind of template they are editing.
To set default content, use the default
method of the mailable template and return a new DefaultMailable
instance.
use Modules\Core\MailableTemplate\DefaultMailable;
public static function default(): DefaultMailable
{
return new DefaultMailable('Default content', static::name());
}
Now, navigate to Settings -> Mail Templates. You will see the previously created template in the list of available templates for all locales.
Use this mailable template as a regular Laravel mailable to send emails from anywhere in the module codebase.
namespace Modules\Invoices\Http\Controllers\Api;
use Modules\Core\Http\Controllers\ApiController;
use Modules\Invoices\Mail\OrderShipped;
use Modules\Invoices\Models\Order;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
class OrderShipmentController extends ApiController
{
/**
* Ship the given order.
*/
public function store(Request $request): JsonResponse
{
$order = Order::findOrFail($request->order_id);
// Ship the order...
Mail::to($request->user())->send(new OrderShipped($order));
return $this->response('', JsonResponse::HTTP_NO_CONTENT);
}
}
Notifications
In most cases, to send emails and notifications to users, you will want to use the Laravel notification feature. Concord CRM already uses this feature with a few specific additions.
With the notifications feature, you can send notifications to multiple channels. For example, you can notify a user that an invoice is overdue via email and notification (bell).
To create a Concord CRM notification, use the module:make-notification
command as shown below:
php artisan module:make-notification OrderShipped Invoices
The OrderShipped.php
file will be created in your module app/Notifications
directory, you will need to register the notification in the module service provider:
use Modules\Core\Facades\SettingsMenu;
use Modules\Core\Settings\SettingsMenuItem;
use Modules\Core\Support\ModuleServiceProvider;
class InvoicesServiceProvider extends ModuleServiceProvider
{
protected array $notifications = [
\Modules\Invoices\Notifications\OrderShipped::class,
];
}
Use this notification as regular Laravel notification to send from anywhere in the module codebase:
namespace Modules\Invoices\Http\Controllers\Api;
use Modules\Core\Http\Controllers\ApiController;
use Modules\Invoices\Notifications\OrderShipped;
use Modules\Invoices\Models\Order;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class OrderShipmentController extends ApiController
{
/**
* Ship the given order.
*/
public function store(Request $request): JsonResponse
{
$order = Order::findOrFail($request->order_id);
// Ship the order...
$request->user()->notify(new OrderShipped($order));
return $this->response('', JsonResponse::HTTP_NO_CONTENT);
}
}
To send an email from the notification, you will need to use a previously created mailable template or regular Laravel mailable.
namespace Modules\Contacts\Notifications;
use Illuminate\Contracts\Queue\ShouldQueue;
use Modules\Invoices\Mail\OrderShipped as OrderShippedMailable;
use Modules\Invoices\Models\Order;
use Modules\Core\MailableTemplate\MailableTemplate;
use Modules\Core\Notification;
class UserAssignedToContact extends Notification implements ShouldQueue
{
/**
* Create a new notification instance.
*/
public function __construct(protected Order $contact) {}
/**
* Get the mail representation of the notification.
*/
public function toMail(object $notifiable): OrderShippedMailable&MailableTemplate
{
return (new OrderShippedMailable($this->order))->to($notifiable);
}
/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
public function toArray($notifiable): array
{
return [
'path' => sprintf('/orders/%s', $this->order->id).
'lang' => [
'key' => 'invoices::order.notifications.shipped',
'attrs' => [
'number' => $this->order->number,
],
],
];
}
}
If the notification does not need to send an email, you must instruct Concord CRM that no email channel is available for this notification. This ensures that the email channel checkbox won't appear in the user notification settings.
/**
* Provide the notification available delivery channels.
*/
public static function channels(): array
{
return ['database', 'broadcast'];
}
Module Relations
To add a relation to a module you don't have access to, you should use the Laravel dynamic way for resolving relations.
use Modules\Invoices\Models\Order;
use Modules\Billing\Models\Customer;
Order::resolveRelationUsing('customer', function (Order $orderModel) {
return $orderModel->belongsTo(Customer::class, 'customer_id');
});
Please refer to the Laravel documentation for more information.
Module Archive
When you are done developing the module and want to prepare it for the end customer, such as uploading it for sale on the CodeCanyon platform, you need to prepare the module .zip
archive.
Before creating the archive, you will need to perform a few steps:
Preparing the Assets
If your module has assets, include them in the .zip
archive so Concord CRM can copy them to the public
directory upon activation. Assets are any static files that should exist in the public directory.
- In the module root directory, create a folder named
public
and move any assets there. - To include Vite assets, run
npm run build-isolated
in the module root directory. This command will build the Vite assets tomodules/[ModuleName]/public/build
instead ofpublic/ModuleName/build
.
Please note that the build
directory is the one that is used in your module service provider:
Innoclapps::vite('resources/js/app.js', 'modules/'.$this->moduleNameLower().'/build');
If for some reason you decided to use a different directory, you will need to adjust this directory as well in vite.config.js
, packages.json
and in your service provider as it was shown above.
Concord CRM will publish the assets in the public/modules/[ModuleName]
directory after the module is enabled and will delete them when the module is disabled or deleted.
Vendor Folder
Since most customers installing Concord CRM don't have Composer on their server, include the vendor
folder in the .zip
archive. Concord CRM will autoload this folder automatically.
Creating Module Archive
Create a .zip
archive including the module folder, the archive structure should be like this:
- Invoices (the module folder name)
-- app
-- database
-- module.json
-- ...
After creating the module .zip
archive, always test if uploading, enabling, disabling, and deleting the module works fine and does not trigger any errors that may stop the customer installation from working.