Plugins
A plugin is newmeta's core unit of extension. Every admin module, shop feature, Pagebuilder widget, and API endpoint lives in a plugin. The CMS ships with 42 plugins — the same plugin API is available for your own modules.
What is a plugin?
Every plugin is a subdirectory under _public/extensions/core/backend/{plugin-name}/ and consists of:
- a
bootstrap.phpthat registers the plugin, - optional admin layouts (
layout/index.vue,layout/index_detail.vue), - optional PHP action scripts (
script/), - optional API models (
api/), - optional Pagebuilder widgets (
widgets/), - optional SQL migrations (
migrations/).
Each plugin decides for itself which of these parts it needs. Small plugins only ship a bootstrap.php plus a table; large plugins like shop or elearning come with full API directories, their own widgets, and dozens of migrations.
Why build a plugin?
| Use case | Plugin pattern |
|---|---|
| CRUD for a new data model | content_construct=table + content_table=my_table → automatic backend with list + edit modal + API |
| Custom UI without a DB table | content_construct=template + your own layout/index.vue |
| New REST endpoint | $this->apiEndpoints in bootstrap.php, model in api/ |
| Integration with an external service | Pagebuilder widget + webhook event + scheduled task |
| Custom Pagebuilder element | Widget subdirectory under widgets/ (see widget docs) |
Plugins are the only right place to add functionality — you never edit the core framework directly.
Plugin lifecycle
A plugin goes through three phases. install() itself only sets properties — the actual DB inserts happen in the core installers and don't need to be idempotent (a re-install typically removes the previous one via uninstall() first). uninstall(), in turn, must clean up properly so re-installs don't run into stale state.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ install() │ --> │ update() │ --> │ uninstall() │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
▼ ▼ ▼
Create Schema migration Remove
plugin_backend via MigrationRunner plugin_backend
entry entriesinstall() — runs once on first activation. Sets all properties (content_construct, content_table, apiEndpoints, scheduledTasks, webhookEvents, …). The core then automatically calls these installers:
installBackend() → plugin_backend entries (one per content_construct)
installWidgets() → Pagebuilder widgets in pagebuilder_widgets + page_widgets
installDatabase() → Run plugin-local migrations
installInjections() → Register script injections
installSources() → CSS/JS assets for the frontend bundle
installAPI() → apiEndpoints in the pluginAPI table
installScheduledTasks() → scheduled_tasks entries
installWebhookEvents() → webhook_events entries (manual + auto from content_table)update() — runs on a system update. Typically empty — schema changes go through migrations, not through update(). Only needed when existing plugin_backend entries must be rewritten.
uninstall() — runs on deactivation. Removes entries from plugin_backend, pluginAPI, scheduled_tasks, and webhook_events. Tables are not dropped — data is retained in case the plugin is reactivated later.
Required methods
Every plugin class must implement install(), uninstall(), update(), getVersion(), and getName(). Details: Plugin Anatomy.
The plugin_backend table
The heart of backend integration. For every content_construct of a plugin, one entry is created:
CREATE TABLE plugin_backend (
id INT AUTO_INCREMENT PRIMARY KEY,
plugin_id INT NOT NULL, -- FK → plugins.id
content_construct ENUM('table','template','formonly'),
content_table VARCHAR(255), -- DB table when construct=table
content_titles VARCHAR(255), -- UI label ("Articles", "Courses", …)
content_columns TEXT, -- Visible columns (JSON/CSV)
content_button TEXT, -- Row buttons (JSON, urlencoded)
action_button TEXT, -- Header action button (JSON)
content_replace TEXT, -- Column renames
header_buttons TEXT, -- Additional header buttons
url_rewrite VARCHAR(255), -- URL slug → /admin/{url_rewrite}
modal_edits TEXT -- Modal field config
);content_construct drives the entire UI logic of a plugin. The three variants (table, template, formonly) are explained in Content Constructs.
Other important tables
| Table | Contents |
|---|---|
plugins | Plugin registry (name, version, root_folder, type, location) |
plugin_backend | Admin UI entry points per plugin |
pluginAPI | Registered REST endpoints (endpoint, modelPath, plugin_id) |
scheduled_tasks | Cron jobs per plugin |
webhook_events | Registered webhook events per plugin |
pagebuilder_widgets | Pagebuilder widget registry (one row per widget type) |
page_widgets | Widget instances per page (one row per placed widget) |
_migrations | Executed SQL migrations (scope + migration + checksum) |
Install & update
You activate plugins via the Plugin Manager under /admin/plugin-manager, or through the core installer during a system update. Both paths call installPlugin() on the install_controller and run the steps listed above.
Just drop new plugins into _public/extensions/core/backend/, with migrations either in the plugin's own subdirectory or centrally under _migrations/plugins/{name}/. On the next admin login, the Plugin Manager sees the plugin and offers installation.
Details on the update process: Plugins › Migrations.
See also
- Plugin Anatomy — directory structure, required methods, your first class
- Content Constructs —
tablevs.templatevs.formonly - Buttons — button types in the admin
- Migrations — writing and rolling out SQL migrations
- API Endpoints — registering REST endpoints per plugin
- Webhook Events — emitting events, webhook subscriptions
- Scheduled Tasks — cron jobs from within a plugin
- Example: Hello World — minimal plugin from scratch
- Widget docs — Pagebuilder widgets inside plugins