digitallyhappy/assets
A Laravel package for managing front-end assets in one place. Register CSS and JavaScript files from controllers, views, or service providers, then render them in your layouts with a simple Blade directive—keeping asset inclusion organized and consistent.
digitallyhappy/assets is the asset manager behind Backpack for Laravel, but it is framework-agnostic enough to use in any Blade app: you point it at a CSS/JS file (local path or CDN URL), and it takes care of emitting the right <link>/<script> tag — serving a cached, hash-versioned copy in production and the original file in development.
Install
composer require digitallyhappy/assets:^2.1
Version 2.1.0 is the first release with Laravel 10 support. If your composer.json pins ^2.0, Composer will happily resolve 2.0.4 on a Laravel 10 app and then fail on framework constraints — bump the constraint rather than fighting --ignore-platform-reqs.
The service provider and Basset facade are auto-discovered; there is nothing to register in config/app.php.
Publish the config and look at it first
php artisan vendor:publish --provider="DigitallyHappy\Assets\AssetsServiceProvider" --tag=config
config/basset.php is where the package's behaviour actually lives: dev-mode flag, where cached files are written, and how they're named. Read it before writing any Blade — most "why is this file 404ing" questions are answered there.
First use case
Take a hardcoded tag in resources/views/layouts/app.blade.php:
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
and replace it with:
@basset('css/app.css')
@basset('https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css')
Then set BASSET_DEV_MODE=true in .env locally and false in production. Locally you load the original file (so edits show up instantly); in production Basset copies the file into a public cache folder and serves it with a fingerprint so browsers can cache it forever.
Layout partial, not the layout itself. Keep every @basset call in one place — e.g. resources/views/inc/assets.blade.php — and @include it from the layout. When a designer asks "where does this script come from", there's exactly one answer.
Per-page assets via Blade stacks. This is where Basset earns its keep in a component/widget codebase. A view pulls in its own dependencies without the layout knowing about them:
@push('after_styles')
@basset('vendor/flatpickr/flatpickr.min.css')
@endpush
@push('after_scripts')
@basset('vendor/flatpickr/flatpickr.min.js')
@bassetBlock('booking-datepicker')
<script>flatpickr('.datepicker', { minDate: 'today' });</script>
@endBassetBlock
@endpush
@bassetBlock('unique-key') … @endBassetBlock wraps inline code so it gets hashed and cached like a real file — useful for widget init snippets you don't want inlined on every page load. The key must be globally unique.
Registering assets from PHP. The Basset facade works outside Blade: Basset::script($path), Basset::style($path), Basset::add($path). Use it from a View Composer or a service provider to conditionally register, say, a map library only on pages that render a map — that keeps your layout free of @if (Route::is(...)) branches.
Mixing CDN and local files. Absolute URLs pass through as-is; local paths get copied and fingerprinted. Both go through the same directive, so you can migrate a CDN asset to a self-hosted one by changing a single string.
Deployment. The public cache folder is generated, not committed. Run the package's cache command as part of deploy (check php artisan list | grep basset for the exact names in your version — typically a build/refresh and a clear command), after config:cache, and make sure the folder is writable by the web user. Committing the generated folder is a legitimate alternative if your deploys have no post-deploy hook.
Tests. Force dev mode off (or the cache path to a temp dir) in phpunit.xml so tests don't write into public/; assertions on markup are then stable.
^2.1. On a fresh Laravel 10 upgrade, the failure mode is a constraint conflict in composer update, not a runtime error. Bump the requirement in the same commit as the framework upgrade.BASSET_DEV_MODE=true, the raw source path is emitted; with it off, the cached path is emitted. If the cached copy was never generated on the server, you get a 404 that only exists in production. Verify by viewing source: cached assets show up under the configured cache prefix (/basset/... by default) with a hash in the filename.BASSET_DEV_MODE read through config() is frozen by php artisan config:cache. If flipping the env var does nothing, run php artisan config:clear.@bassetBlock keys silently serve the wrong script — the second block with the same identifier wins or gets skipped depending on order. Namespace keys like booking-datepicker rather than scripts.@basset inside @push('after_scripts') lands wherever @stack('after_scripts') sits in the layout. If a script depends on jQuery or on Basset's own output, echo that stack after the layout's base assets.app.js, either Vite owns the bundle or Basset does — running both over the same entry point gives you two copies and confusing order-of-execution bugs.sudo-created folder is a classic silent break.Basset facade. For anything bespoke (custom attributes on the emitted tags, a different cache strategy), wrap @basset in your own Blade directive or a small helper class rather than patching vendor code — you'll want the next minor to install cleanly.How can I help you explore Laravel packages today?