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.
- PHP 8.2+
- The
imagickPHP extension, ideally built with thelibrsvgdelegate (for SVG rasterization) andlibheifdelegate (for AVIF) - Laravel 10, 11, or 12
composer require backstage/faviconOptionally publish the config:
php artisan vendor:publish --tag=favicon-configuse 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).
<x-favicon url="https://github.com" size="32" type="png" class="rounded" />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# 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 --queueuse 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.
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.
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.
composer testMIT.