LaraSVG Deep Dive: SVG Conversion at Scale in Laravel

SVG conversion sounds simple until you're doing it at scale. After running 80 million+ conversions in production, we learned everything that can go wrong — and built LaraSVG to make it right.

This is a deep dive into how LaraSVG works, why we made the architectural decisions we did, and how you can use it to handle SVG conversion in your Laravel application.

The Problem with SVG Conversion in PHP

SVG is the web's native vector format, but getting it into PNG, PDF, or EPS requires an external renderer. PHP can't do it natively. Your options are:

  • Imagick — Relies on Ghostscript, poor SVG spec coverage, inconsistent output
  • Inkscape CLI — Excellent quality, but slow startup time (~1 second per conversion) and requires a desktop environment
  • rsvg-convert — Fast, lightweight, but limited to Linux, inconsistent CSS support
  • headless Chromium — Nuclear option, works well but heavy resource footprint
  • External APIs — Adds latency, costs money, creates a dependency on third parties

Most teams pick one and commit to it forever. Then they hit its limitations and have no easy way out.

The Multi-Provider Architecture

LaraSVG's core insight: decouple your application code from the converter. Your business logic should never care whether Resvg or Inkscape is doing the actual work.

// This code works with ANY provider
$result = SvgConverter::open('logo.svg')
    ->setFormat('png')
    ->setDimensions(1024, 1024)
    ->toFile('output/logo.png');

Under the hood, LaraSVG routes this to whichever provider is configured. Change your provider in config/larasvg.php, and nothing else changes.

Supported Providers

Provider Best For Speed Quality
Resvg Most use cases Fast Excellent
Inkscape PDF, complex SVGs Slower Best
rsvg-convert High-volume Linux workloads Very fast Good
CairoSVG Python environments Fast Good

Installing LaraSVG

composer require laratusk/larasvg
php artisan larasvg:setup

The setup command auto-detects which converters are available on your system and writes a sensible default configuration. No manual setup required.

Publish the config to customize:

php artisan vendor:publish --tag=larasvg-config

Common Conversion Patterns

Basic PNG Export

use Laratusk\Larasvg\Facades\SvgConverter;

SvgConverter::open('path/to/source.svg')
    ->setFormat('png')
    ->setDimensions(800, 600)
    ->toFile('output/result.png');

Streaming to an HTTP Response

This is the pattern we use most in production — no temp files, no cleanup:

public function downloadLogo(Request $request): Response
{
    return SvgConverter::open(storage_path('logos/brand.svg'))
        ->setFormat('png')
        ->setDimensions(2000, 2000)
        ->toStdout();
}

The output streams directly to the HTTP response without writing to disk. At scale, this matters — disk I/O is often the bottleneck, not the conversion itself.

Storing to Any Laravel Disk

LaraSVG integrates with Laravel's filesystem abstraction, so you can write directly to S3, GCS, or any configured disk:

SvgConverter::open('template.svg')
    ->setFormat('png')
    ->setDimensions(1200, 630)
    ->toDisk('s3', 'og-images/post-123.png');

Choosing a Provider Per Call

Sometimes you need different providers for different tasks. Inkscape for PDF, Resvg for PNG:

// High-quality PDF with Inkscape
SvgConverter::using('inkscape')
    ->open('diagram.svg')
    ->setFormat('pdf')
    ->toFile('exports/diagram.pdf');

// Fast PNG with Resvg
SvgConverter::using('resvg')
    ->open('icon.svg')
    ->setFormat('png')
    ->setDimensions(64, 64)
    ->toFile('icons/icon.png');

Handling SVG from User Input

One of our most common use cases is converting SVGs that users upload. A few things to keep in mind:

// Validate before converting
$request->validate([
    'file' => 'required|mimes:svg|max:2048',
]);

$path = $request->file('file')->store('uploads/svg', 'local');

// LaraSVG reads from storage paths
$result = SvgConverter::open(Storage::disk('local')->path($path))
    ->setFormat('png')
    ->setDimensions(1024, 1024)
    ->toFile(storage_path('app/exports/' . Str::uuid() . '.png'));

Always sanitize user-uploaded SVGs before processing. SVG files can contain embedded JavaScript. Consider using a sanitization library before passing files to LaraSVG.

Queueing Conversions

For high-volume or slow conversions (large files, PDF output, Inkscape), push to a queue:

class ConvertSvgJob implements ShouldQueue
{
    public function __construct(
        private string $sourcePath,
        private string $outputPath,
        private string $format = 'png',
    ) {}

    public function handle(): void
    {
        SvgConverter::open($this->sourcePath)
            ->setFormat($this->format)
            ->setDimensions(2000, 2000)
            ->toFile($this->outputPath);
    }
}

// Dispatch
ConvertSvgJob::dispatch(
    sourcePath: storage_path('app/uploads/logo.svg'),
    outputPath: storage_path('app/exports/logo.png'),
)->onQueue('conversions');

Performance at Scale

At 80 million+ conversions, performance details matter. A few things we've learned:

Resvg is your default. It starts in ~50ms vs Inkscape's ~800ms. For PNG output, Resvg quality is indistinguishable from Inkscape for 95% of SVGs.

Batch when you can. If you're generating the same SVG in multiple sizes (thumbnails, OG images, print resolution), do it in a single job rather than dispatching three separate ones.

Avoid disk writes on the hot path. Use toStdout() for HTTP responses. Disk writes add latency and wear.

Scale horizontally, not vertically. SVG conversion is embarrassingly parallel. Add more queue workers rather than a bigger server.

What's Next

We're actively developing:

  • WebP output — Already in progress, coming in the next minor release
  • Batch processing API — Convert multiple sizes in a single call
  • Async/queue-friendly helpers — First-class integration with Laravel's queue system
  • Image optimization — Post-processing hooks for PNG/JPEG compression

Star the repo on GitHub and follow along. Issues, PRs, and feedback are always welcome.

View on GitHubStar the repo, explore the source code, and get started.
Go to Repository
Share this post
Back to Blog