Skip to content
Docent Docs

Writing Docs

Access Control

Gate whole pages with authorize and audience. Denied viewers never see the page, the nav entry, or the search result.

Ability blocks hide parts of a page. To gate an entire page, use the authorize or audience front matter keys. Authorization in Docent isn't a rendering detail; it's enforced at every surface at once.

Gating a page

Add authorize with a gate ability:

yaml
---
title: Reports
description: Financial reporting for account admins.
authorize: reports.view
---

Or gate on a named audience for rules that are more than a single ability:

yaml
---
title: Admin Runbook
audience: billing-admin
---

A page can use either key. The viewer must pass it to open the page.

What "denied" means

When a viewer fails a page's authorize or audience check, the page isn't the only thing hidden:

  • The page itself returns the configured denied response.

  • The navigation omits the entry, so an unauthorized reader never sees it in the sidebar.

  • Search filters it out server-side, so it can never surface in results.

  • Table-of-contents entries for headings inside conditional blocks only appear for viewers who would see them.

Search runs the same authorization as page rendering, and conditional block content is never indexed, so a snippet can never leak gated text.

Hiding a gated page everywhere leaves one gap: a link to it from a page the reader can see. Nothing about that link is broken, so the broken-link check passes, and you won't notice it yourself because you can open both pages. The only signal is a reader with a narrower role reporting a link that goes nowhere.

The worst version is a deliberately ungated page explaining roles and permissions, where the readers who most need it are exactly the ones a gate would turn away. A link from there to a gated page hands a 404 to the audience least equipped to interpret it.

Turn on the gated-link rule to catch this:

php
// config/docent.php
'check' => [
    'rules' => ['gated-link' => 'warning'],
],

Each link carries the requirements its readers provably satisfy: the page's own authorize and audience, plus anything an enclosing :::can or :::audience block adds. Whatever the target needs beyond that gets reported. Docent won't guess that holding one of your abilities implies another, so it only ever asks whether a requirement is present.

That makes the escape hatch a statement rather than a trick. When the link is deliberate, name the target's own requirement and the rule believes you:

markdown
:::can ability="billing.manage"
Configure it in the [billing guide](billing).
:::

A block naming some other ability isn't a guarantee about this one, so it still reports. :::cannot widens rather than narrows, and :::when gates on a condition rather than authorization, so neither counts either.

The denied response

What a denied viewer receives is set once in config:

php
'authorization' => [
    'denied_response' => 404,
],
  • 404 (the default): hide the page's existence entirely.

  • 403: acknowledge the page but forbid it.

  • 'redirect:/login': send the viewer somewhere else.

404 is the safest default. A denied viewer can't tell the difference between a page that doesn't exist and one they're not allowed to see.

Fully private documentation

Per-page gates control who sees which page. To require authentication for the whole site, so even the existence of /docs is behind login, add auth to the route middleware in your site's config entry:

php
// inside sites.docs
'route' => [
    'prefix' => 'docs',
    'middleware' => ['web', 'auth'],
],

Combine the two: route middleware puts the entire site behind your guard, and per-page authorize and audience rules decide who sees what once they're in. See configuration for the route options.

Because middleware belongs to a site, privacy is a per-site decision. A public help center and a fully private internal site can run side by side in one install, each with its own middleware and its own admin gate. See multiple sites.

Letting one page out

A guarded site raises a question the guard can't answer on its own: what do you do when someone with no account needs to read a single page? A support recipient who isn't signed in, a sales lead who never will be.

Share links cover that case. A viewer you trust copies a link to one page, and it opens for whoever holds it, without opening the rest of the site. Authorization on a shared page resolves as it would for a logged-out visitor, and none of your registered values, links, components, or conditions run at all, so nothing about the person who shared it travels with the link.

Validate all of this before it ships. Head to validation.