Extensions
Capabilities
Latch extensions are capable of providing any or all of the following behaviors:
- Loading stylesheets. Latch autoloads
css/style.cssfor each active extension when the file exists. - Overriding core scripts and templates. Most files are loaded through
Latch\File\ext_part(), which checks active extensions before core files. Files inappcontain CMS initialization logic and are not extension override targets. - Event handlers. Extensions can run PHP code for any Latch event and can define custom event names for their own code.
- Custom admin forms that write data to the
latch_extensionstable. - Custom shortcodes from
extensions/my_extension/lib/shortcodes. - Custom post templates, page templates, form templates, datatype handlers, cachers, and other files resolved through
ext_part().
Metadata
Every extension must have an extension.json file in the root of the extension directory, for example extensions/my_extension/extension.json.
Here is an example with the common supported keys:
{
"name": "Backups",
"description": "Schedule backups of your Latch files and database.",
"author": "Sparknight",
"url": "https://sparknight.io",
"version": "0.0.1",
"icon": "latch-icon ph-hard-drive",
"show_in_sidebar": true
}
A few points to keep in mind:
nameis the friendly value shown in the dashboard. The extension directory is the unique identifier used by the filesystem.iconis optional visual metadata and does not control whether the extension appears in the Admin Dashboard sidebar.- Set
show_in_sidebartotrueto add an active extension to theExtensionssidebar submenu. The link is rendered without an icon. versionshould follow Semantic Versioning.
Database Persistence
The latch_extensions table is the registry and persistence boundary for installed extensions. Each extension directory has one row, identified by its unique name; Latch stores its friendly name, enabled state, load priority, and extension-owned JSON in the metadata column.
An extension settings form with database-action set to extension writes its submitted values into that metadata object. Extension code can use the same object for internal state by reserving keys that begin with an underscore, such as _queue; ordinary form saves preserve those internal keys.
Do not create or alter application tables as part of an extension installation routine. If the metadata object cannot reasonably support an extension's data model or workload, document the limitation and coordinate the database design with the siteowner before introducing schema changes.
Importing
Administrators can import an extension from the Extensions page of the Admin Dashboard. The ZIP may contain the extension files directly or wrap them in one top-level directory. In either layout, extension.json must be at the extension root.
Latch validates archive paths, symbolic links, file counts, extracted size, required name and description metadata, Semantic Versioning, URLs, and directory/name collisions before moving the extension into place. An imported extension remains disabled until an administrator explicitly enables and saves it.
Extensions can execute server-side code and should only be imported from trusted sources. Validation confirms that a package is structurally safe to extract; it does not establish that the extension's code is trustworthy.
Priority
Extensions are loaded in the order they appear in the active extension list.
For file overrides, the first active extension with a matching file takes precedence. Other matches further down the extension list are ignored.
For events, multiple extensions can hook into the same event. The earlier extension in the active extension list runs first.
Managed Apache Configuration
Extensions that need root .htaccess directives should use Latch\Util\upsert_htaccess_block() and Latch\Util\remove_htaccess_block() with a stable, extension-specific block name. The helpers preserve line endings, serialize read-modify-write operations with a file lock, replace existing named blocks idempotently, and leave unrelated directives untouched.
Listen for extensions_update when configuration should be removed on disable. The event runs through the previous registry before extension handlers are unloaded.
Events
Event files live at:
extensions/my_extension/lib/events/event_name.php
The event class must use this namespace and class pattern:
<?php
namespace Latch\Ext\MyExtension\Events;
class EventName
{
public static function event($args)
{
// Your processing here.
}
}
For example, to add content to the bottom of a Latch form, create extensions/my_extension/lib/events/form_after.php:
<?php
namespace Latch\Ext\MyExtension\Events;
class FormAfter
{
public static function event($args)
{
return <<<HTML
<button type="button">My Custom Button</button>
HTML;
}
}
The $args variable is a named array provided by $_SESSION["latch"]->events->trigger().
Initialization Event
The init event is triggered immediately after extension event files are loaded. It is a good place to include extension libraries, run installation routines, or initialize namespaced services.
Example init.php:
<?php
namespace Latch\Ext\MyExtension\Events;
class Init
{
public static function event($args)
{
include_once __DIR__ . "/../my-extension.php";
}
}
If your extension exposes reusable code, put it in a namespaced file outside the event handler, such as extensions/my_extension/lib/my-extension.php:
<?php
namespace Latch\Ext\MyExtension;
function get_log()
{
return \Latch\Util\new_log("ext.my_extension");
}
Cron Event
A cron job is a task that runs automatically at a specific time or interval. Latch relies on a single cron job that fires every minute. It calls lib/hooks/cron.php, which triggers the cron event.
An extension can add scheduled behavior by creating:
extensions/my_extension/lib/events/cron.php
The handler should decide for itself whether work is due. For example, the Backups extension checks configured backup jobs and only runs a job when its schedule has elapsed.
