Skip to content

1. Install

node ace add @adonia/core installs the package and runs its configure hook. One command, nine steps, all idempotent — running it twice changes nothing.

node ace add @adonia/core
Adonia installed. Summary:

Created:
  config/adonia.ts
  app/adonia/panels/admin_panel.ts
  .adonisjs/adonia/resources.ts
  .adonisjs/adonia/descriptor.d.ts
  .adonisjs/adonia/manifest.json
  inertia/pages/adonia/resource_index.tsx
  inertia/pages/adonia/resource_form.tsx
  inertia/pages/adonia/resource_detail.tsx
  inertia/pages/adonia/dashboard.tsx
  inertia/pages/adonia/login.tsx
  inertia/pages/adonia/error.tsx
  inertia/adonia.ts
  app/middleware/adonia_panel_access_middleware.ts
  database/migrations/<timestamp>_create_adonia_action_events_table.ts

Modified:
  adonisrc.ts
  package.json
  inertia/app.tsx
  inertia/css/app.css
  start/kernel.ts

What each piece is for

adonisrc.ts gains three entries: the provider @adonia/core/providers/adonia_provider, the command loader @adonia/devtools/commands (this is what gives you node ace adonia:*), and the codegen hook indexAdoniaResources() inserted immediately after indexEntities() in hooks.init.

// adonisrc.ts — examples/blog-admin/adonisrc.ts
import { indexAdoniaResources } from '@adonia/devtools/hooks'

export default defineConfig({
  commands: [
    // …
    () => import('@adonia/devtools/commands'),
  ],
  providers: [
    // …
    () => import('@adonia/core/providers/adonia_provider'),
  ],
  hooks: {
    init: [indexEntities({ /* … */ }), indexAdoniaResources(), indexPages({ framework: 'react' })],
  },
})

config/adonia.ts is the framework defaults, written out so you can read them: 25 rows per page, offset pagination, redirectAfterCreate: 'edit', authorization.fallback: 'deny' (with devFallback: 'allow' outside production), the database search driver, and a 10 MB upload ceiling. Leave it alone for now; chapter 6 changes one line of it.

app/adonia/panels/admin_panel.ts is your panel. The stub mounts it at /admin behind the web guard:

import { Panel } from '@adonia/core'

// The GENERATED registry — `.adonisjs/adonia/resources.ts`, never edited.
// import registry from '../../../.adonisjs/adonia/resources.js'

export default Panel.make('admin')
  .path('/admin')
  .guard('web')
  .login({ model: () => import('#models/user') })
  .brand({ name: 'Blog Admin' })
  .resources(registry)

examples/blog-admin/app/adonia/panels/admin_panel.ts

Two calls the stub does not make, both worth adding now. login({ model }) names the model behind the guard’s user provider — an AdonisJS guard signs a user in but cannot verify a password, so the packaged login page needs the model carrying withAuthFinder’s verifyCredentials. brand() is the name and accent colour the chrome renders; chapter 8 comes back to it.

.adonisjs/adonia/ holds three generated artifacts — never edit them:

File Contents
resources.ts The registry the panel imports: slug → () => import(module)
descriptor.d.ts Ambient AdoniaGen.<Resource>.Row / .FormState types
manifest.json The §14 manifest: resources, their columns and fields, panels

They are regenerated by the init hook whenever the dev server boots and whenever a file under app/adonia/resources or app/adonia/panels changes under node ace serve --hmr.

inertia/pages/adonia/*.tsx are six thin re-exports — the packaged pages, imported into your Vite build so they are scanned, bundled and hot-reloaded like your own pages:

// inertia/pages/adonia/resource_index.tsx
export { ResourceIndexPage as default } from '@adonia/ui/pages'

To take one over, node ace adonia:eject resource_index replaces the file with the full source. inertia/adonia.ts is the client entry (adonia({})) where custom components and theme overrides are registered, imported for side effects from inertia/app.tsx.

inertia/css/app.css gains one mandatory line. Tailwind v4 only scans files it can see, and the packaged components live in node_modules:

@source '../../node_modules/@adonia/ui';

Without it the panel renders unstyled. If your app had no Tailwind, the installer also adds tailwindcss + @tailwindcss/vite to package.json and registers the Vite plugin — run your package manager afterwards.

start/kernel.ts gains the panel gate as the first named middleware:

export const middleware = router.named({
  'adonia.panel-access': () => import('#middleware/adonia_panel_access_middleware'),
  // …
})

Every panel route except the login page goes through it, so an unauthenticated GET is redirected to the panel’s own login route rather than the app’s.

Boot it

node ace migration:run
node ace serve --hmr

Visit http://localhost:3333/admin. You are redirected to /admin/login, sign in with a seeded user, and land on an empty dashboard: the panel works, it just has no resources yet.

Run node ace adonia:doctor if anything looks wrong — it checks the init hook, the generated files, cookie/domain settings, Vite host config and the security defaults, and tells you which one is off.

Next: the Post resource.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close