aldeebhasan/lottie-laravel
A Laravel package for embedding Lottie animations in Blade views. It offers a simple component or directive for rendering JSON-based animations with configurable playback, sizing, and asset options, making lightweight motion graphics easy to add to Laravel apps.
Install and publish. The package is a thin wrapper around lottie-web aimed at two jobs: keeping your .json animation files organized inside a Laravel app, and giving you a ready-made preview surface for them.
composer require aldeebhasan/lottie-laravel
php artisan vendor:publish --provider="Aldeebhasan\Lottie\LottieServiceProvider"
If the provider tag fails, list what’s actually publishable first — small packages often split config, views, and JS assets into separate tags:
php artisan vendor:publish --list | grep -i lottie
Where to look first (in this order).
config/lottie.php — this is the contract for the whole package. Expect a storage disk/path for animation files, a route prefix for the preview endpoint, and a pointer to the player script. Everything you’ll want to change day-to-day lives here.routes/ inside the package (or php artisan route:list | grep lottie) — confirms whether a preview route is auto-registered and under what middleware.resources/views/ — the preview Blade view, which doubles as the reference implementation for how the player is bootstrapped. Copy from it rather than reading the README.src/ — usually a handful of classes: a service provider, a controller for serving JSON, and one helper/facade.First use case: preview a real animation end-to-end. Drop a valid Bodymovin export into the configured animation directory (commonly storage/app/lottie or public/lottie), then hit the preview route:
cp ~/Downloads/confetti.json storage/app/lottie/confetti.json
php artisan serve
# visit /lottie/confetti (check route:list for the real URI)
If you see the animation play, the package is wired correctly and you can move on to embedding. If you see raw JSON in the browser, the route works but the player asset isn’t published — go back and publish assets.
Embedding in Blade. The package almost certainly ships either a Blade component or a directive that renders a container plus the player init. Treat the preview view as the source of truth for the exact signature. A typical shape:
{{-- component form --}}
<x-lottie name="confetti" :loop="true" :autoplay="true" class="h-64 w-64" />
{{-- or a directive/helper form --}}
@lottie('confetti', ['loop' => false])
Because the underlying library is lottie-web, anything the package doesn’t expose (renderer type, subframe rendering, speed) you can still reach through the player instance. Prefer the package’s attributes for the common 90% and drop to raw JS for the rest.
Serving animation JSON as an API endpoint. The most useful pattern in a real app is not embedding the file path — it’s fetching the JSON so you can gate access, version it, and cache it. Add a small controller that delegates path resolution to the package’s config, then set aggressive cache headers since these files are immutable-by-content:
Route::get('/animations/{name}', function (string $name) {
$path = storage_path("app/lottie/{$name}.json");
abort_unless(File::exists($path), 404);
return response()->file($path, [
'Content-Type' => 'application/json',
'Cache-Control' => 'public, max-age=31536000, immutable',
'ETag' => md5_file($path),
]);
})->where('name', '[A-Za-z0-9_-]+');
Then in the front end, feed src/path to the player pointing at /animations/confetti.
Organizing multiple animations. Keep a flat directory keyed by slug (empty-state.json, success-check.json, loading-dots.json) and resolve by name in config rather than hardcoding paths in views. This makes it trivial to swap an animation later without touching Blade, and lets you A/B two variants by changing one config entry.
Inertia / SPA usage. In an Inertia or Livewire app you rarely want the package’s full preview page — you want the JSON. Use your own route (above) and initialize lottie-web directly in the component. This avoids double-loading the player if you already bundle lottie-web via npm, and keeps the animation lifecycle tied to component mount/unmount:
import lottie from 'lottie-web';
const anim = lottie.loadAnimation({
container: el,
renderer: 'svg',
loop: true,
autoplay: true,
path: '/animations/empty-state',
});
onUnmounted(() => anim.destroy());
Preview route as a dev tool. The preview endpoint is genuinely handy during development for designers and PMs to eyeball animations without pulling the repo. Gate it — abort_unless(app()->isLocal(), 404) in the controller or ->middleware('can:view-lottie') — so it doesn’t become an unauthenticated file browser in production.
Testing. Animations are data, so test them like data: assert the route returns 200 with Content-Type: application/json, and add a CI check that every file under the animation directory parses as valid JSON and contains the required v, w, h, layers keys. That single test catches the most common failure mode — a designer committing a truncated or reformatted file.
Content-Type is the #1 silent failure. Serve Lottie JSON with application/json, not text/plain or application/octet-stream. Some players refuse to parse otherwise, and the error surfaces as a blank canvas with nothing in the console.
“Valid JSON” ≠ “valid Lottie.” A file can parse fine and still render nothing because it lacks layers, or because it was exported as a GIF-adjacent format. The Bodymovin schema is loose enough that malformed animations fail silently. Validate against a known-good animation early, and keep the w/h/v keys in your CI check.
Publishing is a one-time snapshot. Published config, views, and assets are copies. After composer update, re-publish with --force (config especially) or you’ll be running old defaults against new package code and debugging ghosts. If you’ve customized a published view, diff it against the package’s before forcing.
Storage disk vs. public path. If the config points at a disk, Storage::disk('lottie')->url(...) may not produce a browsable URL unless the disk is public and symlinked. For internal animations, serving through a controller (as above) sidesteps the symlink entirely and is the safer default on shared hosting.
Path traversal. Any route that takes an animation name from the URL must constrain it — ->where('name', '[A-Za-z0-9_-]+') plus a basename() or realpath() check. A naive storage_path("app/lottie/{$name}.json") with ../../.env is a file disclosure.
Player version mismatch. lottie-web has changed renderer behavior across majors. Animations exported with recent After Effects/Bodymovin plugins sometimes need a newer player than the one bundled. If an animation plays in lottiefiles.com but not locally, that’s the cause — bump the bundled player or load lottie-web from npm and skip the package’s script entirely.
File size. Lottie files are JSON text and compress extremely well but are large uncompressed (multi-hundred-KB is normal). Enable gzip/brotli at the web server and set long cache lifetimes — they’re effectively immutable. Don’t inline the JSON into Blade; you lose caching and bloat every HTML response.
CORS if the player is elsewhere. Serving animations from a Laravel API to a separately-hosted front end requires a CORS entry for the animation route, not just your API routes. Add config/cors.php paths for the lottie prefix.
Extension points worth knowing. The config file is the intended customization surface — disk, path, route prefix, middleware, and player defaults. If you need behavior the package doesn’t expose, the cleanest move is usually to ignore the wrapper for rendering and only use it for storage conventions, wiring lottie-web yourself. Ten stars means the package is small and unlikely to grow features on your timeline; don’t wait for upstream.
Laravel version drift. With a low-star package, check the composer.json constraints before upgrading Laravel. Auto-discovery and Blade component registration are the usual breakpoints, and a package that hasn’t been touched since 2026-10-18 may lag a major release. Pin it in composer.json if you’re on the bleeding edge.
How can I help you explore Laravel packages today?