---
title: "1. Install"
image: "https://adonia.pages.dev/og.png"
version: "next"
---

> Documentation Index
> Fetch the complete documentation index at: https://adonia.pages.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

```sh
node ace add @adonia/core
```

```text
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`.

```ts no-check
// 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:

<!-- @sample-preamble
import type { ResourceRegistryInput } from '@adonia/core'
declare const registry: ResourceRegistryInput
-->

```ts
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)
```

<small>`examples/blog-admin/app/adonia/panels/admin_panel.ts`</small>

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:

```tsx no-check
// 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`:

```css
@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:

```ts no-check
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

```sh
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](/guide/tutorial/resource).

Source: https://adonia.pages.dev/guide/tutorial/install/index.mdx
