Purpose
The data shape a plugin uses to add a link to the editor's
chrome. Whether the link lands in the left-rail sidebar, the
top-right profile cluster, or a plugin-introduced slot of its
own is decided by displayType.
Final readonly DTO. You construct one per entry inside
Plugin::register() and hand it to
PluginContext::addEditorMenuItem(). The editor's templates
walk the registry, group entries by displayType, sort by
position, render the markup.
FQCN + file path
- FQCN:
Scriptor\Boot\Editor\Menu\MenuItem - Source:
boot/Editor/Menu/MenuItem.php
When to use
You construct MenuItem instances inside Plugin::register()
and pass them to PluginContext::addEditorMenuItem(). You do
not receive MenuItem from any other API surface; the registry
is the consumer.
A MenuItem is independent of the corresponding Module. You
typically register both side by side (so the link does something
when clicked), but the framework does not enforce the pairing.
Surface
Constructor
public function __construct(
public string $slug,
public string $label,
public string $icon = '',
public string $displayType = 'sidebar',
public int $position = 0,
public ?string $href = null,
)
Six public readonly fields:
| Field | Type | Purpose |
|---|---|---|
slug |
string |
URL slug. When href is null, the layout builds $siteUrl . '/' . $slug . '/'; also the convention key for matching against the current Editor::$urlSegments to mark the item active |
label |
string |
Displayed text. Plain text; the renderer escapes it |
icon |
string |
Icon class name (theme-defined; the bundled editor uses Material-Icons-style strings) |
displayType |
string |
Layout slot: 'sidebar' (left rail), 'profile' (top-right cluster), or a plugin-introduced slot. Default 'sidebar' |
position |
int |
Sort key inside its slot. Lower wins. Ties broken by registration order. Default 0 |
href |
?string |
Optional literal URL. When null, derived from slug. Pass a literal when you need a suffix (e.g. logout with ?tokenName=…&tokenValue=…) |
No methods.
Lifecycle
Constructed by your plugin, handed to
PluginContext::addEditorMenuItem($item). The context records
it on its own registration snapshot and forwards to
MenuRegistry::add(). The editor's template fragments walk the
registry per request to render the chrome.
final readonly, so once constructed the entry is frozen.
Re-registering an item with the same slug does not replace; the
registry just appends. Plugins should ensure unique slugs.
Common patterns
Sidebar link for the plugin's own module
$context->addEditorMenuItem(new \Scriptor\Boot\Editor\Menu\MenuItem(
slug: 'blog',
label: 'Blog',
icon: 'article',
displayType: 'sidebar',
position: 50,
));
Renders in the sidebar. Clicks land at /editor/blog/,
matching the 'blog' slug a sibling
registerEditorModule('blog', …) claims.
Profile-cluster entry with a literal href
$context->addEditorMenuItem(new MenuItem(
slug: 'docs',
label: 'Docs',
icon: 'help_outline',
displayType: 'profile',
position: 10,
href: 'https://scriptor-cms.dev/developer-guide/',
));
Links to an external URL. The slug field is still required
(it is used for the DOM id and the registry key) but not for
URL building, since href overrides.
Introducing your own slot
$context->addEditorMenuItem(new MenuItem(
slug: 'gallery-quick',
label: 'New gallery',
icon: 'add_photo_alternate',
displayType: 'page-actions', // plugin-introduced slot
position: 10,
));
By itself, this entry never renders: no template reads
'page-actions'. The plugin pairs it with a small template
fragment its own module includes, which iterates
MenuRegistry::displayItems('page-actions') and emits the
markup. The framework is content-agnostic; it just stores
entries by displayType and hands them back on demand.
See also
Module: the corresponding URL handler the menu entry'sslugtypically points atEditor: holds the request and is what the module writes throughPluginContext: theaddEditorMenuItem()entry point- Concept: Editor extensions: module + menu + container walkthrough