Developer Guide
Learnomy overviewFree + ProGet Learnomy →
Extending Learnomy from a theme or a plugin of your own.
Pages in this section
Section titled “Pages in this section”| Page | Description |
|---|---|
| WP-CLI Commands | The wp learnomy command group: enrolling, inspecting a learner, repairing data after an import, migrating, and exports. |
| Template Overrides | Copying a Learnomy template into your theme at theme/learnomy/*.php so you can change the markup without touching the plugin. |
| Hooks Reference | Every action and filter Learnomy fires, with its arguments and where it is fired. Generated from the code. |
| Hooks Reference (Pro) | The same for Learnomy Pro’s extensions. |
| Store bridges for developers | Pro. Connect another store or membership plugin to Learnomy access: the bridge base class, the shared access picker and its two routes. |
| Background Jobs | The scheduled and queued jobs Learnomy and Learnomy Pro run, how often, and how to check them. |
| Payment Gateways and Purchase Sources | The purchase flow, a sample gateway adapter, adding a product type, adding an inbound purchase source, and the conformance gate. |
| Registration and Login Security | What protects the sign-up and sign-in routes, what does not, and how to add your own challenge. |
Where the rest of the developer material lives
Section titled “Where the rest of the developer material lives”Learnomy’s hooks and filters are explained on the page for the feature they belong to; each of these pages ends with a For developers section. The Hooks Reference lists all of them in one place and is regenerated from the code, so it cannot drift from it:
| Looking for | Page |
|---|---|
| URL slugs, the route registry, adding pages to a nav menu | Pages and URL Slugs |
| The private-academy gate and the routes it covers | General Settings |
| Per-course search snippet fields | Course SEO and Sharing |
| Canonical, Open Graph, and who owns the head tags | SEO Plugin Handoff |
| Repointing instructor links at another profile | Instructor Public Profile |
| REST endpoints, authentication, the mobile app contract | REST API and App |
| Registering LMS actions for AI assistants | AI Agents and the Abilities API |
| Outbound webhooks and their event list | Outgoing Webhooks |
| Giving access for a sale made in an external system | Incoming Access Webhook |
Working with stored dates
Section titled “Working with stored dates”Learnomy’s tables declare their timestamp columns datetime NOT NULL DEFAULT '0000-00-00 00:00:00', which is what MySQL stores when a write leaves the
column out. That value is awkward to test for: it is not empty, and
strtotime() returns a large negative number, which is truthy - so the
obvious guard formats it as “November 30, -0001” and a comparison reads it as
long past.
If you render a stored date in a template or an extension, use the helper rather than writing your own guard:
echo esc_html( \Learnomy\date_label( $row->created_at ) ); // "August 3, 2026" or "—"echo esc_html( \Learnomy\date_label( $row->expires_at, 'Never' ) ); // custom fallbackecho esc_html( \Learnomy\date_label( $row->issued_at, '—', 'Y-m-d' ) );It returns the fallback for all four ways a date can be missing - NULL, an empty
string, the zero-date sentinel, and anything that does not parse - so you cannot
handle three of them and meet the fourth in production. \Learnomy\is_zero_date( $value ) is the bare predicate when you need to branch rather than render.
Writes are handled for you: Model::insert() and Model::update() fill in
created_at and updated_at when you omit them, so new rows never carry the
sentinel. A value you pass explicitly is always kept - an importer backdating
records keeps its own dates - and created_at is only ever set on insert, so
an update cannot rewrite a row’s history.

