Using Browsershot for PDF generation

by Jeffrey van Rossum

Yesterday I was trying to tweak the invoice design for Addrow, a SaaS application I made with Laravel. I have basically two templates for the invoice, one for web display and a PDF-version.

I tried to use one template for both versions, but using Dompdf I found myself having to tweak quite a lot to get the PDF anywhere near the HTML-version so ended up with two separate versions.

As I was trying to tweak the design, I decided to look for alternatives to Dompdf and try to get to a situation where I only needed to maintain one template file. I then stumbled upon this tweet by David Hemphill.

Using Spatie's Browsershot Package

And that sort of blew my mind. David is using Spatie's Browsershot package to utilize the headless version of Chrome and generate a PDF download that way. A massive advantage is the template reusability and the PDF looks pretty darn close to the web-version. I can also use TailwindCSS to style the invoice. Pretty cool!

The package requires you to have Node and the Puppeteer library installed. But that is pretty easy to install. The package readme file, even includes instructions how to install this on a Forge provisioned server which is exactly my use case.

What does the code look like?

You can get the idea from David's screenshot, but I thought it would be nice to share some more code to give an indication of how I implemented this. I have added a pdf method on my Invoice class like this:

public function pdf()
{
    $content = view('templates.invoice', ['invoice' => $this])->render();

    return Browsershot::html($content)
        ->margins(18, 18, 24, 18)
        ->format('A4')
        ->showBackground()
        ->pdf();
}

The generated HTML is just a Blade rendered template. Then, in my InvoicesController I can do the following to stream the PDF to the browser:

public function pdf($id)
{
    $invoice = Invoice::findOrFail($id);

    $this->authorize('view', $invoice);

    return response()->stream(function () use ($invoice) {
        echo $invoice->pdf();
    }, 200, ['Content-Type' => 'application/pdf']);
}

To add this PDF as an email attachment, I use the attachData method on the Mailable class.

public function build()
{
    return $this->from(config('mail.from.address'))
        ->replyTo($this->invoice->profile->email)
        ->markdown('mail.invoice')
        ->subject(__("Your invoice is ready"))
        ->attachData($this->invoice->pdf(), __('Invoice :number', ['number' => $this->invoice->invoice_number]) . '.pdf');
}

Conclusion

In the end I am very happy with this refactor. I can now use one template and use TailwindCSS for styling. Thanks to Spatie for providing this package and David for mentioning this on Twitter. Also, thanks to Dompdf since I've used this for years and in a lot cases it might very well still be the way to go.

Wouldn't you like for invoicing to be just done quick and simple? Addrow does just that. You can create invoices and send them as a PDF to your customers by email, send reminders automatically, include payment links and much more! And yes, there is an API too. Try it out for free

Comments

You can post a comment to this article by replying to this tweet.
Did you like this post?

If you sign up for my newsletter, I can keep you up to date on more posts like this when they are published.