1. Creating a Module
This tutorial will guide you through creating a simple "My Module" that adds a new menu item and a corresponding page.
Step 1: Create the Module Directory
All modules live inside the private/modules/
directory. Create a new folder for your module. The folder name should be unique and use PascalCase.
private/modules/MyModule/
Step 2: Create the Manifest File
Every module requires a module.json
file in its root directory. This file tells the system about your module.
File: private/modules/MyModule/module.json
{
"name": "My First Module",
"version": "1.0",
"description": "A simple module that adds a new page.",
"hooks": []
}
Step 3: Create the Initialization Script
The init.php
script runs when an administrator activates your module. Its job is to set up everything the module needs, like database tables, menu items, and roles.
File: private/modules/MyModule/init.php
<?php
// init.php
require_once(__DIR__ . '/../config.php');
try {
$pdo->beginTransaction();
$pdo->exec("INSERT OR IGNORE INTO roles (name, is_system) VALUES ('my_module_view', 0)");
$stmt = $pdo->prepare("INSERT INTO menu_items (name, url, parent_id, order_num, is_dropdown, is_visible, required_role) VALUES ('My Module', NULL, NULL, 50, 1, 1, 'my_module_view')");
$stmt->execute();
$parentId = $pdo->lastInsertId();
$stmt = $pdo->prepare("INSERT INTO menu_items (name, url, parent_id, order_num, is_dropdown, is_visible, required_role) VALUES ('My Page', 'my_page.php', ?, 1, 0, 1, 'my_module_view')");
$stmt->execute([$parentId]);
$stmt = $pdo->prepare("INSERT INTO modules (name, path, is_active) VALUES ('My First Module', 'MyModule', 1)");
$stmt->execute();
copy(__DIR__ . '/my_page.php', PUBLIC_PATH . 'my_page.php');
$pdo->commit();
echo "My Module initialized successfully.";
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
die("Error initializing My Module: " . $e->getMessage());
}
?>
Step 4: Create the De-initialization Script
The deinit.php
script is the opposite of `init.php`. It runs when the module is deactivated and must clean up everything the `init.php` script created.
File: private/modules/MyModule/deinit.php
<?php
// deinit.php
require_once(__DIR__ . '/../config.php');
try {
$pdo->beginTransaction();
$pdo->exec("DELETE FROM menu_items WHERE url = 'my_page.php'");
$pdo->exec("DELETE FROM menu_items WHERE name = 'My Module'");
$pdo->exec("DELETE FROM roles WHERE name = 'my_module_view'");
$pdo->exec("DELETE FROM modules WHERE path = 'MyModule'");
if (file_exists(PUBLIC_PATH . 'my_page.php')) {
unlink(PUBLIC_PATH . 'my_page.php');
}
$pdo->commit();
echo "My Module deinitialized successfully.";
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
die("Error deinitializing My Module: " . $e->getMessage());
}
?>
Step 5: Create the Public Page
This is the actual page the user will see. It must include the authentication header and check for the required role.
File: private/modules/MyModule/my_page.php
<?php
require_once('components/auth.php');
if (!checkRole(['my_module_view'])) {
header('Location: index.php');
exit;
}
require_once('components/header.php');
?>
<h1>Hello from My Module!</h1>
<p>This page is part of a custom module.</p>
<?php
require_once('components/footer.php');
?>
Step 6: Package Your Module
To prepare your module for installation, create a ZIP archive of the entire `MyModule` folder and rename the file extension from `.zip` to `.dmod`.
.dmod
file extension.2. Using the Hook System
Hooks are the primary way for modules to interact with the core application without modifying its files. They act as "event listeners" at specific points in the code.
Example: Adding a Button to a Table
Let's add a custom "View Details" button to the actions column of the main Transactions table.
Step 1: Register the Hook in `module.json`
First, you must tell the system that your module wants to listen for the TRANSACTIONS_ACTIONS_COLUMN
hook point.
{
"name": "My First Module",
"hooks": [
{
"hook_point": "TRANSACTIONS_ACTIONS_COLUMN",
"file": "hooks/transaction_actions.php"
}
]
}
Step 2: Create the Hook File
The system will automatically include this file when the hook is triggered. The $context
variable is automatically available, containing data relevant to the hook point.
File: private/modules/MyModule/hooks/transaction_actions.php
<?php
$transaction = $context['transaction'] ?? null;
if (!$transaction) { return; }
// Only show the button for amounts over 100.
if ($transaction['amount'] > 100) {
echo '<a href="my_page.php?id=' . $transaction['id'] . '" class="button">Details</a>';
}
?>
3. Custom Document Types
The Document Editor is extensible. You can create new "Types" of documents (like the built-in Trial Balance or Invoice types) that can be selected in the editor. This allows you to create completely custom, data-driven PDF reports and documents.
Directory Structure
Each custom document type must have its own folder inside public/documents/type/
.
public/documents/type/my_report/
|-- type.json
|-- toolbar.json
|-- property.json
|-- objects.js
|-- fpdf.php
`-- dialog.php
File Breakdown
- type.json: The manifest that tells the editor which files to load for your type.
- toolbar.json: Defines custom buttons for the editor's toolbar.
- property.json: Defines custom properties for the right-hand panel.
- objects.js: JavaScript for creating your custom object, rendering its preview, and handling property updates.
- fpdf.php: The core PHP logic using FPDF commands to draw your object onto the final PDF.
- dialog.php: An optional file to create a parameter pop-up (e.g., for date ranges) before PDF generation.
4. Developing with AI
You can significantly accelerate the development of new modules and document layouts by using a Large Language Model (LLM) like Google AI Studio as a development partner. By providing the AI with the complete source code of the application, you give it the full context it needs to generate new, compatible code that follows the existing patterns and architecture.
How to Use
- Download the Source File: A special, concatenated text file containing the entire application's source code is available for developers.
Download the Complete Source Text File. - Go to an LLM Interface: Navigate to a powerful LLM that accepts file uploads, such as Google AI Studio.
- Upload the File: Upload the `output.txt` file you downloaded.
- Provide a Clear Prompt: Write a detailed prompt explaining exactly what you want the AI to create. The more specific you are, the better the result will be.