WordPress Routes
The framework implements a hybrid routing system that combines Laravel’s powerful routing capabilities with WordPress’s template hierarchy. This system follows a clear priority order:
-
Laravel Routes First: All routes defined in your
routes/web.phpfile take precedence. This gives you full control over specific URLs and allows you to leverage Laravel’s routing features when needed. -
WordPress Template Hierarchy as Fallback: If no Laravel route matches the current URL, the system falls back to WordPress’s template hierarchy system, automatically mapping URLs to corresponding Blade views.
This “Laravel-first, WordPress-fallback” approach provides the best of both worlds:
- Full control and flexibility of Laravel routing when you need it
- Automatic handling of standard WordPress content through the template hierarchy
- No need to define routes for every WordPress page or post
Incoming Request │ ▼┌──────────────┐│ WordPress │ Yes ┌─────────────────┐│ Admin Area? ├──────────►│ Handle via │└──────┬───────┘ │ Admin Route │ │ No └─────────────────┘ ▼┌──────────────┐│ Special WP │ Yes ┌─────────────────┐│ Request? ├──────────►│ Handle via │└──────┬───────┘ │ Special Route │ │ No └─────────────────┘ ▼┌──────────────┐│ Match Laravel│ Yes ┌─────────────────┐│ Route? ├──────────►│ Execute Laravel │└──────┬───────┘ │ Route │ │ └─────────────────┘ │ No ▼┌──────────────┐│ Match │ Yes ┌─────────────────┐│ WordPress ├──────────►│ Render Template ││ Template? │ └─────────────────┘└──────┬───────┘ │ No ▼┌──────────────┐│ Fallback to ││ Frontend ││ Controller │└──────────────┘Laravel Routing Basics
Section titled “Laravel Routing Basics”In Laravel, routes are defined in the routes/web.php file. A route associates a URL with a controller action or a closure that returns a view. For example:
use App\Http\Controllers\HomeController;
Route::get('/', [HomeController::class, 'index']);Here, when the URL /home is accessed, the index method of the HomeController is invoked.
Route Groups and Middleware
Section titled “Route Groups and Middleware”You can group routes to apply shared attributes, such as middleware, prefixes, or namespaces. Middleware can help perform actions before or after a request reaches the controller. For example:
// Group routes with middleware and prefixRoute::middleware('auth')->prefix('admin')->group(function () { Route::get('dashboard', 'AdminController@dashboard'); Route::get('settings', 'AdminController@settings');});Route Alias (Naming)
Section titled “Route Alias (Naming)”Route naming, or aliasing, allows you to refer to routes by a name instead of their URL. This is particularly useful when generating URLs or redirecting. Here’s how you can name a route:
// Named route for the home pageRoute::get('/', [HomeController::class, 'index'])->name('home');You can find more about Laravel’s routes in the official documentation.
WordPress-Style Route Declarations
Section titled “WordPress-Style Route Declarations”Pollora Framework provides WordPress-specific routes that integrate with WordPress’s conditional tags system.
WordPress Routes vs Standard Routes
Section titled “WordPress Routes vs Standard Routes”There are two main methods to define routes in your application:
-
Standard Laravel Routes (
Route::get(),Route::post(),Route::any(), etc.):- These routes follow Laravel’s standard routing behavior
- They do not interact with WordPress’s conditional tags system
- They do not automatically receive WordPress-specific middleware
-
WordPress-Specific Routes (
Route::wp()andRoute::wpMatch()):- These routes integrate with WordPress’s conditional tags system
- They automatically receive WordPress-specific middleware:
WordPressBindings: Adds WordPress objects (post, query) to the routeWordPressHeaders: Manages HTTP headers for WordPress responsesWordPressBodyClass: Handles body classes for WordPress templates
- They are processed through WordPress’s conditional logic
Route::wp()accepts all HTTP verbsRoute::wpMatch()allows specifying specific HTTP verbs
WordPress Route Conditions
Section titled “WordPress Route Conditions”WordPress routes use conditions that can be specified in two ways:
- Using predefined aliases (shorter syntax)
- Using actual WordPress conditional function names (more explicit)
The framework provides a default set of aliases in the config/wordpress.php file. These aliases map WordPress conditional tags to route URIs:
return [ 'conditions' => [ // Core WordPress conditions 'is_404' => '404', 'is_archive' => 'archive', 'is_attachment' => 'attachment', 'is_author' => 'author', 'is_category' => ['category', 'cat'], 'is_date' => 'date', 'is_day' => 'day', 'is_front_page' => ['/', 'front'], 'is_home' => ['home', 'blog'], 'is_month' => 'month', 'is_page_template' => 'template', 'is_page' => 'page', 'is_paged' => 'paged', 'is_post_type_archive' => ['post-type-archive', 'postTypeArchive'], 'is_search' => 'search', 'is_single' => 'single', 'is_singular' => 'singular', 'is_sticky' => 'sticky', 'is_subpage' => ['subpage', 'subpageof'], 'is_tag' => 'tag', 'is_tax' => 'tax', 'is_time' => 'time', 'is_year' => 'year',
// WooCommerce conditions 'is_shop' => 'shop', 'is_product' => 'product', 'is_cart' => 'cart', 'is_checkout' => 'checkout', 'is_account_page' => 'account', 'is_product_category' => 'product_category', 'is_product_tag' => 'product_tag', 'is_wc_endpoint_url' => 'wc_endpoint', ], // ... other WordPress configuration options];The framework provides a default set of aliases in its config/wordpress.php file. You can override or extend these aliases in your application’s config/wordpress.php file.
Publishing the WordPress Configuration File
Section titled “Publishing the WordPress Configuration File”To customize WordPress route conditions, you can publish the framework’s configuration file to your application:
php artisan vendor:publish --tag=wp-configThis command will copy the framework’s configuration file to your application’s config/ directory, allowing you to customize it according to your needs.
Each alias maps a WordPress conditional function (like is_page()) to a route URI or array of URIs. For example, 'is_page' => 'page' means that when you use Route::wp('page', ...) or Route::wp('is_page', ...), it will check if the current request matches the is_page() WordPress condition.
Using WordPress Routes
Section titled “Using WordPress Routes”To define a WordPress-specific route, you can use either the wp or wpMatch method. You have two ways to specify the WordPress condition:
- Using the predefined aliases (shorter syntax)
- Using the actual WordPress conditional function names (more explicit)
Here are examples of both approaches:
// Using aliases (shorter syntax)Route::wp('single', function () { return view('post');});
// Using actual WordPress function names (more explicit)Route::wp('is_single', function () { return view('post');});
// Using wpMatch with aliasesRoute::wpMatch('GET', 'page', 'landing-page', function() { return view('pages.landing');});
// Using wpMatch with actual WordPress function namesRoute::wpMatch('GET', 'is_page', 'landing-page', function() { return view('pages.landing');});
// Using wpMatch with multiple HTTP verbs and aliasesRoute::wpMatch(['GET', 'POST'], 'page', ['contact', 'request-a-quote'], [FormController::class, 'index']);
// Using wpMatch with multiple HTTP verbs and actual WordPress function namesRoute::wpMatch(['GET', 'POST'], 'is_page', ['contact', 'request-a-quote'], [FormController::class, 'index']);Both methods accept a variable number of arguments:
-
For
wp:- First argument is the WordPress condition (either an alias or the actual function name)
- Last argument is the callback function or controller action
- Any arguments in between are passed as parameters to the WordPress condition function
-
For
wpMatch:- First argument is the HTTP method(s) (‘GET’, ‘POST’, etc. or an array of methods)
- Second argument is the WordPress condition (either an alias or the actual function name)
- Last argument is the callback function or controller action
- Any arguments in between are passed as parameters to the WordPress condition function
For example:
// Route for GET requests to a specific page using aliasRoute::wpMatch('GET', 'page', 'landing-page', function() { return view('pages.landing');});
// Route for GET requests to a specific page using actual function nameRoute::wpMatch('GET', 'is_page', 'landing-page', function() { return view('pages.landing');});
// Route for POST requests to a form page using aliasRoute::wpMatch('POST', 'page', 'contact', [FormController::class, 'submit']);
// Route for POST requests to a form page using actual function nameRoute::wpMatch('POST', 'is_page', 'contact', [FormController::class, 'submit']);
// Route accepting both GET and POST for a product using aliasRoute::wpMatch(['GET', 'POST'], 'singular', 'product', 123, function() { return view('product.show');});
// Route accepting both GET and POST for a product using actual function nameRoute::wpMatch(['GET', 'POST'], 'is_singular', 'product', 123, function() { return view('product.show');});
// Route for all HTTP verbs using wp shortcut with aliasRoute::wp('tax', 'product_cat', 'electronics', function() { return view('taxonomy.product-category');});
// Route for all HTTP verbs using wp shortcut with actual function nameRoute::wp('is_tax', 'product_cat', 'electronics', function() { return view('taxonomy.product-category');});Important Differences from Standard Routes
Section titled “Important Differences from Standard Routes”When using Route::wp() or Route::wpMatch() instead of Route::any() or other standard methods:
- The route will be processed through WordPress’s conditional tags system
- WordPress-specific middleware will be automatically applied
- WordPress objects like
$postand$wp_querywill be available in the route parameters - The route will only match if the WordPress condition is met
Template Routes
Section titled “Template Routes”When working with WordPress custom templates (defined in themes/[theme-name]/config/templates.php), you can create specific routes to handle these templates. For example:
// This will match any page using the "contact" templateRoute::wp('template', 'contact', function () { return view('page');});
// This will match any page using the "landing" template and pass data to the viewRoute::wp('template', 'landing', function () { return view('page.landing', [ 'features' => Feature::all(), ]);});⚠️ Important: Template routes must be declared before the generic page route (Route::wp('page')), otherwise they won’t be taken into account.
Extending Route Conditions
Section titled “Extending Route Conditions”You can extend the WordPress route conditions by adding your own entries to the conditions array in your application’s config/wordpress.php file:
return [ 'conditions' => [ // Add your custom conditions 'is_custom_post_type' => 'custom-post-type', 'is_special_page' => ['special-page', 'special'],
// Override existing conditions 'is_page' => ['page', 'static-page'], ],];You can also create your own WordPress conditional functions and use them in your routes:
// functions.php or a custom plugin filefunction is_special_page() { // Your custom logic to determine if this is a special page return is_page() && has_term('special', 'page_category');}
// routes/web.phpRoute::wp('special-page', function() { return view('pages.special');});This approach allows you to create highly customized routing logic while maintaining the clean syntax of WordPress-style routes.
Dynamic Template Hierarchy
Section titled “Dynamic Template Hierarchy”The framework uses an enhanced template hierarchy system that builds upon WordPress’s native template hierarchy, but with several improvements:
-
Plugin Integration: The system dynamically integrates with plugins that register custom template types or conditions through WordPress hooks.
-
Template Filter Hooks: Plugins can filter the template hierarchy for each template type using hooks like
pollora/template_hierarchy/{type}_templates. -
Custom Template Handlers: Developers can register custom template handlers for specific scenarios:
// In a service provider or plugin$templateHierarchy = \Pollora\Theme\TemplateHierarchy::instance();
// Register a custom template handler for product pages on sale$templateHierarchy->registerTemplateHandler('product_on_sale', function($queriedObject) { if (!$queriedObject || !function_exists('wc_get_product')) { return []; }
$product = wc_get_product($queriedObject->ID); if (!$product || !$product->is_on_sale()) { return []; }
return [ "product-on-sale-{$product->get_slug()}.php", 'product-on-sale.php', ];});
// Add the corresponding conditionadd_filter('pollora/template_hierarchy/conditions', function($conditions) { $conditions['is_product_on_sale'] = 'product_on_sale'; return $conditions;});
// Define the conditional functionfunction is_product_on_sale() { if (!function_exists('is_product') || !is_product()) { return false; }
global $product; if (!$product) { $product = wc_get_product(get_queried_object_id()); }
return $product && $product->is_on_sale();}- Performance Optimization: The template hierarchy can be cached to improve performance:
// In a service provider$templateHierarchy = \Pollora\Theme\TemplateHierarchy::instance();$templateHierarchy->finalizeHierarchy(true); // true enables caching- Adjusting Template Priority: Plugins can modify the hierarchy order:
add_filter('pollora/template_hierarchy/order', function($hierarchyOrder) { // Move 'is_product' higher in priority if (($key = array_search('is_product', $hierarchyOrder)) !== false) { unset($hierarchyOrder[$key]); array_unshift($hierarchyOrder, 'is_product'); } return $hierarchyOrder;});Automatic Template Routing
Section titled “Automatic Template Routing”The framework implements a cascading routing system that works as follows:
-
WordPress Admin Requests: If the request is for the WordPress admin area, it is handled by a dedicated admin route.
-
Special WordPress Requests: If the request is for a special WordPress feature (robots.txt, favicon, feeds, trackbacks), it is handled by a dedicated special route.
-
Laravel Custom Routes: The framework checks if a matching route exists in your
routes/web.phpfile. These routes have high priority and will be executed if they match the requested URL. -
WordPress Template Hierarchy: If no custom route matches, the
FrontendControllertakes over and:- Determines the appropriate template hierarchy based on WordPress conditional tags
- Sequentially searches for matching Blade views in your views directory
- Returns the first view found in the hierarchy order
-
Fallback Handling: If no view is found in the template hierarchy, a 404 error is returned.
For example, for an “About” page, the process would be as follows:
// 1. First checks if a custom route exists in routes/web.phpRoute::wp('page', 'about-us', function() { return view('pages.about');});
// 2. If no route matches, FrontendController checks the template hierarchy:// - page-about.blade.php// - page.blade.php// - singular.blade.php// - index.blade.php
// 3. If no view is found, returns a 404This approach offers several benefits:
- Allows fine-grained customization via Laravel routes when needed
- Maintains compatibility with WordPress template hierarchy
- Provides automatic fallback to more generic templates
- Reduces boilerplate code by automatically handling standard cases
- Ensures that all requests are handled appropriately, even special WordPress requests
⚠️ Important: Routes defined in routes/web.php always take precedence over the automatic template system. Use this advantage to:
- Add custom logic to specific pages
- Inject specific data into your views
- Handle special cases requiring processing before display
Special WordPress Requests
Section titled “Special WordPress Requests”The framework automatically handles special WordPress request types, including:
- robots.txt: Requests for
/robots.txtare handled by WordPress’s built-indo_robotsaction - favicon.ico: Requests for
/favicon.icoare handled by WordPress’s built-indo_faviconaction - Feeds: RSS and Atom feed requests are handled by WordPress’s built-in
do_feedfunction - Trackbacks: Trackback requests are handled by WordPress’s built-in trackback handler
You can override these default handlers by defining your own routes for these special requests:
// Override the default robots.txt handlerRoute::wp('is_robots', function() { return response("User-agent: *\nDisallow: /wp-admin/\nAllow: /wp-admin/admin-ajax.php", 200) ->header('Content-Type', 'text/plain');});
// Override the default feed handlerRoute::wp('is_feed', function() { return response()->view('custom-feed', ['posts' => Post::latest()->take(10)->get()]) ->header('Content-Type', 'application/rss+xml');});Fallback Routes
Section titled “Fallback Routes”When no route matches the current request, the framework automatically creates a fallback route that delegates to the FrontendController. This controller:
- Determines the appropriate template based on WordPress’s template hierarchy
- Renders the corresponding Blade view
- Returns a 404 response if no appropriate view is found
This ensures that all requests are handled appropriately, even if no explicit route is defined.
Extending WordPress Routes
Section titled “Extending WordPress Routes”You can extend WordPress routes by adding custom conditions and template handlers. This is typically done in a service provider:
use Pollora\Theme\TemplateHierarchy;
class RouteServiceProvider extends ServiceProvider{ public function boot(TemplateHierarchy $templateHierarchy) { // Register a custom template handler $templateHierarchy->registerTemplateHandler('custom_post_type', function($post) { if (!$post || $post->post_type !== 'custom_post_type') { return []; }
return [ "custom-post-type-{$post->post_name}.blade.php", 'custom-post-type.blade.php', ]; });
// Add custom condition add_filter('pollora/template_hierarchy/conditions', function($conditions) { $conditions['is_custom_post_type'] = 'custom_post_type'; return $conditions; });
// Define custom conditional function if (!function_exists('is_custom_post_type')) { function is_custom_post_type() { return is_singular('custom_post_type'); } } }}This allows you to create custom routes that integrate seamlessly with both Laravel’s routing system and WordPress’s template hierarchy.
Theme & Plugin API Routes
Section titled “Theme & Plugin API Routes”Themes and plugins can define their own routes by placing route files in a routes/ directory at their root. The framework automatically discovers and loads these files during registration.
Route Files
Section titled “Route Files”| File | Prefix | Use case |
|---|---|---|
routes/api.php | /api | JSON endpoints, AJAX handlers, headless APIs |
routes/web.php | none | Custom pages, form handlers, redirects |
Basic Usage
Section titled “Basic Usage”use Illuminate\Support\Facades\Route;use Theme\MyTheme\Http\Controllers\Api\ProductSearchController;
Route::get('/products/search', ProductSearchController::class);// → accessible at GET /api/products/searchuse Illuminate\Support\Facades\Route;
Route::get('/custom-page', function () { return view('pages.custom');});API routes are standard Laravel routes — you can use controllers, middleware, route groups, rate limiting, and all other Laravel routing features.
Lightweight API Mode
Section titled “Lightweight API Mode”API routes (/api/*) automatically benefit from lightweight mode: WordPress loads without plugins, reducing response times from ~1.3s to ~100ms. The theme is still loaded, so routes, controllers, and services work normally.
This is controlled by the wordpress.api_plugins configuration key:
// No plugins loaded (fastest — default)'api_plugins' => [],
// Selective: only these plugins are loaded'api_plugins' => ['woocommerce', 'advanced-custom-fields-pro'],
// Glob patterns: load all plugins matching a prefix'api_plugins' => ['woocommerce*'],// → matches woocommerce, woocommerce-subscriptions, woocommerce-payments, etc.
// Load all plugins (disable lightweight mode)'api_plugins' => ['*'],// or'api_plugins' => null,Since plugins are not loaded by default, API route handlers should use Eloquent / DB facade instead of WordPress or plugin functions (e.g., get_permalink(), wc_get_product()). When a handler needs a specific plugin, add it to the api_plugins array or use the function_exists() pattern to provide fallbacks:
// Works with or without WooCommerce loadedif (function_exists('get_woocommerce_currency_symbol')) { $currency = get_woocommerce_currency_symbol();} else { $currency = DB::table('options') ->where('option_name', 'woocommerce_currency') ->value('option_value') ?? 'EUR';}Plugin Routes
Section titled “Plugin Routes”Plugins follow the same convention. Place a routes/api.php or routes/web.php in the plugin root:
my-plugin/├── app/│ └── Http/Controllers/Api/│ └── StatsController.php├── routes/│ └── api.php ← GET /api/stats├── config/└── my-plugin.phpuse Illuminate\Support\Facades\Route;use Plugin\MyPlugin\Http\Controllers\Api\StatsController;
Route::get('/stats', StatsController::class);Comparison with WordPress REST API
Section titled “Comparison with WordPress REST API”| Feature | Theme/Plugin API routes | WordPress REST API |
|---|---|---|
| Prefix | /api/ | /wp-json/ |
| Framework | Laravel router | WP REST infrastructure |
| Plugins loaded | Configurable (none by default) | All |
| Response time | ~100ms | ~1.3s |
| Authentication | Laravel middleware | WP nonce / application passwords |
| ORM | Eloquent / DB facade | WP_Query / wpdb |
Use theme/plugin API routes for performance-critical endpoints (search suggestions, autocomplete, dashboards). Use the WordPress REST API when you need full WordPress context (Gutenberg editor, third-party plugin integrations).