Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Folio Laravel Package

laravel/folio

Laravel Folio is a powerful page-based router for Laravel applications. It simplifies routing by mapping Blade views to routes automatically, letting you build pages with a clean, file-based structure instead of manual route definitions.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and scaffold.

composer require laravel/folio
php artisan folio:install

The install command creates a resources/views/pages directory (and wires up whatever it needs). You then mount that directory onto a URI — either in routes/web.php or AppServiceProvider::boot():

use Laravel\Folio\Folio;

Folio::path(resource_path('views/pages'))->uri('/');

First page. Create resources/views/pages/index.blade.php:

<x-layouts.app>
    <h1>Hello from Folio</h1>
</x-layouts.app>

Hit / and you're done — no route file entry, no controller, no closure. Every .blade.php under pages/ becomes a GET route derived from its path.

Where to look first, in order:

  1. php artisan folio:list — the single most useful command. It prints URI, route name, and the backing file for every page Folio registered. Run it after every routing change.
  2. php artisan folio:page users/index (or php artisan list folio to confirm the exact command name on your version) — scaffolds a page file in the right place.
  3. The official docs at laravel.com/docs/folio — the README is intentionally a stub; the real documentation lives there.
  4. vendor/laravel/folio/src — Folio.php, PendingFolioRoute.php, and the page-resolution pipeline are small and readable when something doesn't match.

First real use case: a marketing site, docs site, or blog index. Folio is at its best when URLs are "one page per file" and mostly GET: /about, /pricing, /blog/[slug], /docs/[...path]. Do not migrate an existing CRUD-heavy app into Folio wholesale.

Implementation Patterns

Filename → URI mapping

File URI
pages/index.blade.php /
pages/about.blade.php /about
pages/blog/index.blade.php /blog
pages/blog/[Post].blade.php /blog/{post}
pages/users/[User]/edit.blade.php /users/{user}/edit
pages/docs/[...path].blade.php /docs/{path} (catch-all wildcard)

Model binding is driven by capitalization

[Post] (capitalized) triggers implicit route-model binding and type-hints the model in the render closure; [post] (lowercase) is a plain string parameter. Keep the convention consistent across a project or you will chase 404s.

Per-page configuration with the @php header

@php
    use function Laravel\Folio\{middleware, name, render};

    middleware(['auth', 'verified']);
    name('dashboard');
@endphp

<x-layouts.app>...</x-layouts.app>

middleware() and name() must be called inside the @php block. Named routes then work with route('dashboard'), and request()->routeIs('dashboard') works for nav highlighting.

render() for data and alternate views

Keep Blade files thin; push queries into the render callback, which receives the bound models after resolution:

@php
    use function Laravel\Folio\render;

    render(function (View $view, User $user) {
        return $view->with('posts', $user->posts()->latest()->paginate(10));
    });
@endphp

@foreach ($posts as $post)
    ...
@endforeach

Returning something other than the page view (a redirect, a different view) also works from here — useful for gating logic that isn't authorization.

Multiple mounts, with different middleware

Split concerns by directory instead of repeating middleware('auth') on every page:

Folio::path(resource_path('views/pages'))->uri('/');
Folio::path(resource_path('views/pages/admin'))->uri('/admin')->middleware(['auth', 'can:admin']);
Folio::path(resource_path('views/pages/tenant'))->domain('{tenant}.example.com')->uri('/');

Overlapping paths across domains had a silent route-dropping bug — fixed in v1.1.11. Pin at least that version if you use domains.

Mixing with conventional routing

Folio is GET-only. The everyday split:

  • Folio pages for all GET/display routes (dashboards, detail pages, static content).
  • routes/web.php / controllers for POST/PUT/PATCH/DELETE, webhooks, and anything needing a named controller action.
  • Livewire (or Volt) components embedded in Folio pages for interactive forms — @livewire('post-editor') inside the page, so submission happens over Livewire's endpoint, not a Folio route.

