Getting Started
Multiple sites
Run separate documentation sites, each with its own content, routes, branding, and access rules, from one install.
One Docent install can serve any number of documentation sites. The classic
split is a public help center at /help next to internal admin docs at
/admin/docs, each with its own content directory, middleware, branding, and
admin panel. Every install already uses this machinery: the shipped config
defines a single site keyed docs, and until you add a second entry you never
notice it.
A site is one entry in the sites array of config/docent.php. The key is
yours to choose (letters, numbers, underscores, and hyphens), and it shows up
in route names, cache keys, and upload paths, so pick something you're happy
to see in a URL helper.
Adding a second site
'default' => 'public',
'sites' => [
'public' => [
'name' => 'Help Center',
'route' => ['prefix' => 'help', 'middleware' => ['web']],
'filesystem' => ['path' => resource_path('docs-public')],
],
'admin' => [
'name' => 'Admin Docs',
'route' => ['prefix' => 'admin/docs', 'middleware' => ['web', 'auth']],
'filesystem' => ['path' => resource_path('docs-admin')],
'admin' => ['enabled' => true, 'gate' => 'manageAdminDocs'],
'theme' => ['accent' => '#e11d48'],
],
],
Two rules apply to every site beyond the shipped docs entry. It must set
filesystem.path, because only docs gets the resource_path('docs')
fallback. And it needs its own route.prefix or route.domain;
docent:check warns when two sites would collide.
Each site owns its identity sections: name, description, route,
filesystem, admin, navigation, and layouts. Everything else (theme,
search, ai, insights, content, database, cache, authorization,
widget) is inherited from the top level and overridable per site. The
configuration page covers the cascade in detail.
Separate audiences, separate access
Access is decided per site twice over. The site's route.middleware gates the
whole surface, so the admin site above requires a login before Docent renders
anything, while public stays open. On top of that, each site's admin panel
has its own gate, so the people who edit your help center and the people who
edit internal runbooks can be entirely different groups.
Everything permission-aware stays inside its site. Search results, navigation,
llms.txt feeds, Assistant answers, and widget suggestions all draw from the
current site's corpus and nothing else. A page about internal tooling on the
admin site cannot leak into a public search, even when both sites share a
database.
What isolation looks like in practice
Database-backed pages, Assistant question logs, and insight events share
tables, scoped by a site column. Page identity is (site, slug), so
billing/refunds can exist once on each site without conflict.
Admin image uploads are stored under docent/{site}/ on the site's configured
disk, and each site's _uploads route serves only its own directory. Two
sites can share the default public disk without a private site's images
becoming reachable through the public one.
Caches are namespaced per site, and console commands follow suit:
php artisan docent:clear # every site
php artisan docent:clear --site=public # just one
php artisan docent:check --site=admin
Route names
Every route name includes its site key: docent.public.home,
docent.admin.show, and so on for the routes of any site, including the
shipped docs site. Use the keyed name anywhere your application links into
documentation:
route('docent.public.home');
route('docent.admin.show', ['slug' => 'internal/runbook']);
Integrations per site
Registrations on the Docent facade apply to every site. Register the same
identifier through Docent::site() to override it for one:
Docent::value('support.email', fn () => 'help@example.com');
Docent::site('admin')->value('support.email', fn () => 'ops@example.com');
Every closure also receives the current site on its context, so one global registration can branch instead:
Docent::value('support.portal', fn ($context) => match ($context->site?->key) {
'admin' => route('admin.support'),
default => route('support'),
});
See dynamic content for the full integration reference.
Targeting a site from the widget
The in-app help widget takes a site attribute, and different widgets on the
same host page can target different sites:
<x-docent::widget site="public" />
Omit the attribute and the widget uses your default site.
One rule for early-running code
Docent selects the current site when the request's route is matched. Inside a
Docent request you can inject DocentManager or any other Docent service and
get the right site. Code that runs before routing, such as a global middleware,
should call Docent::site('public') instead, because an injection that early
holds the default site's instance.