Skip to main content
zerotal

References

Panel

MethodDescription
Panel.configure(config)Merge panel configuration.
Panel.register(...resources)Register one or more Resource classes.
Panel.pages(...pages)Register AdminPage subclasses.
Panel.plugin(...plugins)Install app-authored plugins.
Panel.widgets(...widgets)Register dashboard widgets.
Panel.notifications(provider)Wire the notification center.
Panel.auth(config)Enable + configure the auth pages.
Panel.can(ability)Resolve an ability the way the panel does.
Panel.host()The contribution surface, bound as admin.panel.
Panel.resources() / find(slug)Inspect the resource registry.
Panel.registeredPages() / findPage()Inspect the page registry.
Panel.navigation()The full sidebar map, unfiltered.
Panel.visibleNavigation()The sidebar as the current user may see it.
Panel.make(id, config)Create an additional panel on its own path.
Panel.get(id) / Panel.all()Inspect the panel registry.
Panel.default()The panel every app starts with.
Panel.current()The panel owning the request being served.
Panel.renderHook(name, fn)Render into a named position in the chrome.
Panel.renderHooks(name)The hooks registered at a position.

Resource statics

model, slug, label, pluralLabel, primaryKey, perPage, defaultSort, eager, recordTitleAttribute, navigationIcon, navigationGroup, navigationSort, navigationParentItem, navigationBadgeColor, reorderable, defaultGroup, cluster, parent, singular, tableLayout, striped, stickyHeader, density, filterLayout — plus the methods columns(), form(), infolist(), filters(), tabs(), groups(), relations(), recordActions(), headerActions(), bulkActions(), navigationBadge(), emptyState(), widgets(), data(), can(), and the lifecycle hooks above.

Each resource also builds its own URLs — indexUrl(base), recordUrl(base, id), createUrl(base), editUrl(base, id) and routePath(). Link through these rather than assembling paths by hand, and a resource can move into a cluster or under a parent without anything else changing.

Generator

bun zt make:admin-resource Product
bun zt make:admin-resource Comment --parent=Post --foreign-key=post_id
bun zt make:admin-resource Setting --singular
bun zt make:admin-resource Order --cluster=ShopCluster

Writes app/admin/<Name>Resource.ts. Named make:admin-resource because make:resource already belongs to the API transformer generator.

Configuration and middleware

ExportSignatureDescription
AdminConfig(options?: Partial<AdminConfigShape>) => AdminConfigShapeBuild config/admin.ts with defaults filled in, the same shape Panel.configure takes.
AdminGuardMiddlewaremiddleware classGates every panel route behind the configured auth check.
AdminAbilityMiddlewaremiddleware classEnforces the resolved ability for the route being served.
adminHead(title?, theme?) => stringThe panel shell's <head> markup, for use as Layout.head in a custom layout.

Row and bulk actions

The Actions guide covers createAction, editAction, viewAction, deleteAction, replicateAction, importAction, exportAction and impersonateAction. The rest of the built-ins:

ExportSignatureDescription
restoreAction() => ActionRestore a soft-deleted record. Pair with Model.using(SoftDeletes).
forceDeleteAction() => ActionPermanently delete a soft-deleted record, bypassing the trash.
bulkEditAction(fields?: string[])Edit the named fields across every selected record in one form.
bulkRestoreAction() => ActionRestore every selected soft-deleted record.
bulkForceDeleteAction() => ActionPermanently delete every selected record.
textFilter(key: string) => FilterA free-text filter on one column, alongside selectFilter/ternaryFilter.

RelationManager is the base class a resource's relations() returns; see Actions & Relations.

Impersonation

Backing the impersonateAction button, for wiring it into your own UI. Each returns a [true] / [false, reason] pair rather than throwing, so a refusal is a value you can render.

ExportSignatureDescription
startImpersonating(userId) => Promise<[true] | [false, string]>Become another user, remembering the original.
stopImpersonating() => Promise<[true] | [false, string]>Return to the original user.
isImpersonating() => Promise<boolean>Whether the session is currently impersonating.
impersonatedName() => Promise<string | null>The impersonated user's display name, for a banner.

Media

The pieces behind mediaPicker and the media library, for driving uploads yourself.

ExportSignatureDescription
storeMedia(file, options) => Promise<[true, MediaItem] | [false, string]>Store an upload and record it against the provider.
deleteMedia(item, { provider, disk? }) => Promise<[true] | [false, string]>Remove a stored item and its record.
mediaUrl(item, disk?) => Promise<string | null>A browser-fetchable URL, signed when the disk requires it.
mediaPath(name, folder?) => stringThe storage path a given file name resolves to.
resolveMediaSrc(value, disk?) => string | nullTurn a stored field value into an src, or null.
formatSize(bytes: number) => stringHuman file size — 1.4 MB.
databaseMediaproviderThe built-in provider storing media rows in your database.

Import and export

ExportSignatureDescription
importCsv(resource, csv, mapping?, { limit? }) => Promise<ImportResult>Import rows into a resource, honouring the column mapping.
parseCsv(text) => string[][]Parse CSV text into rows of cells.
toCsv(rows, columns) => stringRender rows as CSV using a resource's columns.
toXlsx(rows, columns, { sheet? }) => Uint8ArrayRender rows as a spreadsheet.
dispatchImport(payload) => Promise<boolean>Queue an import; false when no queue is bound, so the caller can run it inline.
runQueuedImport(payload) => Promise<ImportResult>Run a queued import — exported so an app can drive it from its own job class.
ImportRecordsJobjob classThe built-in job dispatchImport enqueues.

Builder classes

You build these through their factories rather than constructing them; the class names matter only when you want to annotate a variable or a return type.

ClassBuilt by
ConstrainttextConstraint(), numberConstraint(), dateConstraint(), selectConstraint(), booleanConstraint()
FormSplitsplit(sections) — side-by-side form sections
Primeprime(text), primeHtml(html), primeImage(src) — static display blocks in a schema
PanelInstancePanel.make(id, config), Panel.get(id), Panel.current()

The same holds for Section, Tab, FormSection, FormTab, FormTabs, Wizard, WizardStep, Stat, Callout, ActionGroup, BuilderBlock, and the dashboard widgets StatsWidget, ChartWidget and TableWidget — each is the return type of the like-named factory documented in its own guide.

History and permissions

ExportSignatureDescription
recordHistory(options) => Promise<HistoryEntry[]>A record's audit history, newest first; empty rather than throwing when the audit table is absent.
panelPermissions(panel: PanelInstance) => Permission[]Every permission a panel checks, derived from what it has registered — use it to seed roles.
roleHas(role, held: string) => booleanWhether a role holds a permission; a superuser holds everything by definition.
authRolesroles helperThe role set the auth pages recognise.

Subpaths

ImportContents
@zerotal/adminResources, columns, fields, actions, widgets, Panel.
@zerotal/admin/authThe opt-in auth page classes + registerAuthRoutes.
@zerotal/admin/testingAdminTest + the assertion helpers.

Deliberately deferred

Multi-tenancy and the 2FA challenge step are intentionally out of scope for now.

Everything else once listed here has landed: clusters, nested and singular resources, and multiple panels; CSV and spreadsheet import and export; the visual query-builder filter; kanban, calendar and tree layouts, header filters and translations; and record history, impersonation, saved views, the media library, roles and per-user dashboards.

Next steps

  • Admin overview — the guide's front page and the rest of the sections.