Raven CMS Extensions
This document explains how the Raven extension system works for both human developers and AI agents.
Authoritative extension contract: private/ext/AGENTS.md.
1) What An Extension Is
An extension is a self-contained feature package under:
private/ext/{extension_slug}/
At minimum, each extension needs:
ext.json(required manifest)- optional
ext.php(service provider) - optional
schema.php(schema provider) - optional
routes_panel.php(panel route registrar) - optional
routes_public.php(public route registrar) - optional
shortcodes.php(page editor shortcode provider) - optional
lib/(autoloaded extension PHP classes under theRaven\Ext\...namespace) - optional
tpl/(extension-owned panel templates; panel-routable types only) - optional extension-local state files when needed by your extension
2) How Extensions Are Loaded
Core runtime bootstrap (private/Raven.php + extension runtime services) does this:
- Reads extension enablement state from
private/dat/ext/.state.php. - Validates extension directory names and manifests.
- Builds nav items from extension directory slug and manifest type/name.
- Loads optional extension providers (
ext.php,schema.php, route registrars) for enabled, valid extensions.
schema.php runs when the extension requests storage in ext.php.
- Injects a context object (
app,panelUrl,requirePanelLogin, etc.) for route registration.
The panel rebuilds its extension navigation after authentication-helper requests
clear the pre-authentication nav snapshot. This keeps sidebar visibility in sync
with route access when a session moves from login or 2FA into the authenticated panel.
Provider files are loaded only from the extension root. Raven no longer falls back to legacy lib/*.php provider locations.
Enabled extension classes autoload only from private/ext/{slug}/lib/. Raven no longer scans a legacy src/ class root.
3) Enablement And Permission Model
Shared state file:
private/dat/ext/.state.php
State keys:
enabled: map of{extension_slug => true}permissions: map of{extension_slug => panel_permission_bit}for non-system extensions
Types:
helper,content,framework: appear in the Extensions nav (when authorized)module: appears in the Modules nav (when authorized)system: appears under System nav and requires system configuration access
4) Data Boundaries (Important)
The extension system is isolated by code location, but data may be split between extension-local assets and shared core storage.
File-backed extension data examples:
- Contact form definitions in
private/dat/ext/contact/forms.php - Signup form definitions in
private/dat/ext/signups/forms.php
Shared/core-managed data examples:
- Enablement and permission masks in
private/dat/ext/.state.php - Contact submissions in DB table
rvn_contactviaContactSubmissionRepository - Signup submissions in DB table
rvn_signupsviaSignupSubmissionRepository
So the correct model is:
- Extension configuration can be local to the extension folder.
- Extension-local persistent files may live under
private/dat/ext/{slug}/when the extension requestsstorage.localinext.php. - Runtime/system state and persistent records can still live in shared core state/DB.
5) Public Runtime Reality (Current)
Panel extension routing is generic (routes_panel.php contract).
Public extension runtime is currently not generic:
- Core public request controllers explicitly integrate supported extension behaviors.
Public\SharedControllerowns extension-template rendering through the site theme pipeline, andPublic\PageControllerowns the built-in shortcode/content runtime integration points.- Current built-in integration points are Contact Forms and Signup Sheets shortcodes.
Page Editor shortcode insertion is generic for enabled extensions:
- Extensions may optionally provide
private/ext/{slug}/shortcodes.php. - That provider can return shortcode items (
label+ literalshortcode) for the editor'sExtensionsbutton dropdown. - When the provider is callable, Raven passes a small context array including:
extension=> current extension directory slugforms=> optional enabled-form loader for stock form-style extensionsconfig=> shared Raven config object
If a feature needs generic public routing/hooks, treat it as a core platform change request.
6) Security Requirements
Every extension route must:
- enforce login/access using
requirePanelLogin - enforce CSRF for state-changing requests
- sanitize inputs via
InputSanitizer - avoid unsafe filesystem path handling (prevent traversal)
- keep frontend assets local (no CDN/telemetry/phone-home behavior)
Also:
- Do not modify core files to ship extension-only behavior.
- Keep extension logic inside
private/ext/{slug}/.
7) Developer Workflow
- Create
private/ext/{slug}/. - Add
ext.jsonfirst. - Add
ext.phpand root-levelschema.phpfor service/storage behavior. - Add root-level
routes_panel.php+tpl/only when panel pages are needed. - Add root-level
routes_public.phponly formoduleextensions that need public endpoints. - Add
shortcodes.phponly when editor shortcode insertion is needed.
It may accept a context array so shortcode options can react to config without manually bootstrapping ext.php.
- Add extension-local state files only when necessary.
- Enable extension in Extension Manager.
- Verify permission masks, nav placement, CSRF-protected actions, and failure behavior.
Autoloaded extension classes belong under private/ext/{slug}/lib/ only.
Alternative bootstrap path:
- Use Extension Manager -> Create New Extension to generate a starter scaffold.
helper:ext.json,ext.php,schema.php,routes_panel.php,tpl/panel_index.phpcontent:ext.json,ext.php,schema.php,shortcodes.php,fields.php,routes_panel.php,tpl/panel_index.phpframework:ext.json,ext.php,schema.phpmodule:ext.json,ext.php,schema.php,shortcodes.php,fields.php,routes_panel.php,routes_public.php,tpl/panel_index.php,tpl/public_index.phpsystem:ext.json,ext.php,schema.php,routes_panel.php,tpl/panel_index.php- Optional in that same modal:
Generate Agent Guidance?to createprivate/ext/{slug}/agents, plusAGENTS.mdandCLAUDE.mdsymlinks for tool compatibility.
Extension Manager Panel Options
The Extension Manager (/extensions) includes three practical control areas.
Upload modal (Upload Extension):
Extension Archivefile input accepts.zip,.7z,.tar,.tar.gz/.tgz,.tar.bz2/.tbz2,.tar.xz/.txz, and.tar.zst/.tzstSlug Override (optional)input- If slug override is blank, upload slug is read from
ext.jsonslug. - If that derived slug already exists, upload auto-renames using
-copy.
Create modal (Create New Extension):
Extension NameDirectory SlugDirectory Slugis the single route/nav slug source for non-helper extensionsTypeVersionAuthorHomepage URLDescriptionGenerate Agent Guidance?- Footer actions:
Cancel,Create Extension
Installed list actions:
- Table columns:
Name,Type,Author,Description,Actions - Per extension:
Read Documentation(links to the extension'sdocsURL fromext.jsonwhen present),Settings(when extension is enabled and has a panel route),Enable/Disable,Exportas a button-opened archive-format dropdown menu, andUninstall(when allowed).
8) Manifest Basics
Common manifest fields:
slug(required, URL-safe extension slug)name(required)versiondescriptiontype(helper,content,framework,module, orsystem)authorhomepagesystem_extension(optional behavior flag)
Notes:
panel_pathandpanel_sectionare legacy manifest keys and are ignored.- Panel route/nav identity comes from the extension directory slug.
- Protected stock extensions are
backup,contact,cron,database,phpinfo,repo, andsignups. ext.phpmay request storage with an array contract:
local, table, tables, aux, panel, public.
localprovisionsprivate/dat/ext/{slug}/.tableandtablesallowschema.phpto manage{prefix}ext_{slug}/{prefix}ext_{slug}_*.auxprovisions one or more sanctioned root-level folders such as/{name}.panelprovisionspanel/ext/{slug}/;publicprovisionspublic/uploads/ext/{slug}/(moduleonly).- Disabling an extension leaves storage intact; uninstalling a non-stock extension removes the storage it explicitly opted into and removes the package files, while stock extension uninstall only purges the opted-in storage and keeps the bundled extension files.
The stock backup extension provides /panel/backup plus a Backup & Restore action on the
page-list heading card. Its JSON archive includes page rows, categories, tags, channels, taxonomy
sets, redirects, and page-to-taxonomy relationships with their numeric IDs preserved. Media rows
and media files are intentionally excluded. Restore is for a new Raven system only; it does not
merge or remap an existing installation.
9) Agent Guidance
For AI agents and maintainers, use:
- private/ext/AGENTS.md as the authoritative extension authoring contract
If this document and private/ext/AGENTS.md ever diverge, treat private/ext/AGENTS.md as source of truth and update this file.
UI Labels Reference
Select extension type...contentframeworkhelpermodulesystemAuthor NameAuthor URLDocumentationDocumentation URLGenerate composer.json?Click to UploadFormats Accepted: zipExtension Archive (zip)Slug Override (optional)