Build Birkly plugins as self-contained packages with plugin.json metadata, a PHP entry point, and declared permissions. The marketplace and CMS use these declarations for preflight checks and install consent.
A Birkly plugin lives in plugins/{folder}/ with at minimum plugin.json and plugin.php. Declare permissions, hooks, and data_access in plugin.json so site owners see an accurate consent screen at install. Register admin pages, API routes, field types, and automation actions from plugin.php. Package as a zip for marketplace upload or manual install.
Beginner
Minimum package
my-plugin/
plugin.json ← name, version, description, permissions
plugin.php ← registers hooks, routes, services
admin/ ← optional admin UI pages
api/ ← optional API endpoints
plugin.json essentials
| Field | Purpose |
|---|---|
name | Display name in admin |
version | Semver (for example 1.0.0) |
description | Short summary |
requires_birkly | Minimum CMS version |
requires_php | Minimum PHP version |
permissions | Admin capabilities the plugin needs |
hooks | Event names the plugin listens to |
data_access | Read/write scope for entries, media, users, settings |
Permissions and data access
Site owners see your declarations before install. Request only what you need:
- permissions — strings like
read_entries,manage_plugins,manage_settings. - hooks — event names like
entry.saved,user.login. - data_access — per domain (
entries,media,users,settings), set level tonone,read, orread_write.
Example:
{
"name": "My Integration",
"version": "1.0.0",
"description": "Syncs entries to an external CRM",
"requires_birkly": ">=0.1.0",
"requires_php": ">=8.1",
"permissions": ["read_entries", "manage_settings"],
"hooks": ["entry.saved"],
"data_access": {
"entries": "read",
"media": "none",
"users": "none",
"settings": "read"
}
}
What plugins can add
- New admin pages and sidebar items
- API endpoints under
/api/{plugin_handle}/... - Field types in the collection editor
- Automation triggers and actions
- Template functions for the storefront
Local development
Place your folder in plugins/ and enable it in Settings → Plugins. Use scaffold-plugin.php to generate a starter package — see Plugin scaffolding CLI.
Publishing
Zip the plugin folder (no path traversal, no parent .. segments) and upload as a marketplace release. Preflight validates plugin.json against the package schema.
Advanced Users
Entry point (plugin.php)
Register services on load. Typical patterns:
- Navigation via
plugin.jsonnavigationblock or programmatic sidebar registration. Setnavigation.iconto a registry name (for example"gear") or inline SVG — see Plugin navigation icon. - Routes under
api/mapped to/api/{handle}/.... - Hook callbacks registered against names declared in
plugin.json. - Visitor website tools (WebMCP) via
birkly_webmcp_register_tools()— a catalog of public-site actions for Discovery → WebMCP. This is not inbound Birkly MCP (register_plugin_mcp_tools()), which is for AI apps connected to the CMS admin. See Website tools (WebMCP). - Admin presentation shelf (P83) —
admin.scripts[], dashboard mounts, notification formatters, WebMCP runtime packs, and hook subscriptions. See Admin plugin extension.
Capability enforcement
Runtime checks use has_plugin_capability($plugin_id, $capability, $scope). The plugin security denylist in Settings → Security can hard-block collections or admin spaces regardless of granted capabilities — see Plugin security denylist.
Permissions manifest at install
The CMS resolves permissions_manifest from the marketplace release metadata or by previewing the zip's plugin.json. Consent is stored in settings/marketplace_consent.json keyed by {slug}/{version}. New versions with changed declarations require re-consent.
Dependencies
Declare soft or hard dependencies in plugin.json (requires_plugins). The CMS blocks activation when dependencies are missing — for example Events soft-depends on Commerce for paid tickets.
Package validation
Marketplace preflight checks:
- Valid semver and required fields
- Zip structure (single root folder or flat layout per schema)
- No dangerous PHP patterns (
eval,shell_exec, etc.) - Capability declarations present for plugins that access data
Developer tooling
| Tool | Purpose |
|---|---|
scaffold-plugin.php | Generate plugin skeleton |
plugin-package.schema.json | Validate local plugin.json |
| Birkly test suite | Denylist and capability tests |
Styling your admin pages
Plugin admin screens should look native. The recommended path ships no CSS at all: wrap the screen in .plugin-page, emit breadcrumbs with {plugin_page_header}, and compose the rest from core classes (.card, .btn, .field, .table, .badge, .callout, .tabs, .modal, .empty-state) plus the u-* helpers. That inherits dark mode, focus rings, contrast-checked colours and reduced-motion handling for free.
If you do ship a stylesheet, load it from the top of your admin template:
<link rel="stylesheet" href="{birkly_url '/admin/css/plugin-page.css'}">
<link rel="stylesheet" href="{birkly_url '/plugins/{handle}/admin/{handle}.css'}">
and follow four rules: never restyle a core class, use var(--…) tokens rather than hex (hardcoded hex is broken in dark mode), no !important against core, and namespace every selector as .{handle}-*.
Full guidance — the three adoption tiers, the CI-verified public class contract, the legacy token alias table, a complete copy-pasteable Tier 0 page, and the .fieldtype-* API — is in Plugin admin styling.