FacturaScripts 2025+ plugin architecture. Controller patterns, Extension closures, XMLView/Table XML, Model conventions, Init.php lifecycle, Settings API, AssetManager, Dinamic namespace.
class Init extends InitClass {
public function init(): void // Called on every request — load extensions here
public function update(): void // Called on install/update — initialize settings, migrations
public function uninstall(): void // Called on plugin removal — cleanup
}
init(): Register extensions via $this->loadExtension(new Extension\Controller\Foo())update(): Set default settings, run one-time migrationsinit(), NOT update()class AiScanConfig extends PanelController {
protected function createViews(): void {
$this->addHtmlView('config', 'AiScanConfig', 'Settings', 'config');
}
public function getPageData(): array {
return ['menu' => 'admin', 'title' => 'ai-scan-config', 'icon' => 'fas fa-brain'];
}
}
AiScanInvoice extends Controller directly for REST-style JSON endpoints:
publicCore(&$response) or handle in privateCore()$this->response->setContent(json_encode($data))$this->permissions->allowUpdateExtensions modify existing FacturaScripts controllers without editing core files:
// Extension/Controller/EditFacturaProveedor.php
class EditFacturaProveedor
{
// Method name = hook point in the target controller
public function createViews(): Closure
{
return function () {
// $this = the target controller instance
AssetManager::addCss(FS_ROUTE . '/Plugins/AiScan/Assets/CSS/aiscan.css');
AssetManager::addJs(FS_ROUTE . '/Plugins/AiScan/Assets/JS/aiscan.js');
};
}
}
Rules:
$this as the target controller (not the extension)FS_ROUTE prefix for asset pathsExtension/Controller/ mirroring the target controller name// Read setting
$value = Tools::settings('AiScan', 'key_name', 'default_value');
// Write setting (in update() or controller action)
$settings = new Settings();
$settings->name = 'AiScan';
$settings->properties = ['key' => 'value', ...];
$settings->save();
'AiScan'AiScanSettings helper in Lib/AiScanSettings.php wraps Tools::settings() with typed defaultsAssetManager::addCss(FS_ROUTE . '/Plugins/AiScan/Assets/CSS/aiscan.css');
AssetManager::addJs(FS_ROUTE . '/Plugins/AiScan/Assets/JS/aiscan-flow.js');
AssetManager::addJs(FS_ROUTE . '/Plugins/AiScan/Assets/JS/aiscan.js');
FS_ROUTE prefix — handles subfolder installationsaiscan-flow.js before aiscan.js (dependency order)class AiScanLog extends ModelClass {
public function primaryColumn(): string { return 'id'; }
public static function tableName(): string { return 'aiscan_logs'; }
public function primaryDescriptionColumn(): string { return 'filename'; }
}
ModelClass for CRUD operationsTable/aiscan_logs.xmlModelClass methods: loadFromCode(), save(), delete(), all(), count()use FacturaScripts\Dinamic\Model\Proveedor;
use FacturaScripts\Dinamic\Model\FacturaProveedor;
use FacturaScripts\Dinamic\Model\LineaFacturaProveedor;
Dinamic namespace for FS core models — enables other plugins to extend themFacturaScripts\Core\Model\... directlyXMLView/AiScanConfig.xml defines the settings panel UI:
<view>
<columns>
<group name="general" title="general">
<column name="enabled" numcolumns="4">
<widget type="checkbox" fieldname="enabled" />
</column>
</group>
</columns>
</view>
Table/aiscan_logs.xml defines database schema:
<table>
<column><name>id</name><type>serial</type></column>
<column><name>filename</name><type>character varying(255)</type></column>
...
<constraint><name>aiscan_logs_pkey</name><type>PRIMARY KEY</type><columns>id</columns></constraint>
</table>
Translation/es_ES.json and Translation/en_EN.jsonTools::lang()->trans('key') in PHP or i18n.trans('key') in Twigai-scan-config, scan-invoiceInit.php — Plugin entry pointController/ — Main controllersExtension/Controller/ — Controller extensionsModel/AiScanLog.php — Audit log modelXMLView/AiScanConfig.xml — Settings UITable/aiscan_logs.xml — Database schemaLib/AiScanSettings.php — Settings helper