Quickstart
Get CRM running locally and build your first custom module in under 10 minutes.
Prerequisites
Before you begin, make sure you have the following installed on your machine:
| Requirement | Version | Notes |
|---|---|---|
| Docker Desktop | 4.x+ | Docker Compose v2 included |
| Git | Any recent | For cloning the repo |
| curl | Any recent | For testing API endpoints |
| PHP | 7.4 (in Docker) | Provided by the Docker image |
You'll also need access to the platform/ directory from the CRM-PHP repository, specifically:
platform/Deployments/deploy-package/CRM-PHP-Docker/— Docker stackplatform/Deployments/deploy-package/CRM-PHP-Docker/crm/httpdocs/— The web root
Install CRM via Docker
The entire CRM stack — nginx, PHP/Apache, MySQL 8.0, LangChain — runs in Docker Compose. Start it with one command.
# Navigate to the Docker stack directory
cd platform/Deployments/deploy-package/CRM-PHP-Docker/
# Copy the example environment file and fill in your credentials
cp .env.example .env
# Start the full stack (nginx, PHP app, MySQL, LangChain)
docker compose up -d
# Wait ~30 seconds for MySQL to initialize, then check status
docker compose ps
Once all containers are running:
| Service | URL |
|---|---|
| CRM Web App | http://localhost:8090 |
| phpMyAdmin | http://localhost:8081 |
http://localhost:8090 with admin / 111. API base URL: http://localhost:8090/api/v1.
Useful Docker Commands
# View app container logs
docker compose logs -f crm-app
# Execute a command inside the PHP container
docker exec -it crm-app bash
# Restart only the app container (after code changes)
docker compose restart crm-app
# Clear Smarty compiled template cache
docker exec crm-app rm -rf /var/www/html/httpdocs/files/themes/default/*
# Stop the entire stack
docker compose down
Create Your First Module
A module consists of two files: a PHP controller and a Smarty template. No registration is needed for a public-facing module — just create the files and visit the URL.
Controller: modules/hello.php
<?php
// httpdocs/modules/hello.php
pb_on_action('view', function () {
pb_title('Hello World');
pb_template('hello');
pb_set_tpl_var('message', 'Welcome to CRM!');
pb_set_tpl_var('items', ['Contacts', 'Organizations', 'Support Tickets']);
});
Create this file at:
platform/Deployments/deploy-package/CRM-PHP-Docker/crm/httpdocs/modules/hello.php
Template: themes/default/hello.tpl
{* httpdocs/themes/default/hello.tpl *}
{if $m.action eq 'view'}
{include file="block_begin.tpl"}
<h1>{$m.message}</h1>
<p>This module is working. Here are the main CRM sections:</p>
<ul>
{foreach from=$m.items item=item}
<li>{$item}</li>
{/foreach}
</ul>
{include file="block_end.tpl"}
{/if}
Create this file at:
platform/Deployments/deploy-package/CRM-PHP-Docker/crm/httpdocs/themes/default/hello.tpl
Access Your Module via URL
No restart or build step needed — PHP files are served directly. Open your browser and visit:
http://localhost:8090/index.php?m=hello&d=view
You should see the CRM dashboard layout with your "Hello World" heading and the list of CRM sections.
?m= is the module name (filename without .php), and ?d= is the action name (passed to pb_on_action()). The URL ?m=hello&d=view loads modules/hello.php and calls the 'view' action handler.
Test the API Endpoint Too
# Get an access token
curl -X POST http://localhost:8090/api/v1/login \
-H "Content-Type: application/json" \
-d '{"login":"webmaster@local.dev","password":"111","device_name":"quickstart"}'
# Use the token to call app-state
curl http://localhost:8090/api/v1/common/app-state \
-H "access_token: YOUR_TOKEN_HERE"
Create an Admin Module
Admin modules are registered in the Module Management UI and support install/uninstall lifecycle hooks. They require a /* * Module Name: ... */ comment so the UI can discover them.
Create: modules/hello-admin.php
<?php
/* * Module Name: Hello Admin */
use PB\Access\PBAccess;
use PB\Common\Redirect;
use PB\Core\PBURL;
use PB\UI\UI;
// Main admin page
pb_on_action('admin', function () {
PBAccess::AdminRequired();
pb_title('Hello Admin');
pb_template('hello-admin');
$ui = new UI();
PB::BreadCrumbs()->Add('Hello Admin', PBURL::CurrentModule('admin'));
$ui->NotificationInfo('Hello from the admin module!');
$ui->Output(true);
});
// Install — creates DB tables and registers the module
pb_on_action('install', function () {
PBAccess::AdminRequired();
pb_register_module('hello-admin', 'Hello Admin');
// Create your tables here:
execsql("CREATE TABLE IF NOT EXISTS hello_items (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
id_company INT UNSIGNED NOT NULL DEFAULT 0,
title VARCHAR(255) NOT NULL DEFAULT '',
timeadded INT UNSIGNED NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
Redirect::Now(PBURL::AdminModulesManagement());
});
// Uninstall — drops DB tables and unregisters the module
pb_on_action('uninstall', function () {
PBAccess::AdminRequired();
pb_unregister_module('hello-admin');
execsql("DROP TABLE IF EXISTS hello_items");
Redirect::Now(PBURL::AdminModulesManagement());
});
Create: themes/default/hello-admin.tpl
{* themes/default/hello-admin.tpl *}
{if $m.action eq 'admin'}
{include file="block_begin.tpl"}
{* The UI component system renders the content here automatically *}
{include file="block_end.tpl"}
{/if}
Install the Module
- Log in as admin at
http://localhost:8090 - Go to Admin → Modules → Manage Modules
- Find Hello Admin in the list
- Click Install
- The module is now active and the
hello_itemstable is created
Visit the admin page at:
http://localhost:8090/index.php?m=hello-admin&d=admin
Next Steps
You have a working module! Here's what to explore next:
Mobile API Reference
Explore all REST endpoints: contacts, activities, support tickets, push notifications, and more.
View Reference →Module Dev Guide
Deep dive into the ORM, UI components, Smarty templates, URL helpers, and the complete polls example.
Read Guide →Swagger UI Explorer
Try API calls directly in the browser with the interactive Swagger UI connected to the live API.
Open Explorer →Key Things to Remember
- Always use
execsql(),getsqlfield(),getsqlarray(),insertsql()— neverdbquery() - Never use
{literal}in Smarty templates — put JS in external.jsfiles loaded viapb_add_jsfile() - Call
PBAccess::AdminRequired()at the top of every admin action - Use
PB::POST($key)->AsString()/AsAbsInt()— never read$_POSTdirectly - Admin modules need 3 actions:
admin,install, anduninstall - The URL pattern is always
?m=module-name&d=action-name - DB date columns use Unix timestamps (
timeadded,created_time) — nevercreated_at
Deploy to Production
When you're ready to deploy your module to the production server:
# From the repo root, commit your module files
git add platform/Deployments/deploy-package/CRM-PHP-Docker/crm/httpdocs/modules/hello.php
git add platform/Deployments/deploy-package/CRM-PHP-Docker/crm/httpdocs/themes/default/hello.tpl
git commit -m "feat: add hello module"
# Deploy to the your production server
./platform/deploy-client.sh [clientname] "add hello module"
docker exec crm-app rm -rf /var/www/html/httpdocs/files/themes/default/* on the production server.