Skip to content

Panel configuration ​

A Panel is Arrel's embedding unit: it groups the Resources, pages and widgets you want to expose under a URL, with its own branding. An application can register more than one (for example, an admin panel and a branded-demo panel with different colors for a specific customer).

Registering a Panel ​

It is registered against the PanelRegistry singleton that Arrel's ServiceProvider already binds, normally from the boot() of a ServiceProvider of your own application:

php
use Arrel\Panels\Panel;
use Arrel\Panels\PanelRegistry;

public function boot(PanelRegistry $panels): void
{
    $panels->register(
        Panel::make('admin')
            ->resources([
                PostResource::class,
            ])
            ->pages([
                SiteSettingsPage::class,
            ])
            ->customPages([
                CustomPage::make('about', 'About'),
            ])
            ->widgets([
                Widget::make('total_posts')
                    ->label('Total Posts')
                    ->value(fn (): int => Post::count()),
            ])
    );
}

Panel::make($id) takes the identifier that will appear in the URL. The resources(), pages(), widgets() and customPages() methods are all optional and can be freely combined. Requesting a panel that is not registered answers 404.

Prefix and panels ​

By default, the admin panel lives at /arrel/admin. You can change it in config/arrel.php:

php
return [
    'prefix' => 'arrel',
    'panels' => [],
    'middleware' => [],
    'authenticated_middleware' => [],
    'extra_scripts' => [],
];
KeyDescription
prefixFirst segment of the SPA URL: /{prefix}/{panel}. It can be any string, or empty.
panelsList of panel identifiers. Only needed when prefix is empty.
middlewareMiddleware that runs before authentication. See Middleware.
authenticated_middlewareMiddleware that runs after authentication.
extra_scriptsURLs of scripts loaded in all panels. See Scripts and stylesheets.

A panel at the root of the domain needs an empty prefix and the list of its identifiers, so that Arrel only answers to those and does not swallow the rest of your application's routes:

php
'prefix' => '',
'panels' => ['admin'],

With that, the admin panel lives at /admin. The API stays under /arrel/api/{panel}/..., whatever the prefix.

To build the URL of a panel page from your own code, use Arrel\Support\PanelUrl:

php
use Arrel\Support\PanelUrl;

PanelUrl::to('admin', 'posts'); // "/arrel/admin/posts"

Your own branding ​

php
Panel::make('branded-demo')
    ->brandName('Branded Demo')
    ->logo('/images/branded-demo-logo.svg')
    ->colors(['brand' => '#2E7D32', 'brand-strong' => '#1B5E20'])
    ->resources([PostResource::class]);

brandName(), logo() and colors() are exposed to the frontend through window.Arrel (brand name in the <title> and in the interface, logo, and the color tokens that override the default palette). Without brandName(), the panel is called "Arrel".

For your own typography, fonts() and stylesheet() work like colors() but for font families:

php
Panel::make('branded-demo')
    ->fonts(['sans' => '"Poppins", sans-serif'])
    ->stylesheet('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');

fonts() overrides the font family tokens (sans, display...) just as colors() does with the color ones. stylesheet() loads an external stylesheet (typically a Google Fonts link) in the <head>, before Arrel's own CSS, so that the @font-face is already registered when needed.

Theme ​

php
Panel::make('admin')->theme('light');

theme() accepts auto (the default, follows the system), light or dark. Any other value throws an InvalidArgumentException.

php
Panel::make('admin')->footer(fn (): array => [
    'text' => '© '.now()->year.' Acme',
    'links' => [
        ['label' => 'Cookies', 'url' => '/cookies'],
    ],
]);

footer() takes an array, or a closure that returns one, with text and a list of links. It is shown below every page of the panel.

Interface texts ​

The interface texts (buttons, states, security screens...) are in English. They can be translated with translations(), which takes an array of key => text or a closure that returns one. The closure is evaluated on every request, so it can depend on the active language:

php
Panel::make('admin')->translations(fn (): array => (array) __('arrel'));

The keys Arrel recognizes are:

save, cancel, remove, saved, loading, search_placeholder, searching, no_results, clear_filters, select_all, clear_selection, sign_in, sign_out, dashboard, email, password, invalid_credentials, something_went_wrong, any, new_prefix, edit_prefix, main_navigation, active, all_with_trashed, trashed_only, selected_suffix, columns, select_row_prefix, home, date_placeholder, today, clear, previous_month, next_month, two_factor_code, two_factor_code_hint, verify, use_another_account, security, two_factor_title, two_factor_on, two_factor_off, two_factor_required, two_factor_enable, two_factor_disable, two_factor_scan, two_factor_secret, two_factor_confirm, recovery_codes_title, recovery_codes_hint, continue, two_factor_disable_hint, verify_email_title, verify_email_hint, verify_email_send, verify_email_resend, verify_email_sent, verify_email_continue, verify_email_pending, notifications, mark_all_as_read and no_notifications.

A key you do not declare keeps its default text.

The error messages generated by the server (sign-in, second factor, email verification) and the names of the built-in actions are Laravel translations under the arrel:: namespace, and can be overridden with files in lang/vendor/arrel.

Profile page and home page ​

php
Panel::make('admin')
    ->profile(AccountPage::class)
    ->home(fn (): ?string => auth()->user()?->is_teacher ? PanelUrl::to('admin', 'custom/pass-list') : null);
  • profile() takes a Page and turns it into the user's profile link in the sidebar. It is only shown if the page's canView() is true.
  • home() takes a closure that returns the route where the user should land when opening the panel (on loading it, or coming from sign-in or the security page). With null, or an empty value, the user lands on the dashboard. The closure is evaluated per user, so everyone can have their own home page.

Scripts and stylesheets ​

To load the JavaScript of your custom pages there are two ways:

php
Panel::make('admin')
    ->scripts(fn (): array => [Vite::asset('resources/js/arrel/pages.ts')])
    ->stylesheets(fn (): array => [Vite::asset('resources/css/arrel.css')]);

scripts() adds <script type="module"> after arrel.js, and stylesheets() adds <link rel="stylesheet"> after Arrel's CSS. Both take an array of URLs or a closure that returns one, and are per panel. config('arrel.extra_scripts') does the same for all panels.

Authentication and security ​

Everything about Sanctum, email verification, second factor and notifications is in Authentication. Authorization and attempt limits are in Security and authorization.

Middleware ​

config('arrel.middleware') is added, in order, before Arrel's own middleware on all web and API routes. It is the entry point for anything your application needs to resolve before Arrel queries anything. The most common case is multi-tenancy:

php
// config/arrel.php
'middleware' => [
    \App\Http\Middleware\IdentifyTenant::class,
],

config('arrel.authenticated_middleware') runs after Arrel has already resolved the user, and after email verification and the second factor. middleware runs before Sanctum resolves the session, so it does not see an authenticated user yet: anything that needs one must go in authenticated_middleware.

php
// config/arrel.php
'authenticated_middleware' => [
    \App\Http\Middleware\RecordLastSeen::class,
],

The full order of the API routes is:

php
[
    ...config('arrel.middleware'),
    ForceJsonResponse::class,
    EnsureFrontendRequestsAreStateful::class,
    'web',
    'auth:sanctum',
    EnsureEmailIsVerified::class,
    EnsureTwoFactorIsSetUp::class,
    ...config('arrel.authenticated_middleware'),
]

The endpoints of the custom pages that you write in your application must use this same stack, so that they inherit the same guarantees (see Custom pages).

Next step ​

With the Panel registered, define your first Resource.