Referensi API Panel
Dokumen ini merangkum seluruh public method pada PandaPanel\Core\Panel, beserta tiga type yang mengelilinginya: PanelProvider, PanelManager, dan helper panel(). Fluent setter menggunakan nama langsung seperti id(), path(), dan middleware(), sedangkan reader memakai prefix get atau has. PHP tidak mendukung method overloading, dan API setter/getter gabungan yang mengembalikan string|static adalah jenis magic yang sengaja dihindari framework ini.
Membuat panel
use PandaPanel\Core\Panel;
$panel = Panel::make('admin')
->path('admin')
->name('Administrator')
->auth()
->discoverResources(app_path('Panels/Admin/Resources'));
$panel->getId(); // 'admin'
$panel->getPath(); // 'admin'2
3
4
5
6
7
8
9
10
public static function make(?string $id = null): selfmake() tanpa id membiarkan id belum terisi, dan getId() kemudian melempar PandaPanel\Exceptions\PanelRegistrationException. Di dalam provider, id sudah di-seed dari nama class provider.
Identitas
| Method | Signature | Default |
|---|---|---|
id | id(string $id): self | dari PanelProvider::panelId() |
name | name(string $name): self | Str::headline($id) |
path | path(string $path): self | id; slash depan/belakang di-trim |
domain | domain(?string $domain): self | null |
getId | getId(): string | melempar exception jika belum pernah diatur |
getName | getName(): string | |
getPath | getPath(): string | |
getDomain | getDomain(): ?string | |
getRouteNamePrefix | getRouteNamePrefix(): string | "panel.{$id}." |
routeName | routeName(string $name): string |
panel('admin')->routeName('dashboard'); // 'panel.admin.dashboard'Middleware dan authentication
| Method | Signature | Default |
|---|---|---|
middleware | middleware(array $middleware): self | ['web'], mengganti stack |
authMiddleware | authMiddleware(array $middleware): self | ['auth'], mengganti stack; gunakan [] untuk panel publik |
auth | auth(bool $verified = true): self | merge auth (+ verified) |
getMiddleware | getMiddleware(): list<string> | base + auth, deduplicated |
getBaseMiddleware | getBaseMiddleware(): list<string> | |
getAuthMiddleware | getAuthMiddleware(): list<string> | |
requireTwoFactor | requireTwoFactor(bool $required = true): self | nonaktif |
requiresTwoFactor | requiresTwoFactor(): bool | false |
Panel::make('admin')->middleware(['web'])->auth()->getMiddleware();
// ['web', 'auth', 'verified']2
Front door milik panel
| Method | Signature | Default | Route yang didaftarkan |
|---|---|---|---|
login | login(bool $login = true): self | false | auth.login pada {path}/login |
registration | registration(bool $registration = true): self | false | auth.register |
passwordReset | passwordReset(bool $passwordReset = true): self | false | auth.password.request, auth.password.reset |
emailVerification | emailVerification(bool $emailVerification = true): self | false | auth.verification.notice |
hasLogin | hasLogin(): bool | ||
hasRegistration | hasRegistration(): bool | ||
hasPasswordReset | hasPasswordReset(): bool | ||
hasEmailVerification | hasEmailVerification(): bool |
$panel->login()->registration()->passwordReset();Page auth didaftarkan di luar auth stack panel dan melakukan POST ke endpoint Fortify. registration() dan fitur auth terkait hanya mendaftarkan page ketika login() aktif.
Akses
| Method | Signature |
|---|---|
canAccess | canAccess(Closure $callback): self — Closure(?Authenticatable): bool |
isAccessibleTo | isAccessibleTo(?Authenticatable $user): bool |
use Illuminate\Contracts\Auth\Authenticatable;
$panel->canAccess(static fn (?Authenticatable $user): bool => $user?->is_admin === true);2
3
isAccessibleTo() juga memeriksa PandaPanel\Contracts\PanelUser::canAccessPanel() jika user model mengimplementasikannya. Kedua rule harus menyetujui akses.
Mendaftarkan class
| Method | Signature |
|---|---|
resources | resources(array $resources): self — class string atau ResourceConfiguration |
pages | pages(array $pages): self |
widgets | widgets(array $widgets): self |
discoverResources | discoverResources(string ...$paths): self |
discoverPages | discoverPages(string ...$paths): self |
discoverWidgets | discoverWidgets(string ...$paths): self |
getResources | getResources(): list<class-string> |
getPages | getPages(): list<class-string> — settings page berada di awal ketika aktif |
getWidgets | getWidgets(): list<class-string> |
getResourceConfigurations | getResourceConfigurations(): list<ResourceConfiguration> |
getResourceDiscoveryPaths | getResourceDiscoveryPaths(): list<string> |
getPageDiscoveryPaths | getPageDiscoveryPaths(): list<string> |
getWidgetDiscoveryPaths | getWidgetDiscoveryPaths(): list<string> |
Keenam method registration bersifat akumulatif dan melakukan deduplication.
Panel::make('modules')
->discoverResources('/one')
->discoverResources('/two', '/one')
->getResourceDiscoveryPaths(); // ['/one', '/two']2
3
4
Landing page
| Method | Signature | Default |
|---|---|---|
dashboard | dashboard(string $page): self — class-string<Page> | PandaPanel\Pages\Dashboard |
dashboards | dashboards(array $pages): self | entry pertama menjadi root, sisanya menjadi page |
getDashboard | getDashboard(): class-string<Page> | |
getExtraDashboards | getExtraDashboards(): list<class-string<Page>> | [] |
$panel->dashboards([Dashboard::class, AccountsDashboard::class]);
$panel->getExtraDashboards(); // [AccountsDashboard::class]2
Built-in settings page
| Method | Signature | Default |
|---|---|---|
settings | settings(bool $settings = true): self | true |
hasSettings | hasSettings(): bool | true |
Ketika aktif, getPages() diawali oleh ProfileSettings, SecuritySettings, dan AppearanceSettings.
Navigation dan shell
| Method | Signature | Default |
|---|---|---|
navigationGroups | navigationGroups(array $groups): self | [], akumulatif |
getNavigationGroups | getNavigationGroups(): list<string> | |
getNavigationGroupParents | getNavigationGroupParents(): array<string, string> | [] |
sidebar | sidebar(bool $collapsible = true, bool $defaultOpen = true, string $variant = 'sidebar', string $appearance = 'inset'): self | sesuai signature |
topNavigation | topNavigation(bool $topNavigation = true): self | variant = 'sidebar' |
sidebarWidth | sidebarWidth(string $width, ?string $collapsedWidth = null): self | '16rem' |
collapsedSidebarWidth | collapsedSidebarWidth(string $width): self | '3rem' |
sidebarComponent | sidebarComponent(?string $component): self | null |
topbarComponent | topbarComponent(?string $component): self | null |
navigation | navigation(bool $navigation = true): self | true |
topbar | topbar(bool $topbar = true): self | true |
breadcrumbs | breadcrumbs(bool $breadcrumbs = true): self | true |
userMenuItems | userMenuItems(array $items): self | [], akumulatif |
maxContentWidth | maxContentWidth(?string $maxContentWidth): self | null |
hasNavigation | hasNavigation(): bool | |
hasTopbar | hasTopbar(): bool | |
hasBreadcrumbs | hasBreadcrumbs(): bool | |
getUserMenuItems | getUserMenuItems(): list<array<string, mixed>> | |
getMaxContentWidth | getMaxContentWidth(): ?string | |
getSidebar | getSidebar(): array<string, mixed> | |
getShell | getShell(): array<string, mixed> |
$panel->navigationGroups(['Content', 'System', 'Access' => 'System']);
$panel->getNavigationGroupParents(); // ['Access' => 'System']2
Branding dan theme
| Method | Signature | Default |
|---|---|---|
brandName | brandName(string $brandName): self | config('app.name') |
brandLogo | brandLogo(?string $brandLogo, ?string $darkBrandLogo = null): self | null |
darkBrandLogo | darkBrandLogo(?string $brandLogo): self | null |
favicon | favicon(?string $favicon, ?string $darkFavicon = null): self | null |
darkFavicon | darkFavicon(?string $favicon): self | null |
icon | icon(?string $icon, ?string $darkIcon = null): self | null, icon registry key |
darkIcon | darkIcon(?string $icon): self | null |
darkMode | darkMode(bool $darkMode = true): self | true |
colors | colors(array $light, array $dark = []): self | [] / [] |
cssHooks | cssHooks(array $classes): self | [] |
getBrandName | getBrandName(): string | |
getBrandLogo | getBrandLogo(): ?string | |
getFavicon | getFavicon(): ?string | |
getIcon | getIcon(): ?string | |
hasDarkMode | hasDarkMode(): bool | |
getTheme | getTheme(): array{light: array<string, string>, dark: array<string, string>} | |
getCssHooks | getCssHooks(): array<string, string> |
Unknown color property, invalid color value, dan unknown hook name dibuang daripada membuat konfigurasi ditolak secara keseluruhan.
Behavior
| Method | Signature | Default |
|---|---|---|
databaseTransactions | databaseTransactions(bool $databaseTransactions = true): self | true |
strictAuthorization | strictAuthorization(bool $strictAuthorization = true): self | false |
unsavedChangesAlerts | unsavedChangesAlerts(bool $unsavedChangesAlerts = true): self | true |
bootUsing | bootUsing(Closure $callback): self — Closure(Panel): void | tidak ada, akumulatif |
configureActions | configureActions(Closure $callback): self — Closure(Action): void | null, mengganti callback sebelumnya |
hasDatabaseTransactions | hasDatabaseTransactions(): bool | |
hasStrictAuthorization | hasStrictAuthorization(): bool | |
hasUnsavedChangesAlerts | hasUnsavedChangesAlerts(): bool | |
getBootCallbacks | getBootCallbacks(): list<Closure> | |
actionConfigurator | actionConfigurator(): ?Closure | |
boot | boot(): void | menjalankan plugin lebih dulu, lalu boot callback |
boot() dipanggil oleh ResolvePanel sekali per request setelah access check. Jangan memanggilnya sendiri.
Behavior navigation
| Method | Signature | Default |
|---|---|---|
prefetch | `prefetch(bool | string $prefetch = 'hover'): self—'hover', 'mount', 'click', true, false` |
fullPageUrls | fullPageUrls(string ...$patterns): self | [], akumulatif |
errorNotification | errorNotification(int $status, string $title, ?string $body = null): self | lihat keterangan di bawah |
hideErrorNotification | hideErrorNotification(int $status): self | |
getPrefetch | getPrefetch(): ?string | null ketika nonaktif |
getFullPageUrls | getFullPageUrls(): list<string> | |
isFullPageUrl | isFullPageUrl(string $url): bool | absolute URL dicocokkan berdasarkan path-nya juga |
getErrorNotifications | `getErrorNotifications(): array<int, array{title: string, body: string | null} |
Default error notification tersedia untuk status 403, 404, 419, 429, 500, dan 503. Panel dapat mengganti satu status tanpa menuliskan ulang yang lain. Entry null dari hideErrorNotification() menekan toast sekaligus Inertia overlay.
Panel::make('patterns')->fullPageUrls('/reports/*')->isFullPageUrl('https://example.test/reports/monthly');
// true2
Global search
| Method | Signature | Default |
|---|---|---|
globalSearch | globalSearch(bool $enabled = true, int $limit = 50, int $debounce = 300, array $keyBindings = ['mod+k']): self | sesuai signature |
hasGlobalSearch | hasGlobalSearch(): bool | true |
getGlobalSearchLimit | getGlobalSearchLimit(): int | 50 |
getGlobalSearchDebounce | getGlobalSearchDebounce(): int | 300 |
getGlobalSearchKeyBindings | getGlobalSearchKeyBindings(): list<string> | ['mod+k'] |
Palette tetap tidak muncul kecuali setidaknya satu resource pada panel mendeklarasikan $globalSearchAttributes, walaupun hasGlobalSearch() bernilai true.
Notification dan broadcasting
| Method | Signature | Default |
|---|---|---|
notifications | notifications(bool $notifications = true): self | true |
broadcasting | broadcasting(bool $broadcasting = true): self | true |
hasNotifications | hasNotifications(): bool | |
hasBroadcasting | hasBroadcasting(): bool | |
getBroadcastChannel | getBroadcastChannel(?Authenticatable $user): ?string | null ketika off atau tidak ada user |
Extension point
| Method | Signature | Default |
|---|---|---|
assets | assets(string ...$entrypoints): self | [], akumulatif |
getAssets | getAssets(): list<string> | tidak pernah diserialisasi |
renderHook | renderHook(RenderHook $hook, string $component, array $data = [], array $scopes = []): self | |
getRenderHooks | getRenderHooks(): array<string, list<array{component: string, data: array, scopes: list<string>}>> | |
subNavigationPosition | subNavigationPosition(SubNavigationPosition $position): self | SubNavigationPosition::Top |
getSubNavigationPosition | getSubNavigationPosition(): SubNavigationPosition | |
plugins | plugins(array $plugins): self — PanelPlugin[], menjalankan register() | |
getPlugins | getPlugins(): array<string, PanelPlugin> | |
hasPlugin | hasPlugin(string $id): bool | |
plugin | plugin(string $id): ?PanelPlugin |
use PandaPanel\Enums\RenderHook;
$panel->renderHook(
RenderHook::HeaderEnd,
'Panels/Admin/Hooks/Announcement',
['message' => 'Maintenance at 5pm'],
[UserResource::class],
);2
3
4
5
6
7
8
Scope direduksi menjadi slug saat registration: resource menjadi resource:{slug} dan page menjadi page:{slug}. Dua plugin yang mengklaim id yang sama melempar exception.
Tenancy
| Method | Signature |
|---|---|
tenant | tenant(string $model, Closure $resolver): self — Closure(Request, ?Authenticatable): ?Model |
tenantUrlUsing | tenantUrlUsing(Closure $url): self — Closure(Model, Panel): string |
getTenantUrl | getTenantUrl(Model $tenant): ?string |
hasTenancy | hasTenancy(): bool |
getTenantModel | getTenantModel(): ?class-string<Model> |
resolveTenant | resolveTenant(Request $request, ?Authenticatable $user): ?Model |
resolveTenant() hanya dipanggil oleh ResolveTenant. Resolver yang mengembalikan model selain class tenant yang dideklarasikan diperlakukan sebagai tidak ada tenant. Tanpa tenantUrlUsing(), tenant switcher tidak dirender.
Data yang dikirim ke frontend
public function toSharedArray(): array[
'id' => 'admin',
'name' => 'Administrator',
'path' => 'admin',
'brandName' => 'Acme',
'brandLogo' => null,
'darkBrandLogo' => null,
'icon' => 'shield',
'darkIcon' => null,
'favicon' => null,
'darkFavicon' => null,
'darkMode' => true,
'maxContentWidth' => null,
'unsavedChangesAlerts' => true,
'prefetch' => 'hover',
'errorNotifications' => [403 => ['title' => 'Not allowed', 'body' => '…'], /* ... */],
'renderHooks' => [],
'sidebar' => ['collapsible' => true, 'defaultOpen' => true, 'variant' => 'sidebar', 'appearance' => 'inset', 'width' => '16rem', 'collapsedWidth' => '3rem', 'component' => null],
'shell' => ['navigation' => true, 'topbar' => true, 'breadcrumbs' => true, 'topbarComponent' => null, 'userMenuItems' => []],
'theme' => ['light' => [], 'dark' => []],
'cssHooks' => [],
]2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Konfigurasi server-only sengaja tidak ada: transaction, strict authorization, boot callback, middleware, discovery path, dan asset list tidak pernah dikirim. Tidak ada value pada shared array yang boleh berisi closure.
PanelProvider
namespace PandaPanel\Core;
abstract class PanelProvider
{
abstract public function panel(Panel $panel): Panel;
public function panelId(): string; // 'AdminPanelProvider' → 'admin'
public function build(): Panel; // panel(Panel::make($this->panelId()))
}2
3
4
5
6
7
8
9
10
Jangan melakukan service resolution di panel(): method berjalan saat provider boot sebelum request-scoped binding siap digunakan.
PanelManager
namespace PandaPanel\Core;
final class PanelManager
{
public function registerProvider(string $provider): Panel; // class-string<PanelProvider>
public function register(Panel $panel): Panel;
/** @return list<Panel> sorted by id */
public function all(): array;
public function has(string $id): bool;
public function get(string $id): Panel; // throws when unknown
public function resolveFromRequest(Request $request): ?Panel; // longest path first
public function firstAccessibleTo(?Authenticatable $user): ?Panel; // id order
public function setCurrentPanel(?Panel $panel): void;
public function currentPanel(): ?Panel;
public function hasCurrentPanel(): bool;
public function resources(Panel|string $panel): ResourceRegistry;
public function pages(Panel|string $panel): PageRegistry;
public function widgets(Panel|string $panel): WidgetRegistry;
public function navigation(Panel|string $panel): NavigationRegistry;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
register() mengisi registry; route didaftarkan secara terpisah oleh PandaPanel\Routing\PanelRouteRegistrar.
Helper
function panel(?string $id = null): ?Panelpanel(); // the panel for this request, or null outside one
panel('admin'); // an explicit panel; throws PanelRegistrationException if unknown2
Contract
namespace PandaPanel\Contracts;
interface PanelContract
{
public function getId(): string;
public function getPath(): string;
public function getDomain(): ?string;
/** @return list<string> */
public function getMiddleware(): array;
public function isAccessibleTo(?Authenticatable $user): bool;
}
interface PanelUser
{
public function canAccessPanel(Panel $panel): bool;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PanelContract adalah dependency yang digunakan route registrar, middleware, dan navigation builder. navigationItem() pada resource/page menerima PanelContract, sehingga implementasi perlu melakukan narrowing ke Panel sebelum memanggil method yang hanya tersedia pada concrete panel.
Catatan
- Setter mengembalikan
self, sehingga seluruh call dapat di-chain. Tidak ada validasi urutan call;sidebar()setelahtopNavigation()mereset variant karenasidebar()mengatur keempat argument sekaligus. - Method akumulatif:
discoverResources,discoverPages,discoverWidgets,resources,pages,widgets,navigationGroups,fullPageUrls,assets,userMenuItems,bootUsing,cssHooks,colors,renderHook, danplugins. Method yang mengganti nilai sebelumnya: yang lain, termasukmiddleware,authMiddleware,canAccess, danconfigureActions. - Reader aman dipanggil kapan saja. Setter yang dipanggil setelah boot tidak mendaftarkan ulang route yang sudah terdaftar.
Paneladalah classfinal. Tambahkan behavior melalui plugin (PanelPlugin), yang mengonfigurasi panel menggunakan public API yang sama dan tidak memerlukan jalur internal lain.