Architecture Overview

Every other page in this tutorial jumps straight into writing PHP files. That's deliberate — Epesi is built for rapid, low-code development — but it means a few terms (Module, RecordBrowser, RBO, Clearance) get used before they're explained. This page exists to fix that: read it first, then follow the tutorial in order starting with Using Other Modules.

Note: this page was written from what's documented across the rest of this tutorial (module lifecycle, templating, persistence layers), not from the Epesi PHP source itself. Where something below would benefit from a maintainer's confirmation, it's flagged.

The big picture

Epesi is a self-hosted PHP framework with an HMVC (Hierarchical Model-View-Controller) architecture: instead of one global MVC layer, every feature is its own self-contained unit — a module — that can have its own model, view, and controller logic, and can nest other modules inside itself. Epesi BIM (the CRM/ERP application most people mean when they say "Epesi") is not a separate codebase bolted on top of the framework — it's a collection of modules built with the exact same APIs available to you. Contacts, Calendar, Record Browser, the admin panel, permissions — all modules.

There's no separate "backend API" you call from a decoupled frontend. A module's PHP code renders its own output (increasingly via templates rather than direct print(), see below), and that output is composed into the page you see. If you're coming from a framework where the standard shape is "REST API + SPA frontend," the closer mental model here is classic server-rendered PHP with a component system — a module is closer to a self-contained widget/controller than to a microservice.

Anatomy of a module

Every module is a small set of PHP classes, conventionally three files, named after the module in CamelCase:

./epesi/modules/Custom/HelloWorld/
    HelloWorld_0.php          <- main module class, extends Module
    HelloWorldCommon_0.php    <- shared/static functions, extends ModuleCommon
    HelloWorldInstall.php     <- install lifecycle, extends ModuleInstall

The trailing _0 is a version suffix Epesi uses internally, not a typo — bump it if you ship a breaking rewrite of the module's main class.

The main class (extends Module) is the controller/view entry point. Its body() method is where output is produced:

<?php
defined("_VALID_ACCESS") || die('Direct access forbidden'); // every module file starts with this

class Custom_HelloWorld extends Module {
    public function body() {
        print('Hello World!');
    }
}

Note the class name: Custom_HelloWorld — the underscore encodes the directory path (Custom/HelloWorld) as part of the class name. This is how Epesi's autoloader finds the file without a separate namespace-to-path config; keep your folder name and class name in sync.

The Common class (extends ModuleCommon) holds static, shared logic — things other modules or the framework itself might call, most importantly menu registration:

class Custom_HelloWorldCommon extends ModuleCommon {
    public static function menu() {
        return array(__('Module') => array('__submenu__' => 1, __('Hello World') => array()));
    }
}

The Install class (extends ModuleInstall) defines the module's lifecycle hooks — install(), uninstall(), info() (author/license/description, shown in the module store), requires() (dependency list), and version(). Nothing runs until a module is installed through the admin panel (Server configuration → Modules Administration & Store, sometimes called "Main Setup"), which scans the modules directory, detects new modules, and lets an administrator install them.

From print() to templates

body() printing raw strings is the fastest path to "Hello World," not the recommended end state. As a module grows, Epesi expects you to move view logic into Smarty templates (.tpl files) rendered through the Base/Theme system, keeping body() focused on preparing data rather than emitting markup directly. The tutorial section Creating Theme covers template syntax, variables, and Smarty functions in detail.

Three layers of persistence

This is the part of the architecture most worth understanding before you write a data-driven module, because the tutorial teaches all three layers and it's easy to lose track of which one is "the modern way":

  1. Raw SQLDB::Execute(), an ADOdb-style API with %s/%d placeholders. Always available, rarely what you want for a new module's primary data.
  2. RecordBrowser — an array-based abstraction for defining a data set's fields (Utils_RecordBrowserCommon::install_new_recordset()) that gets you Epesi's standard list/filter/search/permissions UI for free. This is what most of the existing tutorial content teaches, and it's still what powers most of Epesi BIM itself.
  3. RBO (Objective RecordBrowser) — a fluent, class-based wrapper around the same underlying engine (RBO_Recordset, RBO_Record, and typed field builders like RBO_Field_Text, RBO_Field_Integer, RBO_Field_Calculated). RBO is the newer, recommended way to define a recordset — it's what the Hello World walkthrough later in this tutorial uses.

In short: RecordBrowser and RBO are two APIs over the same CRUD engine, not two competing systems. New modules should reach for RBO; understanding RecordBrowser's array-based conventions still matters because RBO is layered on top of it and a lot of existing/example code (including elsewhere in this tutorial) predates RBO.

Access control: Rules and Clearances

Permissions are not sprinkled through module code as if ($user->isAdmin()) checks. Instead, every Record Set has its own Rules (who can view/add/edit/delete which records, and which fields), and every user carries one or more Clearances (All Users, Admin, Superadmin, Employee, Access: Manager, and any custom clearances you define via the Contacts/Access CommonData table) that determine which Rules apply to them. A record set with no Rules is invisible to everyone except Super Administrators. This system is managed through the admin panel, not hand-written per module — see Record Browser Permissions in the Administer Epesi section for the editor itself.

Other systems you'll run into

  • Translations — wrap user-facing strings in __(), _M(), or _V() rather than hardcoding English text, so the ~40 languages the community has translated Epesi into keep working, and so your own module is translatable.
  • Patches — versioned upgrade scripts (patches/YYYYMMDD_description.php) that run once, in date order, when a module is updated. See Patch later in this tutorial.
  • CommonData — a shared tree of key/value lists (countries, statuses, custom picklists) that any module's select-type fields can source from, editable via Menu → Administrator → CommonData.
  • ActionBar / TabbedBrowser / GenericBrowser — the standard UI building blocks for action buttons, tabbed interfaces, and table browsing respectively, covered in their own tutorial pages.

There is no separate hook/event system beyond what's scoped to RecordBrowser itself — see "Pre/Post-Processing (Triggers)" in the Utils/RecordBrowser reference if you need to run code around a record's save/delete lifecycle.

If you need to integrate from outside PHP

Everything above assumes you're extending Epesi in-process, as a PHP module — that's the primary, well-documented extension path, and there is no REST/JSON API or CLI tool for external integration. There is, however, a working HTTP+XML CRUD interface (get_records.php, get_record.php, save_record.php, authenticate.php) documented under Premium Sync later in this tutorial — it's a real integration option if you need to talk to Epesi from outside PHP, it's just easy to miss since nothing else on this site labels it as an "API."

Where to go next

Follow the tutorial in order from here: Using Other Modules → Creating Modules → Using Translations → Using HTML Forms → Advanced Options → Using ActionBar → Using Tabbed Browser → Creating Theme → Using Table Browser → Using Templates → Hello World (full worked example with RBO) → Utils/RecordBrowser and Utils/RBO (reference) → Shoutbox / Premium Sync (complete feature modules) → Patch.