The reference plugins are ordinary publishable packages built only on the documented manifest and registry surfaces. They are usable integrations and examples, not hidden framework features.
Activity log
Activity log provides a panel- and
tenant-scope-aware, read-only resource over core’s action-event table. It
ships exact resource/action/actor/target filters, semantic change display,
safe resource links, a retention helper, and the
./commands/activity_prune Ace command.
The package exports two manifest entry points:
activityLogManifestcarries protocol, compatibility, client-entry, and migration metadata for installers.activityLogPlugin({ panel, authorize, tenantScope?, ... })returns the configured manifest whose server hook adds the resource to that panel.
Authorization is deliberately required; the package does not invent a policy or expose the log by default.
import { Panel } from '@adonia/core'
import { activityLogPlugin } from 'adonia-activity-log'
import User from '#models/user'
Panel.make('admin').plugins([
activityLogPlugin({
panel: 'admin',
authorize: (ctx) => ctx.auth.user instanceof User && ctx.auth.user.role === 'admin',
}),
])Import and export
Import and export provides explicit CSV/XLSX mapping, bounded and queued imports, streaming/compressed exports, owned history and progress, Drive/Lucid/file persistence adapters, failure artifacts, retries, and export presets.
importExportManifest (also returned by the source-compatible
importExportPlugin() function) declares:
- guarded panel routes under
transfers/:resource/*; - reserved export and import action-job handlers;
- the
import-export-workerdoctor check; - its persistence migration directory; and
- the
adonia-import-export/clientbrowser entry.
The routes resolve the host’s authenticated controller from
adonia.importExportController. Worker handlers resolve the host’s transfer
runtime from adonia.importExportWorker. Attaching the manifest therefore
replaces hand-written route tables and action-name switches, but the host
still binds those two application-specific services, starts a queue consumer,
chooses storage credentials, and enforces resource/actor/tenant ownership.
The low-level services, stores, endpoint contracts, actions, and
routeImportExportWorker() remain exported for advanced composition.
Roles and permissions
Roles provides tenant-aware Lucid role, permission,
and assignment storage plus a panel-scoped role-management resource and a
Bouncer-compatible PermissionPolicy base.
pnpm add adonia-roles @adonisjs/lucid
node ace configure adonia-roles
node ace migration:runAttach rolesPlugin() to every panel that manages roles. Configuration names
the panel, declares the permission catalogue shown in the resource’s
checkbox-list field, and requires a server-side authorize callback:
import { rolesPlugin } from 'adonia-roles'
rolesPlugin({
panel: 'admin',
permissions: [
{ slug: 'posts.view', label: 'View posts' },
{ slug: 'posts.*', label: 'Manage posts' },
],
authorize: (ctx) => ctx.auth.user?.isSuperAdmin === true,
})Queries and assignments are scoped to the configured panel and active tenant.
Use tenantScope: false only for a genuinely non-tenant panel. Permission
slugs support exact <resource>.<ability>, <resource>.*, and * grants;
extend PermissionPolicy on host resources rather than bypassing core’s
normal authorization pipeline. A bare role slug must be unique within its
tenant, so pass a persisted Role or numeric id when multiple panels reuse a
slug. rolesManifest supplies migrations and the roles-tables doctor check;
this package has no separate browser entry because its management UI is a
normal Adonia resource.
Media library
Media library adds a tenant-aware
MediaAsset model, browsable resource, protected picker/upload routes, named
collection rules, optional Sharp variants, and F.media() for storing an
asset id on another resource.
pnpm add adonia-media-library @adonisjs/drive @adonisjs/lucid
node ace configure adonia-media-library
node ace migration:runConfigure an @adonisjs/drive disk, then attach a manifest with explicit
collections, storage, and authorization:
import { defineMediaCollections, mediaLibraryPlugin } from 'adonia-media-library'
mediaLibraryPlugin({
collections: defineMediaCollections([
{ name: 'images', acceptedMime: ['image/*'], maxSizeMb: 12 },
]),
disk: 'media',
visibility: 'private',
authorize: (ctx) => ctx.auth.user !== null,
})Register mediaLibraryUiPlugin from adonia-media-library/client in the
application ComponentRegistry; it supplies the lazy picker used by
F.media('coverAssetId', { collection: 'images' }). The picker browses,
searches, uploads, and selects only through panel-guarded routes. Uploads are
sniffed from stored bytes rather than trusting MIME headers, and private disk
keys stay out of descriptors. Install sharp only when collections declare
image variants: without it the original succeeds but conversion status is
sharp-unavailable. Treat the stored numeric asset id as a host-model foreign
key, not as a Drive object key.
Two-factor authentication
Two factor provides RFC 6238 TOTP credentials, single-use recovery codes, panel middleware, protected setup/challenge routes, and lazy setup and challenge pages.
pnpm add adonia-two-factor
node ace configure adonia-two-factor
node ace migration:runThe host must configure APP_KEY. Attach twoFactorPlugin({ issuer, window, recoveryCodeCount, stepUpSeconds }), and place
requireTwoFactor({ stepUpSeconds }) after the host authentication middleware.
The middleware permits users without a confirmed credential and redirects an
enabled user without a recent panel-bound proof to the challenge route.
Register twoFactorUiPlugin from adonia-two-factor/client, then let the
Inertia resolver ask the registry for adonia-two-factor/setup and
adonia-two-factor/challenge before resolving application pages. Setup shows
the base32 secret and otpauth:// URI; both pages are lazy chunks. Request
bodies never choose the user or panel. Secrets are encrypted with APP_KEY,
plaintext recovery codes are returned only once, regeneration invalidates all
old recovery codes, and disabling 2FA requires the current password plus a
current TOTP or recovery code.
Backups
Backups supplies PostgreSQL, MySQL, and file-backed SQLite backup/restore engines, private gzip artifacts, checksum verification, retention, signed downloads, a read-only panel resource, and Ace commands.
pnpm add adonia-backups @adonisjs/drive @adonisjs/lucid
node ace configure adonia-backups
node ace migration:runCreate a private Drive disk, LucidBackupRecordRepository, BackupEngine,
DefaultBackupsRuntime, and DefaultBackupsController. Bind the runtime at
ADONIA_BACKUPS_RUNTIME_BINDING, bind the controller at
ADONIA_BACKUPS_CONTROLLER_BINDING, and attach
backupsPlugin({ panel, disk, diskName, authorize }). The separately booted
queue worker needs the runtime binding too: the manifest’s reserved
adonia-backups.run handler dispatches server-created jobs to
DefaultBackupsRuntime.work.
The resource exposes server-authorized signed downloads without emitting
opaque Drive keys. Register the exported backup/run and backup/prune commands
in adonisrc.ts for operator use. PostgreSQL images need pg_dump and psql;
MySQL images need mysqldump and mysql; in-memory SQLite is unsupported.
Restore is destructive, requires the exact record-specific
restoreConfirmationToken(record), verifies size and SHA-256 before changing
the target, and has no implicit rollback.
Notifications
Notifications provides viewer-scoped
database notifications, guarded JSON routes, queued-action completion alerts,
an optional mail channel, and a lazy bell in topbar.right.
pnpm add adonia-notifications
node ace configure adonia-notifications
node ace migration:runAttach notificationsPlugin() to each panel. notify(recipient, payload)
requires the panel and normalized tenant, uses the database channel by
default, and accepts root-relative or HTTP(S) href values. For mail, inject
an AdonisMailChannelAdapter into NotificationService and request
channels: ['database', 'mail']; recipients without an email are skipped.
Add adonia-notifications to the Tailwind v4 sources and register
notificationsUiPlugin from adonia-notifications/client. The bell polls its
guarded panel endpoint every 30 seconds, pauses while the document is hidden,
and supports mark-one and mark-all-read; use
createNotificationsUiPlugin({ pollIntervalMs: 0 }) to disable polling.
Routes scope every query by viewer, panel, and tenant, and deliberately omit
notification data from responses. Queued completion alerts are emitted only
for actions declared with isQueued: true, and failure copy never includes
the thrown exception.
Browser registration and installation
Activity log, import/export, media library, two-factor, and notifications
publish named UI plugins from ./client. A manifest’s client string is
metadata rather than an automatic import: register the matching UI plugin in
the application’s ComponentRegistry and keep its server/client package
versions equal. Roles and backups render through core resource surfaces and do
not require a separate browser plugin.
Each package’s node ace configure <package> hook uses
configureAdoniaApp(..., { plugins }) from @adonia/devtools/configure to
publish manifest migrations and collect doctor checks. Run migrations after
configuration. Reference-plugin routes are never public escape hatches:
manifest routes inherit the panel’s access and tenant middleware, and package
handlers add their documented actor/tenant/authorization scopes.
Complete exported surfaces for all reference packages are inventoried in the generated API reference.