Issues getting bud to work in custom plugin

I’ve inherited a few sites that don’t have a complete theme rebuild in the budget so i’m trying to make a drop-in custom plugin that integrates bud for tooling. I’ve tried integrating based off of the example here:

I couldn’t get the example in functions.php to work (after composer install) and ended up writing the following which is working

add_action('wp_enqueue_scripts', 'bud_enqueue_assets', 999);
add_action('admin_enqueue_scripts', 'bud_enqueue_assets', 999);

function bud_enqueue_assets()
    $plugin_path = plugin_dir_path(__FILE__) . '/';
    $manifestPath = $plugin_path . 'dist/entrypoints.json';
    $manifestJson = file_get_contents($manifestPath);
    $manifest = json_decode($manifestJson, true);

    $bundleName = 'app';

    if (isset($manifest[$bundleName])) {
        $bundle = $manifest[$bundleName];

        if (isset($bundle['js'])) {
            $jsFiles = $bundle['js'];

            foreach ($jsFiles as $jsFile) {
                $jsFileUrl = plugins_url('dist/' . $jsFile, __FILE__);


        if (isset($bundle['css'])) {
            $cssFiles = $bundle['css'];

            foreach ($cssFiles as $cssFile) {
                $cssFileUrl = plugins_url('dist/' . $cssFile, __FILE__);


bud build production is working as expected and correctly displaying compiled js/css on the frontend
bud build development though is only rendering JS… I can see the compiled CSS is injected into app.js but it is not rendered on the frontend.

I used npx create-bud-app and am using the following:

    "@roots/bud": "^6.16.1",
    "@roots/bud-babel": "^6.16.1",
    "@roots/bud-eslint": "^6.16.1",
    "@roots/bud-postcss": "^6.16.1",
    "@roots/bud-preset-wordpress": "^6.16.1",
    "@roots/bud-prettier": "^6.16.1",
    "@roots/bud-sass": "^6.16.1",
    "@roots/bud-stylelint": "^6.16.1",
    "@roots/eslint-config": "^6.16.1"
// bud.config.ts

import type {Bud} from '@roots/bud';

 * bud.js configuration
export default async (bud: Bud) => {
    .entry('app', ['index.js', 'index.scss'])

    .proxy('https://valet-experiment.test', (searches) => [
      ['', ''], // Replace with correct address


Not sure where I’m going wrong… any guidance would be greatly appreciated.

Thanks in advance

Hi @wdeer,

Bud, in development mode, will not emit CSS - it is loaded via the webpack runtime.

I haven’t tested your code, but one thing that it is missing at first glance is filtering out the hot-reload entrypoints. If these are being enqueued, it could conceivably cause a JS error, and block execution of the runtime, meaning your styles are never injected.

Are your JS entrypoints definitely getting onto the page and running without error in dev mode?

Also, and I’m not sure this is the real issue, but could bite you later - your example does not deal with dependencies from entrypoint.json, meaning splitChunks and other useful features will not work properly.


Thanks for the quick response, I hadn’t gotten super far with it, spent most of the weekend fighting with lando proxying then vagrant (via trellis) eventually went back to valet… aannnyways, I stopped when I at least got the files enqueuing with the new code (in my original post)

Anyways, even though the original code I posted was working in light of the concerns you mentioned, I decided to revisit the example code in the bud repo with fresh eyes. Originally I was looking in the source for the enqueued files by searching for my custom plugin’s url which made me completely miss the fact that they were indeed being enqueued, just to the incorrect path of (which is the bedrock site_url() + path found in entrypoints.json)

Once I realized that I adjusted the example code in the repo (and updated it to use the newer illuminate/collections) instead of tightenco/collect

// altered example from
// Changes in comments below

namespace App;

require_once __DIR__ . '/vendor/autoload.php';

use Illuminate\Support\Collection;

// Unaltered, just commented out (my use case doesn't need this)
// function partial(string $partial)
// {
//   $templateFile = realpath(get_theme_file_path("src/partials/{$partial}.php"));
//   $templateFile && require_once $templateFile;
// }

// added some constants 
define('ASSETS_PATH_FULL', trailingslashit(plugin_dir_path(__FILE__) . 'dist'));
define('ASSETS_PATH_URI', trailingslashit(plugin_dir_url(__FILE__) . 'dist'));

function getManifest()
    // Updated $path to point to plugin's path instead of get_theme_path
    $path = ASSETS_PATH_FULL . 'entrypoints.json';
    $path = realpath($path);

    if (!$path) throw new \WP_Error('Run yarn build');

    return collect(
                $path // this seemed simpler

function entrypoint(
    string $name,
    string $type,
    Object $entrypoint
) {
    $entrypoint->modules = collect(

    $hasDependencies = $type == 'js' &&
        property_exists($entrypoint, 'dependencies');

    $entrypoint->dependencies = collect(
            ? $entrypoint->dependencies
            : [],

    return $entrypoint->modules->map(
        function ($module, $index) use ($type, $name, $entrypoint) {
            $name = "{$type}.{$name}.{$index}";

            $dependencies = $entrypoint->dependencies->all();


            return (object) [
                'name' => $name,
                'uri' => $module,
                'deps' => $dependencies,

function bundle(string $bundleName)
    $filterHot = fn ($entry) => !strpos($entry->uri, 'hot-update');

    // Added $baseUrl to correct the final enqueued url to point to /dist/ inside plugin's root
    // Originally enqueued urls were pointing to site_url() (which for a bedrock site is
    $baseUrl = ASSETS_PATH_URI;

        ->filter(fn ($value, $key) => $key === $bundleName)
        ->map(fn ($item, $name) => (object) [
            'js' => entrypoint($name, 'js', $item),
            'css' => entrypoint($name, 'css', $item)
        ->each(function ($entrypoint) use ($filterHot, $baseUrl) { // now also using $baseUrl
                fn ($entry) =>
                wp_enqueue_script($entry->name, $baseUrl . $entry->uri, $entry->deps, null, true)

                fn ($entry) =>
                wp_enqueue_style($entry->name, $baseUrl . $entry->uri, $entry->deps, null)

add_action('wp_enqueue_scripts', fn () => bundle('app'), 100);
add_action('enqueue_block_editor_assets', fn () => bundle('editor'), 100);

That code so far appears to be working perfectly. It is correctly finding/reading entrypoints.json and then enqueueing the files within it (while also filtering out hot-updates and (i’m assuming correctly accounting for dependencies)

Additionally, it turns out that the reason CSS wasn’t being properly injected was because I also completely overlooked the js code (that i’m assuming was installed by default when I used npx create-bud-app ) where app.js was looking for #root which isn’t present on my site.

Anyways, all is well now. Thanks @talss89

1 Like