Skip to main content
Where a TableAction targets a row, a Lunar\Panel\Actions\PageAction targets a page’s header — “Import” above a listing, “Audit log” on a record page.

Registering a page action

Register PageAction classes from a Section’s pageActions() hook, keyed by page id — the same route-name-derived ids a slot zone uses:
Outside a Section, call Panel::addPageAction($pageId, ActionClass::class) directly.

Listing pages vs. record pages

Lunar\Panel\Http\Middleware\HandlePanelInertiaRequests resolves the current page’s actions automatically on every request, with no controller-side wiring: it derives the page id from the route name, finds the first Eloquent model bound to the current route (if any), and passes it as $context to each action:
A listing page (customers.index) has no bound model, so its actions resolve with $context === null. A record page (customers.edit) passes the route-bound record — the customer — as $context. The same PageAction class shape covers both cases; only what url() (and visible(), if overridden) does with $context differs.

The PageAction contract

Every content page has a header home for it

A PageAction needs no cooperation from the page it targets. Every content page renders its header through the shared <PageHeader> (or <SettingsShell> for settings pages), and both always carry the page-action ellipsis — enforced by tests/panel/Unit/PageScaffoldTest.php for every content page in the panel. Registering a page action is therefore enough on its own; there’s no equivalent of a slot’s zone-name mismatch to get wrong.

Worked examples

ImportPageAction — a listing-page action with a static URL, ignoring $context:
AuditPageAction — a record-page action, building its URL from $context:
Both are registered together in Registering a page action above, from lunarphp/panel-addon-example’s src/ExampleSection.php.

See also