PHPackages                             adhocrat-io/arkhe-main - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. adhocrat-io/arkhe-main

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

adhocrat-io/arkhe-main
======================

Bootstrap a Laravel backend with users, roles and permissions management on Flux UI.

3.1.2(1mo ago)095↑57.1%MITPHPPHP ^8.3CI failing

Since Apr 3Pushed 1w agoCompare

[ Source](https://github.com/Adhocrat-io/arkhe-main)[ Packagist](https://packagist.org/packages/adhocrat-io/arkhe-main)[ RSS](/packages/adhocrat-io-arkhe-main/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (24)Versions (13)Used By (0)

adhocrat-io/arkhe-main
======================

[](#adhocrat-ioarkhe-main)

Bootstrap a Laravel backend with **users, roles and permissions** management, served by **Livewire 4** and **Flux UI Free**. Ships first-class SEO, sitemap and cookie-consent integrations on top.

> First module of the `adhocrat-io/arkhe-*` namespace.

Requirements
------------

[](#requirements)

ComponentVersionPHP`^8.3`Laravel`^12.0Livewire`^4.0`Flux UI`^2.1` (Free edition)Spatie laravel-permission`^7.0`ralphjsmit/laravel-seo`^1.8`spatie/laravel-sitemap`^8.1`whitecube/laravel-cookie-consent`^1.3`### Recommended starting point

[](#recommended-starting-point)

The installer expects a Livewire 4 + Flux UI host app. The smoothest experience is on **Laravel 12 with the Livewire/Volt starter kit** (or the Flux starter), because:

- the layout default `layouts::app` (the Livewire 4 convention) resolves to a view the starter kit ships;
- the sidebar partial injection (step 8 below) targets the starter's ``;
- the Tailwind v4 `@source` patch (step 9 below) targets the starter's `resources/css/app.css`.

On a **bare Laravel app with no starter kit** the package still installs cleanly — the optional steps (sidebar + CSS) are skipped silently. You then have to point Arkhe at its own bundled layout (header-only, no sidebar):

```
ARKHE_ADMIN_LAYOUT=arkhe::layouts.app
```

See [Limitations](#limitations) for the full list of skip conditions.

Installation
------------

[](#installation)

```
composer require adhocrat-io/arkhe-main
php artisan arkhe:main:install
```

The interactive installer walks through every step in order:

1. Publish `config/arkhe.php`.
2. Publish the migration that adds profile columns (`first_name`, `last_name`, `avatar_path`, `phone`, `date_of_birth`, `civility`, `bio`) to the `users` table.
3. If `spatie/laravel-permission` is not migrated yet, publish its config and migrations automatically — no need to run its setup separately.
4. If `ralphjsmit/laravel-seo` is not migrated yet, publish its migration + config (see [SEO](#seo)).
5. Optionally publish the views.
6. Run `php artisan migrate`.
7. Seed the four default roles: `root`, `administrateur`, `user`, `guest`.
8. Patch your `` with `@include('arkhe::partials.sidebar-items')` (idempotent — skipped if already done).
9. Patch your Tailwind v4 `resources/css/app.css` with the `@source` directive needed to scan the package's Blade views (idempotent). For Tailwind v3 setups, the installer prints the equivalent `content` glob to add to `tailwind.config.js`.
10. Offer to add the `HasBackendProfile` trait to your `App\Models\User` automatically (skipped if the model already uses `HasRoles`, which would conflict).
11. Create a first **root** user via interactive prompts.

Every step is **idempotent** — re-running `arkhe:main:install` after an upgrade is safe and is the recommended way to pick up new install-time integrations (see [Upgrading](#upgrading)).

Creating users from the CLI
---------------------------

[](#creating-users-from-the-cli)

Once installed, add new users without leaving the terminal:

```
# fully interactive
php artisan arkhe:main:add-user

# one-liner with explicit options
php artisan arkhe:main:add-user \
    --email=ops@example.com \
    --first=Ops \
    --last=Team \
    --role=administrateur \
    --password=...
```

The command lets you pick the role from a prompt populated by the `roles` table. CLI calls bypass the runtime role-hierarchy check (the assumption being that anyone with shell access already has full authority), so you can seed a `root` user from a deploy script without auth context.

Configuration
-------------

[](#configuration)

`.env`:

```
ARKHE_ROUTE_PREFIX=administration
ARKHE_AVATAR_DISK=public
ARKHE_AVATAR_PATH=avatars
```

The full configuration lives in `config/arkhe.php` after publishing.

Wiring up your User model
-------------------------

[](#wiring-up-your-user-model)

Add the `HasBackendProfile` trait to your `User` model.

```
use Arkhe\Main\Concerns\HasBackendProfile;

class User extends Authenticatable
{
    use HasBackendProfile; // ⚠ already pulls in Spatie's HasRoles — do NOT add `use HasRoles;` separately.
}
```

The trait adds three accessors (`full_name`, `avatar_url`, `initials`) and two helpers (`isArkheRoot()`, `isArkheAdmin()`).

Accessing the backend
---------------------

[](#accessing-the-backend)

By default: `GET /administration/users` (the prefix is configurable).

Access is granted to users carrying either the `root` or `administrateur` role; everyone else gets a `403` via the `arkhe.backend` middleware.

Coexisting with a custom admin (Livewire starter kit)
-----------------------------------------------------

[](#coexisting-with-a-custom-admin-livewire-starter-kit)

Arkhe is designed to **plug into** your existing admin shell rather than replace it. Two integration points:

### 1. Use your app's layout

[](#1-use-your-apps-layout)

In `config/arkhe.php`:

```
'layout' => 'components.layouts.app', // your starter kit layout
```

Arkhe pages will render inside your existing chrome (sidebar, topbar, your CSS).

### 2. Take over the dashboard route (opt-in)

[](#2-take-over-the-dashboard-route-opt-in)

Comment out the dashboard route in your `routes/web.php` and set the path:

```
ARKHE_DASHBOARD_ROUTE=administration/dashboard
```

Arkhe mounts a minimal users-by-role dashboard at the path you choose. Keep the env var unset and your own dashboard remains untouched.

**Login redirect.** The Laravel starter kits redirect to `route('dashboard', absolute: false)` after authentication. Two ways to point that at Arkhe:

- **A — re-use the `dashboard` route name** (zero patch on your starter):

    ```
    ARKHE_DASHBOARD_ROUTE=administration/dashboard
    ARKHE_DASHBOARD_ROUTE_NAME=dashboard
    ```

    `route('dashboard')` now resolves to `/administration/dashboard`, the after-login redirect just works.
- **B — keep `arkhe.dashboard` and patch the starter's login**: open the login Livewire/Volt component and replace `route('dashboard', absolute: false)` with `route('arkhe.dashboard', absolute: false)`. Pick this when another part of your app still needs `dashboard` to mean something else.

> **Fortify users** (Laravel 12 Livewire starter kit included): the starter's login form posts to Fortify, which redirects to the literal value of `config('fortify.home')` after auth — not via the named `dashboard` route. Arkhe detects Fortify automatically and rewrites that value to your `ARKHE_DASHBOARD_ROUTE` at boot, so neither A nor B is needed for the form submission to land on the right page. Set `ARKHE_OVERRIDE_FORTIFY_REDIRECT=false` to opt out.

### 3. Inject Arkhe entries into your sidebar

[](#3-inject-arkhe-entries-into-your-sidebar)

Include the bundled partial at the top level of your `` — it emits its own Dashboard item plus the registry-driven groups ("Accès", "Réglages", and any group contributed by a satellite package), so it sits alongside your own groups rather than nested inside one:

```

        {{-- your custom admin links --}}

            Projects

    {{-- Arkhe + satellite-package entries --}}
    @include('arkhe::partials.sidebar-items')

```

You can also publish the partial to customise it:

```
php artisan vendor:publish --tag=arkhe-views
# then edit resources/views/vendor/arkhe/partials/sidebar-items.blade.php
```

### 4. Branch a package onto the shared menu — `ArkheNav`

[](#4-branch-a-package-onto-the-shared-menu--arkhenav)

The sidebar is driven by a navigation registry (`Arkhe\Main\Support\ArkheNav`). Arkhè seeds two sections — `access` ("Accès") and `settings` ("Réglages") — and any package can branch onto the same menu from its service provider's `boot()`, with **no Blade patching**. This is how `adhocrat-io/arkhe-watcher` and future packages plug in.

Add an entry to the shared **Réglages** section (one line per package — the goal: every package's settings in one place):

```
use Arkhe\Main\Support\ArkheNav;

ArkheNav::section('settings')->item(
    key:    'billing',
    label:  fn () => __('billing::nav.title'),   // closure → resolved at render (locale-aware)
    icon:   'credit-card',
    route:  'billing.settings',
    active: 'billing.settings*',                 // routeIs pattern(s) for the "current" highlight
    can:    'manage-billing',                    // permission string, closure(?$user): bool, or null
    priority: 50,                                // ordering within the section
);
```

Or declare your **own collapsible group** (for a richer tool with several pages):

```
ArkheNav::section('reports', heading: fn () => __('reports::nav.title'), priority: 90, can: 'view-reports')
    ->item('sales',  fn () => __('reports::nav.sales'),  'chart-bar',  route: 'reports.sales')
    ->item('export', fn () => __('reports::nav.export'), 'arrow-down-tray', route: 'reports.export');
```

A section is rendered only when its gate passes **and** it has at least one visible item; items are filtered per-user by their own `can`. Sections and items order by `priority` (lower first). Because registration is keyed and idempotent, Main and packages can register in any order.

> **A menu `can` only hides the link — it is not access control.** It governs *visibility* in the sidebar, nothing more. You must still protect the destination yourself by gating the package's routes with middleware (`arkhe.backend`, `arkhe.root`, or your own). Treat the menu gate and the route guard as two independent layers and keep them in sync. For example, `adhocrat-io/arkhe-watcher` gates its routes with `arkhe.watcher` (and `arkhe.root` for its settings page) in addition to the matching menu `can`.

Role hierarchy &amp; authorization
----------------------------------

[](#role-hierarchy--authorization)

The `Arkhe\Main\Support\RoleHierarchy` helper encodes a configurable role hierarchy. A user can only assign roles whose rank is less than or equal to their own. The default order, highest first, is:

```
root > administrateur > user > guest

```

This means: only `root` can assign `root`; an `administrateur` cannot promote anyone to `root`; and so on.

The hierarchy is enforced at three layers:

- the role `` only lists roles the acting user can assign;
- a closure rule on `UserForm` rejects any out-of-rank role at validation time;
- `UserService::syncRolesAndPermissions()` throws `AuthorizationException` as a backstop for direct service callers.

### Extending the hierarchy

[](#extending-the-hierarchy)

Two extension paths are available — pick the one that matches your situation.

#### Option A — Static, via `config/arkhe.php`

[](#option-a--static-via-configarkhephp)

Use this when the roles are known at deploy time and live with the application: the values you want are part of the codebase, not contributed by an external package.

The **order** of `config('arkhe.roles')` IS the hierarchy (first entry = highest rank). Insert your role at the right position between two existing entries:

```
// config/arkhe.php
'roles' => [
    'root'          => 'root',
    'administrator' => 'administrateur',
    'manager'       => 'manager',   // new role, ranks between admin and user
    'user'          => 'user',
    'guest'         => 'guest',
],
```

Then create the matching row in the `roles` table by re-running the bundled seeder:

```
php artisan arkhe:main:install   # answer No to publish + migrate, Yes is automatic on the seed step
# or, equivalently in a one-liner:
php artisan tinker --execute="app(\Arkhe\Main\Database\Seeders\ArkheRolesSeeder::class)->run();"
```

Pros: declarative, version-controlled, visible to every dev reading the config. Cons: requires editing the published file in each host app.

#### Option B — Runtime, via `RoleHierarchy::register()`

[](#option-b--runtime-via-rolehierarchyregister)

Use this when the role is contributed by a **package, module or feature flag** — i.e. you cannot (or do not want to) require the host app to edit `config/arkhe.php`.

From your package's service provider:

```
use Arkhe\Main\Support\RoleHierarchy;

public function boot(): void
{
    RoleHierarchy::register('manager', after: 'administrateur');
    RoleHierarchy::register('editor',  before: 'user');
    RoleHierarchy::register('intern');                 // append at the lowest rank
}
```

`register()` can also reposition an already-known role on subsequent calls. Your package is still responsible for creating the matching row in the `roles` table (typically via its own seeder).

Pros: zero config edit on the host side, perfect for distributed packages. Cons: invisible at first glance — document loudly which role(s) your package contributes.

#### Which one to pick?

[](#which-one-to-pick)

ScenarioRecommendedYou own the app code end-to-end and the role belongs there**A — config**The role ships in a Composer/Git submodule that you `require` from many apps**B — register()**You want a flag to enable/disable a role per environment**B — register()** inside an `if (config('feature.x'))`#### Contract

[](#contract)

The four canonical Arkhe keys — `root`, `administrator`, `user`, `guest` — must remain in `config('arkhe.roles')`. Internal Arkhe code references them directly (`config('arkhe.roles.root')`, …). You can:

- ✅ insert new roles between them,
- ✅ change the **value** (the actual role name stored in DB), e.g. `'user' => 'membre'`,
- ❌ rename or remove the four canonical **keys**.

Styling — Tailwind / Flux
-------------------------

[](#styling--tailwind--flux)

Tailwind only compiles classes it can **see**. Since this package's Blade files live in `vendor/adhocrat-io/arkhe-main/resources/views/`, they are not scanned by the default Laravel setup. The installer takes care of this for Tailwind v4 (step 8 above); the snippets below document the same thing for reference or for manual setups.

**Tailwind 4 (recommended, used by Flux 2):**

```
/* resources/css/app.css */
@import 'tailwindcss';
@import '../../vendor/livewire/flux/dist/flux.css';

@source '../views';
@source '../../app/Livewire';
@source '../../vendor/livewire/flux/stubs/**/*.blade.php';
@source '../../vendor/adhocrat-io/arkhe-main/resources/views';
```

**Tailwind 3:**

```
// tailwind.config.js
export default {
  content: [
    './resources/views/**/*.blade.php',
    './app/Livewire/**/*.php',
    './vendor/livewire/flux/stubs/**/*.blade.php',
    './vendor/adhocrat-io/arkhe-main/resources/views/**/*.blade.php',
  ],
  // ...
}
```

Then:

```
pnpm build   # or npm run build / yarn build
```

The layout published with the package uses `@fluxAppearance` and `@fluxScripts` from `livewire/flux` — make sure those directives are reachable (they ship with Flux automatically).

Architecture
------------

[](#architecture)

The package strictly follows the **Repository + Service** pattern:

```
Livewire Component
   ├──[read]──▶ UserRepository ──▶ Eloquent
   └──[mutate]─▶ UserService ──▶ UserRepository ──▶ Eloquent
                       └──▶ Events (UserCreated / UserUpdated / UserDeleted)

```

No Eloquent query happens outside `src/Repositories/`. No mutation happens outside `src/Services/`. When extending, follow the same rule — see `CONTRIBUTING` for details.

### Events

[](#events)

EventDispatched byPayload`Arkhe\Main\Events\UserCreated``UserService::create()`The fresh `Model``Arkhe\Main\Events\UserUpdated``UserService::update()`The fresh `Model``Arkhe\Main\Events\UserDeleted``UserService::delete()`The deleted `Model`Translations
------------

[](#translations)

Locale by default: `fr` (with `en` fallback). Override per-app via:

```
php artisan vendor:publish --tag=arkhe-translations
```

SEO
---

[](#seo)

`adhocrat-io/arkhe-main` ships [`ralphjsmit/laravel-seo`](https://github.com/ralphjsmit/laravel-seo) as a first-class dependency since 3.1.0. The `arkhe:main:install` command publishes the SEO package's migration and config, and the package's `seo()` helper is rendered in the Arkhe layout's ``.

### Site-wide defaults — `/administration/seo`

[](#site-wide-defaults--administrationseo)

A root-only Livewire page at `/administration/seo` (named `arkhe.site-seo.edit`) edits the site SEO defaults stored in the `arkhe_site_seo` table:

- Site name (used in OpenGraph tags)
- Title suffix (appended to every page ``, e.g. `| Acme`)
- Default description
- Default OG image
- Author
- Robots
- Twitter / X handle
- Favicon

These values are merged into the `SEOData` rendered on every page via a `SEOManager::SEODataTransformer` registered by `Arkhe\Main\ArkheMainServiceProvider::bootSeo()`. They serve as fallbacks: anything provided per-page or per-model (see below) wins.

### Per-model SEO — `HasArkheSeo` trait

[](#per-model-seo--hasarkheseo-trait)

Drop the trait on any Eloquent model to get per-record SEO storage:

```
use Arkhe\Main\Concerns\HasArkheSeo;

class Post extends Model
{
    use HasArkheSeo;
}
```

This creates a polymorphic `seo` row on every new `Post`, exposes a `$post->seo` relation, and lets you render:

```
{!! seo($post) !!}
```

You can also override SEO dynamically by implementing `getDynamicSEOData()` on your model (see the [upstream package docs](https://github.com/ralphjsmit/laravel-seo) for the full API).

The merge order (highest priority first):

1. `getDynamicSEOData()` overrides on the resolved model
2. The polymorphic `seo` row (`$model->seo`)
3. Arkhe site defaults from `/administration/seo`
4. `config('seo.php')` (the upstream package's static defaults)

### Disabling the integration

[](#disabling-the-integration)

Set `arkhe.features.seo` to `false` in your `config/arkhe.php` to skip the SEOData transformer registration. The `seo()` helper still works (the upstream package is always loaded), it just won't pick up Arkhe's site defaults.

Sitemap
-------

[](#sitemap)

Arkhe ships [`spatie/laravel-sitemap`](https://github.com/spatie/laravel-sitemap) since 3.1.0. The package registers a scheduled `GenerateSitemap` job and exposes a root-only admin page at `/administration/sitemap` (route `arkhe.sitemap.edit`) to inspect status and trigger a regeneration on demand.

### Configuration

[](#configuration-1)

`config/arkhe.php`:

```
'sitemap' => [
    'enabled'  => env('ARKHE_SITEMAP_ENABLED', true),
    'url'      => env('ARKHE_SITEMAP_URL'),      // null → falls back to config('app.url')
    'path'     => env('ARKHE_SITEMAP_PATH'),     // null → falls back to public_path('sitemap.xml')
    'schedule' => env('ARKHE_SITEMAP_SCHEDULE', '0 3 * * *'),
],
```

The cron expression is registered with `callAfterResolving(Schedule::class, …)` so it lights up as soon as the host app's scheduler runs. To skip the automatic scheduling without losing the admin button, set `ARKHE_SITEMAP_ENABLED=false`.

### Running the job manually

[](#running-the-job-manually)

```
php artisan queue:work          # if the queue isn't already running
# then click "Regenerate now" on /administration/sitemap
```

The `Regenerate now` button dispatches `Arkhe\Main\Jobs\GenerateSitemap` onto the host app's default queue. With the `sync` driver, the regeneration runs inline — same code path, no scheduler dependency.

### Customising the generator

[](#customising-the-generator)

Subclass `Arkhe\Main\Services\SitemapService` and override `configureGenerator(SitemapGenerator $generator): void` to add URLs, swap the crawl profile, or filter pages. Then point the binding at your subclass:

```
// AppServiceProvider::register
$this->app->bind(\Arkhe\Main\Services\SitemapService::class, \App\Services\MySitemapService::class);
```

For per-model integration, implement Spatie's `Sitemapable` contract on any Eloquent model — see the [upstream docs](https://github.com/spatie/laravel-sitemap).

Cookies &amp; GDPR
------------------

[](#cookies--gdpr)

Arkhe ships [`whitecube/laravel-cookie-consent`](https://github.com/whitecube/laravel-cookie-consent) since 3.1.0. The package's `@cookieconsentscripts` and `@cookieconsentview` blade directives are rendered in the Arkhe layout, gated by `Features::hasCookieConsent()` (defaults to `true`).

### Out-of-the-box behaviour

[](#out-of-the-box-behaviour)

`Arkhe\Main\Cookies\ArkheCookiesServiceProvider` is registered automatically and declares Laravel's session + CSRF cookies under the **essentials** category. As soon as you install the package, the consent banner appears with a GDPR-compliant baseline — no extra setup required.

### Registering app-specific cookies

[](#registering-app-specific-cookies)

For cookies your app sets beyond the essentials (analytics, optional features, …), publish the upstream stub provider and register your own cookies in it:

```
php artisan vendor:publish --tag=laravel-cookie-consent-service-provider
php artisan vendor:publish --tag=laravel-cookie-consent-config
```

Then add `App\Providers\CookiesServiceProvider::class` to `bootstrap/providers.php` and edit it as documented [upstream](https://github.com/whitecube/laravel-cookie-consent#registering-cookies):

```
protected function registerCookies(): void
{
    Cookies::analytics()->google(id: config('services.google_analytics.id'));
    Cookies::optional()->name('darkmode')->duration(120);
}
```

Both providers coexist — Arkhe registers essentials, your provider adds the rest.

### Audit page — `/administration/cookies`

[](#audit-page--administrationcookies)

A root-only read-only Livewire page at `/administration/cookies` (route `arkhe.cookies.index`) lists every category and cookie currently registered through both Arkhe and any consumer-side providers. Use it as a GDPR audit trail.

### Disabling the integration

[](#disabling-the-integration-1)

Set `arkhe.features.cookie_consent` to `false` in `config/arkhe.php` to remove the banner directives from the Arkhe layout and skip registering Arkhe's essentials. The upstream package remains installed (`Cookies::hasConsentFor(...)` keeps working) — only the banner is silenced.

Feature flags
-------------

[](#feature-flags)

Both SEO and cookie consent became first-class features in 3.1.0 and default to **on**. The flags remain as escape hatches for consumers that want to keep the dependencies installed but silence the integration:

```
// config/arkhe.php
'features' => [
    'seo'            => true, // SEOData transformer, /administration/seo
    'cookie_consent' => true, // Banner directives, /administration/cookies
],
```

Read them programmatically via `\Arkhe\Main\Support\Features::hasSeo()` / `hasCookieConsent()`.

Extension points at a glance
----------------------------

[](#extension-points-at-a-glance)

Seven layered ways to customise Arkhe without forking it — pick the lightest one that fits:

\#LeverUse when1**Events** — `UserCreated`, `UserUpdated`, `UserDeleted` (see [Events](#events))You need a side-effect (newsletter sync, audit log, webhook) that does NOT need access to the Livewire component state.2**Lifecycle hooks** on the Livewire pages — `beforeSave(array): array`, `afterCreate(Model, array)`, `afterUpdate(Model, array)`, `beforeDelete(Model)`The side-effect needs UI context — form payload, flash messages, redirects. Override in a subclass (see lever 3).3**Rebindable Livewire components** via `config('arkhe.components')`You want to subclass `ListUsers` / `ListRoles` / `ListPermissions` / `Dashboard` / `SiteSeo` / `Sitemap` / `Cookies` to add `wire:click` targets or extra fields. The route map auto-resolves to your class.4**`RoleHierarchy::register()`** (runtime) or `config('arkhe.roles')` (static)You ship a new role from a package or a host module — see [Role hierarchy](#role-hierarchy--authorization).5**Custom permissions** via `config('arkhe.permissions')` + `config('arkhe.role_permissions')`, re-seed with `ArkheRolesSeeder`You add domain permissions (`manage-posts`, `publish-article`, …) that should live next to Arkhe's bundled set.6**`ArkheNav` navigation registry** — add an item to the shared `settings` section or declare your own group (see [Branch a package onto the shared menu](#4-branch-a-package-onto-the-shared-menu--arkhenav))A package needs to contribute sidebar entries that show up in the common backend menu, gated by permission, with no Blade patching.7**Publish the views** (`vendor:publish --tag=arkhe-views`)The hooks / subclasses are not enough — you need a different Blade structure. Last resort; you take ownership of upgrade diffs.Subclass override example (lever 3 + lever 2):

```
// config/arkhe.php
'components' => [
    'list-users' => App\Livewire\Admin\Users\AppListUsers::class,
],

// app/Livewire/Admin/Users/AppListUsers.php
class AppListUsers extends \Arkhe\Main\Livewire\ListUsers
{
    protected function afterCreate(Model $user, array $payload): void
    {
        app(NewsletterService::class)->subscribe($user, 'admin');
    }

    public function resetPassword(int $id): void
    {
        // extra wire:click target — works because the route already resolves to this class
    }
}
```

No route changes needed in the host app.

Limitations
-----------

[](#limitations)

Things that may surprise you. None are blockers — most are deliberate trade-offs to keep the installer non-destructive.

AreaBehaviour**Layout default**`config('arkhe.admin.layout')` defaults to `layouts::app` — a Livewire 4 convention served by the Livewire/Volt and Flux starter kits. On a bare app, set `ARKHE_ADMIN_LAYOUT=arkhe::layouts.app` to fall back on the package's bundled header-only layout, or point it at any view of your own.**Sidebar patch**Step 8 of `arkhe:main:install` only patches a file matching `*sidebar*.blade.php` that contains ``. No match → silently skipped (the bundled layout uses a `` dropdown, so a sidebar is not strictly required). If your app has multiple sidebar candidates, the installer refuses to choose and you must `@include('arkhe::partials.sidebar-items')` manually.**Tailwind v3**Step 9 only auto-patches Tailwind v4 (`@import "tailwindcss"` in `resources/css/app.css`). Tailwind v3 setups get a printed snippet for `tailwind.config.js` — patching JS would be too brittle.**User model patch**Step 10 refuses to inject `HasBackendProfile` if the model already imports `Spatie\Permission\Traits\HasRoles` (it would conflict — `HasBackendProfile` already wraps `HasRoles`). Remove the explicit `use HasRoles;` first, or add `use HasBackendProfile;` by hand.**`/administration/dashboard`**Not registered by default — set `ARKHE_DASHBOARD_ROUTE=administration/dashboard` to opt in. Useful when you want Arkhe's users-by-role widget to replace the starter kit's empty `/dashboard`.**Layout chrome**The bundled `arkhe::layouts.app` ships with a Flux header (brand + profile dropdown) but no sidebar, navigation menu, or footer. It's deliberately minimal — to keep its real chrome, override the layout config.**Fortify redirect rewrite**When Fortify is detected and `arkhe.dashboard_route` is set, Arkhe rewrites `config('fortify.home')` at boot. Set `ARKHE_OVERRIDE_FORTIFY_REDIRECT=false` to opt out.**`spatie/laravel-permission` cache**The seeder calls `Permission::create()` directly. After re-running it (e.g. to add new permissions), clear the permission cache — `php artisan permission:cache-reset` — or restart your queue workers.**Sitemap on `sync` queue**The "Regenerate now" button dispatches `GenerateSitemap` onto the host app's default queue. With the `sync` driver it runs inline; with a real driver, make sure a worker is up — otherwise the page reports "queued" with no visible progress.Upgrading
---------

[](#upgrading)

### Between minor / patch versions

[](#between-minor--patch-versions)

```
composer update adhocrat-io/arkhe-main
php artisan arkhe:main:install   # re-run, answer "no" to steps already done
```

`arkhe:main:install` is idempotent on every step (publish, migrate, seed, sidebar patch, css patch, trait patch). Re-running it after upgrading is the canonical way to pick up new install-time integrations (e.g. a new `@source` to add to `app.css`, a new sidebar entry to inject).

If you'd rather skip the prompts, the manual snippets in the [Styling](#styling--tailwind--flux) and [Wiring up your User model](#wiring-up-your-user-model) sections give you the exact lines to add.

### From V2 to V3

[](#from-v2-to-v3)

V3 keeps the V2 public surface — namespace `Arkhe\Main`, service provider, config prefix — so no global search-replace is required. A dedicated Artisan command handles the config migration:

```
composer update adhocrat-io/arkhe-main:^3.0
php artisan arkhe:main:upgrade-from-v2 --dry-run   # preview the changes
php artisan arkhe:main:upgrade-from-v2             # apply
```

What it does:

- Appends V3-only keys to your published `config/arkhe.php` (`dashboard_route`, `role_permissions`, `components`, `backend_permission`, `root_permission`, `features`) without touching existing V2 entries.
- Rewrites legacy Livewire aliases inside `resources/views/` (e.g. `arkhe.main.livewire.admin.users.users-list` → `arkhe.list-users`).
- Runs the V3 permission seeder so the new 16 default permissions and their role mappings land in your DB.

See [`CHANGELOG.md`](CHANGELOG.md) for the full breaking-change list and the V2 → V3 migration playbook.

Testing
-------

[](#testing)

```
composer install
vendor/bin/pest
```

CI runs the matrix PHP `8.3`/`8.4` × Laravel `12.*`/`13.*` × `prefer-lowest`/`prefer-stable`.

License
-------

[](#license)

[MIT](LICENSE) — Luc, adhocrat.io.

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance95

Actively maintained with recent releases

Popularity13

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~6 days

Total

12

Last Release

43d ago

Major Versions

1.x-dev → 2.0.02026-04-03

2.x-dev → v3.1.02026-06-05

PHP version history (2 changes)1.0.0PHP ^8.2

2.0.0PHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/7c598fadfd8d5661742bc2e67e8434dcaccf104bd279d9b6745cae5751f09041?d=identicon)[Adhocrat](/maintainers/Adhocrat)

---

Tags

laravelfluxlivewirerolespermissionsUsersadminarkhe

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/adhocrat-io-arkhe-main/health.svg)

```
[![Health](https://phpackages.com/badges/adhocrat-io-arkhe-main/health.svg)](https://phpackages.com/packages/adhocrat-io-arkhe-main)
```

###  Alternatives

[spatie/laravel-permission

Permission handling for Laravel 12 and up

12.9k102.4M1.5k](/packages/spatie-laravel-permission)[bezhansalleh/filament-shield

Filament support for `spatie/laravel-permission`.

2.8k3.9M138](/packages/bezhansalleh-filament-shield)[spatie/laravel-passkeys

Use passkeys in your Laravel app

472890.7k40](/packages/spatie-laravel-passkeys)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3914.8k](/packages/rawilk-profile-filament-plugin)[chiiya/filament-access-control

Admin user, role and permission management for Laravel Filament

21852.5k](/packages/chiiya-filament-access-control)[a2insights/filament-saas

Filament Saas for A2Insights

191.7k](/packages/a2insights-filament-saas)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
