Skip to main content

Routing

Sloth comes with two complementary routing mechanisms: the WordPress Template Hierarchy for standard content pages, and a custom router for anything that goes beyond what WordPress offers.

WordPress Template Hierarchy​

For most pages — posts, archives, taxonomies, custom post types — you don't need to define routes at all. Place a Twig file with the right name in theme/View/Layout/ and Sloth picks it up automatically via the WordPress template hierarchy.

theme/View/Layout/
├── index.twig # Fallback for everything
├── single.twig # Single posts
├── single-project.twig # Single post of type "project"
├── archive.twig # Archives
├── archive-project.twig # Archive for post type "project"
├── page.twig # Pages
├── page-about.twig # Page with slug "about"
├── taxonomy.twig # Taxonomy archives
├── search.twig # Search results
└── 404.twig # Not found

See the WordPress Template Hierarchy for the full list of template names.

Custom Routes​

When you need custom URLs or parameters that don't map to WordPress content, use Sloth's router.

Define routes in app/routes/web.php or theme/routes/web.php:

<?php

use Sloth\Facades\Response;
use Sloth\Facades\Route;

Route::get('/feed/custom', function () {
return Response::make(renderFeed(), 200)
->header('Content-Type', 'application/xml');
});

Both files are loaded if present — app/routes/web.php first, then theme/routes/web.php.

HTTP methods​

Route::get('/projects', fn() => Response::view('projects/index'));
Route::post('/contact', fn() => handleForm());
Route::put('/projects/{id}', fn(string $id) => update($id));
Route::delete('/projects/{id}', fn(string $id) => delete($id));

URL parameters​

Parameters in curly braces are passed to the callback in order:

Route::get('/projects/{slug}', function (string $slug) {
$project = Project::where('post_name', $slug)->firstOrFail();

return Response::view('projects/show', ['project' => $project]);
});

Named routes​

Assign a name by chaining ->name():

Route::get('/projects', fn() => Response::view('projects/index'))
->name('projects.index');

Route::get('/projects/{slug}', fn(string $slug) => showProject($slug))
->name('projects.show');

Dispatch​

Routes are matched and dispatched on the template_redirect hook at priority 1 — before WordPress loads its own templates. If a route matches, WordPress template loading is bypassed entirely.

Responses​

Route callbacks return a Response instance. Sloth's Response class extends Illuminate's with static factory methods:

use Sloth\Facades\Response;

// Render a Twig template
Route::get('/projects', function () {
return Response::view('Layout/projects', [
'projects' => Project::all(),
]);
});

// With status code and headers
Route::get('/feed', function () {
return Response::make(renderFeed(), 200)
->header('Content-Type', 'application/xml');
});

// JSON
Route::get('/api/projects', function () {
return Response::json(Project::all()->toArray());
});

Route::get('/api/projects/{id}', function (string $id) {
$project = Project::find($id);
if (!$project) {
return Response::json(['error' => 'Not found'], 404);
}
return Response::json($project->toArray());
});

// Redirect
Route::get('/old-url', function () {
return Response::redirect('/new-url'); // 302
return Response::redirect('/new-url', 301); // permanent
});

// File download
Route::get('/download', function () {
return Response::download('/path/to/file.pdf', 'report.pdf');
});

// Inline file (display in browser)
Route::get('/preview', function () {
return Response::file('/path/to/file.pdf');
});

// 204 No Content
Route::delete('/projects/{id}', function (string $id) {
Project::find($id)?->delete();
return Response::noContent();
});

URL Generation​

The URL facade and url() helper generate URLs for named routes and WordPress locations:

use Sloth\Facades\URL;

// Named routes
URL::route('projects.index'); // /projects
URL::route('projects.show', ['slug' => 'my-project']); // /projects/my-project

// WordPress URLs
URL::home(); // https://example.com
URL::to('/about'); // https://example.com/about
URL::theme(); // https://example.com/wp-content/themes/my-theme
URL::theme('css/app.css'); // https://example.com/.../my-theme/css/app.css
URL::asset('css/app.css'); // https://example.com/.../my-theme/public/css/app.css
URL::content(); // https://example.com/wp-content
URL::uploads(); // https://example.com/wp-content/uploads
URL::current(); // /current/path (no host)
URL::full(); // https://example.com/current/path

In Twig:

{{ url().route('projects.index') }}
{{ url().theme('css/app.css') }}
{{ url().asset('js/app.js') }}

Or via the global url() helper:

url('/about'); // https://example.com/about
url()->route('projects.show', [...]); // named route