backstage / favicon
Fetch, convert, and cache favicons for any website in your Laravel app.
Requires
- php: ^8.2
- ext-imagick: *
- illuminate/contracts: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.34|^3.0
- pestphp/pest-plugin-laravel: ^2.4|^3.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-09-18 13:43:33 UTC
README
Fetch, convert, and cache favicons for any website in your Laravel app.
Given a site URL, the package discovers the best available icon source (preferring an SVG over an ICO over a raster PNG/WebP/AVIF over a JPG), downloads it, and converts it into whichever size/format you ask for — caching the result on disk so subsequent requests are instant until the TTL expires.
Requirements
- PHP 8.2+
- The
imagickPHP extension, ideally built with thelibrsvgdelegate (for SVG rasterization) andlibheifdelegate (for AVIF) - Laravel 10, 11, or 12
Installation
composer require backstage/favicon
Optionally publish the config:
php artisan vendor:publish --tag=favicon-config
Usage
Facade
use Backstage\Favicon\Facades\Favicon; // Absolute filesystem path $path = Favicon::for('https://github.com')->get('png', 32); // Public URL (served from the configured disk) $url = Favicon::for('https://github.com')->url('webp', 64); // The original SVG, only if the discovered source was actually an SVG $url = Favicon::for('https://github.com')->url('svg'); // Force a re-fetch from the source site, ignoring the TTL $url = Favicon::for('https://github.com')->refresh()->url('png', 32);
Supported types: png, jpg, webp, avif, and svg (pass-through only — a raster source can't be vectorized).
Blade component
<x-favicon url="https://github.com" size="32" type="png" class="rounded" />
Eloquent trait
use Backstage\Favicon\Concerns\HasFavicon; class Site extends Model { use HasFavicon; // Reads from $site->website by default; override per-model: protected string $faviconSource = 'homepage_url'; // Optional: override the package-wide default type/size for this model. protected string $faviconType = 'webp'; protected int $faviconSize = 64; }
$site->favicon; // accessor, uses $faviconType/$faviconSize (or config defaults) $site->faviconUrl(); // same as above $site->faviconUrl('png', 32); // explicit args still win over both
Artisan command
# Fetch the default type/size php artisan favicon:fetch https://github.com # Pre-warm every configured size for specific types php artisan favicon:fetch https://github.com --types=png,webp --sizes=16,32,180 # Ignore the TTL php artisan favicon:fetch https://github.com --force # Dispatch to the queue instead of running synchronously php artisan favicon:fetch https://github.com --queue
Queued pre-warming
use Backstage\Favicon\Jobs\FetchFaviconJob; FetchFaviconJob::dispatch('https://github.com', types: ['png', 'webp'], sizes: [32, 180]);
->warm() (used internally by the job/command) also generates a multi-resolution favicon.ico, an apple-touch-icon.png, and a site.webmanifest with Android Chrome icons, per the generate config.
How source discovery works
For a given site, the package:
- Fetches the page and parses
<link rel="icon">,rel="shortcut icon",rel="apple-touch-icon",rel="apple-touch-icon-precomposed", andrel="mask-icon"tags, plus<link rel="manifest">and itsiconsarray. - Falls back to
/favicon.icoat the domain root. - Ranks every candidate: SVG first, then ICO, then PNG/WebP/AVIF (largest declared size wins), then JPG.
- Downloads the best candidate and sniffs its real type from magic bytes (not the
Content-Typeheader, which is often wrong). - If the source is a multi-frame
.ico, the largest embedded frame is used.
Storage
No database table is used. Each fetched favicon is cached under the configured disk (default public) at:
favicons/{domain}/
├── meta.json # source url/type/hash + fetched_at, used for TTL checks
├── source.{ext} # cached original bytes
├── icon.svg # only when the source is svg
├── variants/{type}/{size}.{ext}
├── favicon.ico
├── apple-touch-icon.png
├── android-chrome-{size}x{size}.png
└── site.webmanifest
A favicon is considered fresh for favicon.ttl_days (default 30) days from fetched_at; after that, the next resolution re-fetches from the source site.
Testing
composer test
License
MIT.