An in-app documentation browser for Laravel. It renders the Markdown files in your project's docs/ directory as a documentation section inside the app: a grouped, ordered sidebar built from the directory structure, article-scale typography, sanitized CommonMark rendering, and internal .md cross-links rewritten into viewer URLs.
It runs in two modes, from the same code:
- Standalone — a route in any Laravel app (Livewire + Flux, Blade, anything). No Filament required.
- Filament — a page registered on a v5 panel with a one-line plugin.
The article typography ships as a Tailwind v4 source partial (resources/css/docs.css) that you import from vendor/ into whichever stylesheet renders the browser.
The CSS ships as source, not a compiled stylesheet: it uses
@variantand the--spacing()/--alpha()functions, so it must compile inside your app's build to stay consistent with your theme in light and dark mode.
It also ships a Laravel Boost skill, docs-authoring, which teaches AI agents to find undocumented functionality and write new docs that follow this package's conventions.
- PHP 8.4+
- Laravel 12 or 13
- Tailwind CSS v4 (for the article typography partial)
- Filament v5 — optional, only for the panel page
The package is installed from a Git repository:
{
"repositories": [
{ "type": "vcs", "url": "git@github.com:vmeretail/product-documentation.git" }
],
"require": {
"vmeretail/product-documentation": "^0.2"
}
}composer require vmeretail/product-documentation
php artisan vendor:publish --tag=product-documentation-configOut of the box the package registers a route at /docs, behind the web and auth middleware. Everything about it is configurable:
// config/product-documentation.php
'title' => 'Documentation',
'layout' => 'components.layouts.app',
'route' => [
'enabled' => true,
'prefix' => 'docs',
'name' => 'product-documentation.',
'middleware' => ['web', 'auth'],
'domain' => null,
],Layout. The browser renders inside the Blade layout component named by layout, passed as $slot. The default matches the Laravel starter kits (including Livewire + Flux). If your app has no components.layouts.app, point it at the bare layout that ships here — product-documentation::layouts.app — and set vite_entry to your Vite entry point so it picks up your compiled CSS.
Registering the route yourself. Set route.enabled to false and call ProductDocumentation::routes() from your own routes file, inside whatever group you want:
use VmeRetail\ProductDocumentation\ProductDocumentation;
Route::middleware(['web', 'auth', 'can:view-docs'])->group(function () {
ProductDocumentation::routes();
});Stylesheet. Import the partial into resources/css/app.css:
@import '../../vendor/vmeretail/product-documentation/resources/css/docs.css';
/* Scan the package's Blade views for utility classes. */
@source '../../vendor/vmeretail/product-documentation/resources/views/**';Then npm run build.
use VmeRetail\ProductDocumentation\Filament\ProductDocumentationPlugin;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugin(ProductDocumentationPlugin::make());
}The page appears in the panel navigation as "Documentation" under a "Help" group, at /{panel}/documentation/{doc?}.
If the panel is the only place the docs should be reachable, turn the standalone route off so the app does not also serve /docs:
// config/product-documentation.php
'route' => ['enabled' => false, /* ... */],Import the partial into your panel theme (the file passed to ->viteTheme(...)) instead of app.css:
@import '../../../../vendor/filament/filament/resources/css/theme.css';
@import '../../../../vendor/vmeretail/product-documentation/resources/css/docs.css';
@source '../../../../vendor/vmeretail/product-documentation/resources/views/**';Create a docs/ directory at your project root, or set path in the config file. The conventions:
- Two levels: group directories at the root, Markdown files inside them. Loose root files and deeper nesting are ignored.
NN-prefixes on directories and files control ordering and are stripped from slugs and labels:04-reports/02-scores.md→ URL/docs/reports/scores, sidebar group "Reports".- No frontmatter: the article title is the file's first
# Heading(humanised filename as fallback), and the group label is the humanised directory name. - Cross-references are ordinary relative Markdown links to the real, prefixed filenames —
[Key concepts](02-key-concepts.md),[Roles](../05-people/01-users-and-roles.md)— and are rewritten into viewer URLs at render time. External links, anchors, and links to unknown files are left untouched.
docs/
├── 01-getting-started/
│ ├── 01-overview.md
│ └── 02-key-concepts.md
└── 02-reports/
└── 01-understanding-a-report.md
If the consuming project uses Laravel Boost, running php artisan boost:install (or boost:update) installs this package's docs-authoring agent skill. The skill instructs AI agents to inventory the app's user-facing functionality, compare it against the existing docs, and write any missing articles following the conventions above.
- Rendering uses
Str::markdown()(league/commonmark) followed by asymfony/html-sanitizerpass configured for what CommonMark emits — raw HTML in Markdown is escaped, not executed. - Tables are wrapped in a scroll container (
.docs-table-wrap) by the renderer, and styled by the CSS partial; the two ship together. - Route input is matched against the known page set before touching the filesystem, so arbitrary
{doc}segments cannot reach disk. An unknown slug falls back to the first article rather than 404ing. - The sidebar's mobile toggle uses Alpine. In apps without Alpine (the bare fallback layout, for instance) the sidebar is simply always visible.
- Link colour resolves against whichever theme compiles the partial:
--color-primary-*(Filament), then--color-accent(Flux), then plain Tailwind blue. - There is no per-article authorization; whoever can reach the route can read every article.
composer testFilament is a dev dependency, so running the suite needs access to the private packages.filamentphp.com repository declared in composer.json. Consumers who do not use Filament never need it.
MIT — see LICENSE.