Layouts

Folio doesn't impose a layout system. Blade components (<x-layouts.app>) or classic @extends('layouts.app') both work, since the rendered page is just a view. Use whatever the rest of the app uses.

Testing

Folio pages are ordinary routes, so ordinary feature tests work:

$this->get('/users/1')->assertOk()->assertSee('Jane');
$this->get('/admin')->assertRedirect('/login');

Use $this->get('/dashboard')->assertSeeLivewire(...) where relevant, and withoutVite() in tests that render a layout referencing @vite.

Gotchas and Tips

GET only. There is no way to define a POST route as a Folio page. This is the first wall people hit. Reach for Livewire/Volt or a conventional controller route; don't fight the router.

Route caching is mandatory in production — and easy to forget. Folio pages are compiled into the route cache. New pages added in a deploy are invisible until php artisan route:cache reruns. If a route "doesn't exist" on staging but works locally, that is almost always the cause. Run php artisan optimize:clear while debugging; run route:cache in the deploy script.

Wildcards are greedy. A root-level [...slug].blade.php will shadow everything. Scope catch-alls into subdirectories (docs/[...path]) and confirm the final ordering with php artisan route:list — Folio registers concrete pages and wildcards in a defined order, and a stray catch-all silently wins.

Filesystem case-sensitivity. [user] and [User] differ semantically (string vs. model binding) but collide on Windows and default macOS volumes. Folio's own history includes Windows path fixes. Normalize on capitalized model parameters everywhere.

Naming collisions. Route names are auto-derived from the path, which can collide when you have both /blog and /blog/index. Set explicit name() on index/parent pages and treat folio:list as the source of truth. The route() helper's handling of base URI, domain, and query params was only fully fixed in the 1.0.0-beta.6 era — if you're on a pre-1.0 pin, upgrade.

Slugs beginning with "index". Fixed in v1.1.6; earlier versions mangled /index-of-something-style routes. Another reason to stay on a current 1.1.x.

Don't stack Folio and Volt @php headers in one file. Both parse a leading @php block for directives. If a page is a Volt component, let Volt own the header; if it's a Folio page, don't add Volt's use function lines into the same block.

Debugging workflow, in order: folio:list → route:list → check the @php block for a swallowed exception (a failing name()/middleware() call shows as a 404, not a 500) → check the route cache.

Extension points worth knowing:

  • Laravel\Folio\Events\ViewMatched — fires when a page is matched. Hook analytics, audit logging, or per-page policy checks without touching page files.
  • The page-resolution Pipeline is resolvable from the container (since v1.1.3) — you can prepend/append steps to customize how pages are discovered and rendered.
  • Folio::path(...)->middleware(...) — mount-level middleware beats per-page repetition, and terminable middleware is supported.
  • php artisan folio:list — also useful as a CI assertion that a page you expect actually registered.

Version floor. v1.1.19 fixed route caching breaking Folio and v1.1.11 fixed domain/overlap route loss; 1.1.14+ adds Laravel 13 compatibility, and 1.1.12/1.1.16 cover PHP 8.5 and Symfony 8. There's no 2.x yet — treat 1.1.x as the line and pin ^1.1.19 or newer.

Team-fit note. Folio's benefit is proportional to how "page-shaped" your URLs are. On a content site it removes a whole layer of boilerplate; on a resource-heavy CRUD app it mostly relocates routing into filenames and makes route discovery harder for people who don't know the convention. Run folio:list in onboarding docs, or new hires will be grepping routes/web.php for routes that don't exist there.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
vento/laravel-airbrake
wfeller/laravel-batch
maco-studios/mage-os-installer
busarm/armie
siberfx/laravel-logger
kfteam1991/dcat-admin
dashed/dashed-laposta
dashed/dashed-ecommerce-montaportal
dashed/dashed-ecommerce-etsy
neohq/string-blade-compiler
log1x/acf-composer
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager