Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Acorn
https://github.com/roots/acorn
Admin
Integrate Laravel functionality into WordPress projects
Tokens:
8,047
Snippets:
46
Trust Score:
9.5
Update:
1 week ago
Context
Skills
Chat
Benchmark
38.3
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Roots Acorn Acorn is a framework that brings the full Laravel ecosystem into WordPress projects. It wraps and extends Laravel's application container, configuration, service providers, routing, view system (Blade), caching, queuing, events, validation, and more — all bootstrapped inside a running WordPress installation. Rather than replacing WordPress, Acorn layers on top of it, so plugin and theme authors can write modern, testable PHP using Laravel patterns while still interoperating with WordPress hooks, templates, and the WP-CLI. The core of Acorn is a custom `Application` class that extends Laravel's `Illuminate\Foundation\Application`, adding WordPress-aware bootstrapping (HTTP, WP-CLI, and console boot paths), a smarter `PackageManifest` that discovers Composer packages from themes and active plugins, WordPress-integrated path resolution (falling back to `WP_CONTENT_DIR/cache/acorn` when no `storage/` directory exists), and a resilient provider system that silently skips broken service providers rather than crashing the entire WordPress request. Acorn also ships its own asset pipeline (`Manifest`, `Bundle`, `Manager`) that understands Bud, Vite, and Laravel Mix manifest formats, a `Composer` base class for passing typed data to Blade views, and a `Sage` integration layer that filters WordPress template selection to use Blade. --- ## `Application::configure()` — Bootstrap the Acorn application The fluent entry point for creating and configuring an Acorn application. Automatically infers the base path from the active WordPress theme, `ACORN_BASEPATH`, or `APP_BASE_PATH` env var. Returns an `ApplicationBuilder` that can be chained to register routing, middleware, providers, and exceptions before calling `boot()` or `create()`. ```php // bootstrap/app.php (inside a Sage theme or WordPress plugin) <?php use Roots\Acorn\Application; // Minimal bootstrap — paths, kernels, events, commands, providers, // middleware, and exceptions are all wired automatically. return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__ . '/../routes/web.php', api: __DIR__ . '/../routes/api.php', wordpress: true, // pass WordPress-rendered responses through Laravel's HTTP kernel ) ->withMiddleware(function (\Roots\Acorn\Configuration\Middleware $middleware) { $middleware->web(append: [ \App\Http\Middleware\HandleWordPressSession::class, ]); }) ->withExceptions(function (\Roots\Acorn\Configuration\Exceptions $exceptions) { $exceptions->report(function (\Illuminate\Validation\ValidationException $e) { // custom reporting for validation failures }); }) ->withProviders([ \App\Providers\AppServiceProvider::class, \App\Providers\AuthServiceProvider::class, ]) ->boot(); // Expected: returns the booted Roots\Acorn\Application instance ``` --- ## `Application::usePaths()` — Override application directory paths Allows the developer to remap any of the nine application directories (`app`, `bootstrap`, `config`, `database`, `lang`, `public`, `resources`, `storage`, `environment`) to absolute paths. Binds all paths into the container and re-resolves `lang` relative to `resources` when appropriate. ```php <?php use Roots\Acorn\Application; $app = Application::configure(basePath: get_template_directory())->create(); // Redirect storage to a writable location outside the theme $app->usePaths([ 'storage' => WP_CONTENT_DIR . '/uploads/my-theme/storage', 'resources' => get_template_directory() . '/resources', 'config' => get_template_directory() . '/config', 'public' => get_template_directory() . '/public', ]); // All path helpers now reflect the overrides: // storage_path('framework/views') → .../uploads/my-theme/storage/framework/views // resource_path('views') → .../my-theme/resources/views echo $app->storagePath('logs'); // /var/www/html/wp-content/uploads/my-theme/storage/logs ``` --- ## `ApplicationBuilder::withPaths()` — Configure paths during boot fluently Part of the fluent builder chain returned by `Application::configure()`. Accepts individual named path arguments; unspecified paths fall back to `ACORN_*_PATH` env vars, `ACORN_*_PATH` PHP constants, theme file paths, or sensible defaults. ```php <?php return \Roots\Acorn\Application::configure(basePath: dirname(__DIR__)) ->withPaths( storage: WP_CONTENT_DIR . '/cache/my-plugin', config: __DIR__ . '/../config', resources: __DIR__ . '/../resources', public: __DIR__ . '/../public', ) ->withKernels() ->boot(); ``` --- ## `ApplicationBuilder::withRouting()` — Register application routes Extends Laravel's `withRouting()` with a `wordpress: true` flag that registers a catch-all route processed through Laravel's middleware stack, enabling Acorn to intercept, modify, or wrap any WordPress-rendered response using middleware. ```php <?php // routes/web.php use Illuminate\Support\Facades\Route; Route::get('/custom-page', function () { return view('custom-page', ['title' => 'Hello from Acorn']); }); Route::middleware(['auth'])->group(function () { Route::get('/dashboard', [\App\Http\Controllers\DashboardController::class, 'index']); }); // bootstrap/app.php return \Roots\Acorn\Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__ . '/../routes/web.php', wordpress: true, // WordPress pages also pass through the HTTP kernel ) ->boot(); ``` --- ## `Roots\view()` — Render a Blade view Global helper (and `Roots\Acorn\View\Component::view()` instance method) that resolves a Blade view by name or falls back to rendering a raw file path. Transparently replaces the standard `view()` Laravel helper, using Acorn's `FileViewFinder` which searches both the theme directory and registered view paths. ```php <?php // In a WordPress template file (page.php, single.php, etc.) // Renders resources/views/partials/hero.blade.php echo \Roots\view('partials.hero', [ 'title' => get_the_title(), 'subtitle' => get_field('subtitle'), // ACF example ]); // Conditionally pass extra data $data = [ 'posts' => get_posts(['post_type' => 'product', 'numberposts' => 6]), 'categories' => get_terms('product_cat'), ]; return \Roots\view('woocommerce.archive', $data); // Expected output: rendered HTML from the Blade template ``` --- ## `Roots\asset()` / `\asset()` — Resolve a versioned asset URL Looks up an asset key in the active `Manifest` (default: `theme`) and returns an `Asset` object whose `__toString()` produces the versioned/cache-busted public URL. Falls back to the raw key if not found in the manifest. The global `asset()` function is available in WordPress templates; the `\Roots\asset()` function is the canonical namespaced version. ```php <?php // In a Blade view or PHP template // Simple URL output (manifest.json maps "app.css" → "app.a1b2c3d4.css") $url = \Roots\asset('images/logo.svg'); echo $url; // https://example.com/app/themes/my-theme/public/images/logo.abc12345.svg // In a Blade template using the @asset directive // resources/views/layouts/app.blade.php ?> <link rel="stylesheet" href="@asset('styles/app.css')"> <script src="@asset('scripts/app.js')" defer></script> <?php // Access a specific named manifest $pluginAsset = \Roots\asset('admin.css', 'my-plugin'); echo $pluginAsset->uri(); // returns the full URI string ``` --- ## `Roots\bundle()` — Enqueue a Bud/Vite/Mix bundle Retrieves a `Bundle` object from the named entrypoint in `entrypoints.json` (Bud) or the equivalent manifest format. `Bundle` exposes `css()` and `js()` collection methods, and an `enqueue()` helper that calls `wp_enqueue_script` / `wp_enqueue_style` directly. ```php <?php add_action('wp_enqueue_scripts', function () { // Enqueue all CSS and JS from the "app" entrypoint \Roots\bundle('app')->enqueue(); // Manual iteration — register each file individually \Roots\bundle('editor')->css(function (string $handle, string $src) { wp_enqueue_style($handle, $src, [], null); }); \Roots\bundle('editor')->js(function (string $handle, string $src, array $deps) { wp_enqueue_script($handle, $src, $deps, null, true); }); // Inline the webpack runtime to avoid extra HTTP requests if ($runtime = \Roots\bundle('app')->runtimeSource()) { wp_add_inline_script('app/app', $runtime, 'before'); } }); ``` --- ## `Composer` — Bind PHP data to Blade views Abstract base class for View Composers. Subclass it inside `app/View/Composers/` and declare which views it serves via the static `$views` property (or rely on auto-detection from the class name). Public properties and public methods are automatically extracted and passed to the view. Override `with()` for explicit data and `override()` to force-overwrite existing view data. ```php <?php namespace App\View\Composers; use Roots\Acorn\View\Composer; class FeaturedPosts extends Composer { // Explicit view binding (dot-notation or array) protected static $views = ['partials.featured-posts', 'home']; // Public properties are auto-passed to the view public string $heading = 'Latest Posts'; // Public methods are also exposed as view variables public function posts(): array { return get_posts([ 'post_type' => 'post', 'numberposts' => 3, 'meta_key' => '_featured', 'meta_value' => '1', ]); } // Override parent's with() for explicit control protected function with(): array { return [ 'posts' => $this->posts(), 'heading' => $this->heading, ]; } // override() data takes precedence over anything the view already has protected function override(): array { return [ 'bodyClass' => 'featured-posts-layout', ]; } } // Register in AppServiceProvider::boot(): // $this->app->make('view')->composer(FeaturedPosts::views(), FeaturedPosts::class); // // In resources/views/partials/featured-posts.blade.php: // <h2>{{ $heading }}</h2> // @foreach ($posts as $post) // <a href="{{ get_permalink($post) }}">{{ $post->post_title }}</a> // @endforeach ``` --- ## `Cacheable` trait — Cache composer data Mix into a `Composer` subclass to cache the result of `with()` indefinitely (or for a configurable TTL). Uses Laravel's Cache facade with optional tag support. The default cache key is a `crc32b` hash of the class name and the current queried object, so different archive/single pages get independent cache entries. ```php <?php namespace App\View\Composers; use Roots\Acorn\View\Composer; use Roots\Acorn\View\Composers\Concerns\Cacheable; class SidebarWidgets extends Composer { use Cacheable; protected static $views = ['partials.sidebar']; // Cache TTL in seconds (omit to cache forever) protected $cache_expiration = 3600; // 1 hour // Optional: override the cache tags protected $cache_tags = ['sidebar', 'widgets']; protected function with(): array { return [ 'recentPosts' => get_posts(['numberposts' => 5]), 'categories' => get_terms(['taxonomy' => 'category', 'hide_empty' => true]), ]; } } // To manually bust the cache (e.g. on post save): add_action('save_post', function () { // flush() clears all keys tagged with sidebar + widgets (new SidebarWidgets())->flush(); // or forget a single key: // (new SidebarWidgets())->forget('my-custom-key'); }); ``` --- ## `Filesystem::closest()` — Walk up the directory tree to find a file Traverses upward from a given starting directory until it finds the specified filename, respecting PHP's `open_basedir` restriction. Used internally by Acorn to locate `.env`, `composer.json`, and other configuration files relative to the WordPress install root. ```php <?php use Roots\Acorn\Filesystem\Filesystem; $files = new Filesystem(); // Find the nearest composer.json walking up from the current theme $composerPath = $files->closest(get_template_directory(), 'composer.json'); // e.g. /var/www/html/composer.json if ($composerPath) { $composer = json_decode(file_get_contents($composerPath), true); echo 'Project name: ' . $composer['name']; // e.g. "roots/bedrock" } else { // No composer.json found within open_basedir echo 'Not found'; } // Locate the nearest .env (Acorn uses this to determine environmentPath()) $envPath = $files->closest(WP_CONTENT_DIR, '.env'); echo dirname($envPath); // /var/www/html ``` --- ## `Filesystem::getRelativePath()` — Compute relative path between two absolute paths Calculates the relative path from a base file path to a target path, including leading `../` traversals. Used by the asset system to resolve relative asset URLs. Path separators are normalized to `/` on all platforms. ```php <?php use Roots\Acorn\Filesystem\Filesystem; $files = new Filesystem(); $relative = $files->getRelativePath( '/var/www/html/wp-content/themes/my-theme/resources/views/layouts/app.blade.php', '/var/www/html/wp-content/themes/my-theme/public/images/logo.svg', ); echo $relative; // ../../public/images/logo.svg $relative2 = $files->getRelativePath( '/var/www/html/wp-content/themes/my-theme/public/', '/var/www/html/wp-content/themes/my-theme/public/scripts/app.js', ); echo $relative2; // scripts/app.js ``` --- ## `AcornServiceProvider` — Multisite queue isolation The built-in `AcornServiceProvider` automatically configures cache prefix and session cookie isolation per-blog on WordPress Multisite. For queued jobs, it injects `blogId` into every job payload and calls `switch_to_blog()` before processing, then `restore_current_blog()` after completion or failure. ```php <?php // This behavior is automatic when Acorn is installed on a multisite. // No manual configuration required. The provider is registered via DefaultProviders. // Example: dispatching a job from blog 3 — it will run in blog 3's context switch_to_blog(3); dispatch(new \App\Jobs\GenerateReport(get_current_blog_id())); restore_current_blog(); // The job handler: namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; class GenerateReport implements ShouldQueue { use Dispatchable, Queueable; public function __construct(public int $blogId) {} public function handle(): void { // Acorn has already called switch_to_blog($this->blogId) before this runs $posts = get_posts(['numberposts' => -1]); // generate and store report... } } ``` --- ## WP-CLI integration — Run Artisan commands via `wp acorn` Acorn registers itself as a WP-CLI command group at boot. Every Laravel/Artisan command registered with the console kernel is accessible as `wp acorn <command>`. The `acorn:install` command configures `composer.json` post-autoload-dump hooks; `acorn:init` scaffolds the directory structure; and `wp acorn list` displays all available commands. ```bash # Install Acorn and scaffold the application structure wp acorn acorn:install --autoload --init --no-interaction # Generate a new service provider wp acorn make:provider ThemeServiceProvider # Generate a new view composer wp acorn make:class App/View/Composers/HeroComposer # Cache routes and config for production wp acorn route:cache wp acorn config:cache wp acorn optimize # Publish Acorn config files to the theme wp acorn vendor:publish --tag=acorn-configs # Clear all caches wp acorn optimize:clear # Run database migrations (requires illuminate/database) wp acorn migrate --force # Generate an application key wp acorn key:generate ``` --- ## Asset `Manager` — Register and resolve named manifests The `Manager` is bound as `assets` in the container. It lazily resolves `Manifest` instances from the `config/assets.php` configuration, running each config through a middleware pipeline (`RootsBudMiddleware`, `ViteMiddleware`, `LaravelMixMiddleware`) that auto-detects the manifest format and normalizes the config keys. ```php <?php // config/assets.php — register a second manifest for a plugin return [ 'default' => 'theme', 'manifests' => [ 'theme' => [ 'path' => get_theme_file_path('public'), 'url' => get_theme_file_uri('public'), 'assets' => get_theme_file_path('public/manifest.json'), 'bundles' => get_theme_file_path('public/entrypoints.json'), ], 'my-plugin' => [ 'path' => plugin_dir_path(__FILE__) . 'public', 'url' => plugin_dir_url(__FILE__) . 'public', 'assets' => plugin_dir_path(__FILE__) . 'public/manifest.json', 'bundles' => plugin_dir_path(__FILE__) . 'public/entrypoints.json', ], ], ]; // Usage in a service provider or template: $manager = app('assets'); $manifest = $manager->manifest('my-plugin'); $cssUrl = $manifest->asset('styles/admin.css'); // returns Asset object echo $cssUrl; // https://example.com/wp-content/plugins/my-plugin/public/styles/admin.abc123.css $bundle = $manifest->bundle('admin'); $bundle->css(fn ($handle, $src) => wp_enqueue_style($handle, $src)); $bundle->js(fn ($handle, $src, $deps) => wp_enqueue_script($handle, $src, $deps, null, true)); ``` --- Acorn's primary use cases are WordPress theme development (especially with Sage), WordPress plugin development requiring complex business logic, and headless or hybrid WordPress applications where Laravel's routing handles specific URL patterns while WordPress manages the rest. It provides full access to Laravel's Eloquent ORM (for custom databases or direct wp-prefixed table queries), the Illuminate cache, queue, and events systems, all within a standard WordPress page-load lifecycle. Integration follows a consistent pattern: `Application::configure()->withRouting()->withProviders()->boot()` in a bootstrap file required from `functions.php` or a must-use plugin; service providers register bindings and listeners; view composers bind PHP data to Blade templates; and asset manifests produced by Bud, Vite, or Laravel Mix are consumed through `\Roots\bundle()` and `\Roots\asset()` in `wp_enqueue_scripts` actions. The WP-CLI bridge means all Artisan-style generators, cache commands, and custom commands work directly inside any WordPress environment via `wp acorn`.