Oh Dear is the all-in-one monitoring tool for your entire website. We monitor uptime, SSL certificates, broken links, scheduled tasks and more. You'll get a notifications for us when something's wrong. All that paired with a developer friendly API and kick-ass documentation. O, and you'll also be able to create a public status page under a minute. Start monitoring using our free trial now.

Easily create PDFs in Laravel apps

Original – by Freek Van der Herten – 6 minute read

We’ve released a new package called spatie/laravel-pdf, a batteries-included package to generate PDFs in Laravel apps. Under the hood, it uses Chromium to generate PDFs from Blade views. You can use modern CSS features like grid, flexbox, and even frameworks like Tailwind to create beautiful PDFs.

In this post, I’d like to introduce and demonstrate the package.

Creating PDFs

Once the package has been installed, you can use the PDF facade to create PDFs. The basis for PDF creation is HTML, and the easiest way to generate HTML in a Laravel app is by using a view.

Here's an example of creating a PDF from a Blade view.

use Spatie\LaravelPdf\Facades\Pdf;

Pdf::view('pdf.invoice')->save('/some/directory/invoice.pdf');

Under the hood, the package will, via Browsershot, spin up an instance of Chrome, load your HTML, and let Chrome save a PDF. Because Chrome has a state-of-the-art rendering engine, you should be able to use modern CSS features to create your PDF.

As a second parameter, you can pass an array of data that will be made available in the view. You might use that to pass an Eloquent model, such as an invoice, to the view.

use Spatie\LaravelPdf\Facades\Pdf;

Pdf::view('pdf.invoice', ['invoice' => $invoice])
   ->save('/some/directory/invoice.pdf');

In addition to saving locally, you could save your PDF on any of your configured disks.

Here's an example of saving a PDF to the s3 disk.

use Spatie\LaravelPdf\Facades\Pdf;

Pdf::view('invoice')
   ->disk('s3')
   ->save('invoice-april-2022.pdf');

Instead of using a Blade view, you can also create a PDF from a string of HTML.

use Spatie\LaravelPdf\Facades\Pdf;

Pdf::html('<h1>Hello world!!</h1>')->save('/some/directory/invoice.pdf');

Responding with PDFs

In a controller, you can create and return a PDF using the pdf() function.

use function Spatie\LaravelPdf\Support\pdf;

class DownloadInvoiceController
{
    public function __invoke(Invoice $invoice)
    {
        return pdf()
            ->view('pdf.invoice', compact('invoice'))
            ->name('invoice-2023-04-10.pdf');
    }
}

By default, the PDF will be inlined in the browser. This means that the PDF will be displayed in the browser if the browser supports it. If the user tries to download the PDF, it will be named "invoice-2023-04-10.pdf". We recommend that you always name your PDFs.

You can use the download() method to force the PDF to be downloaded.

use function Spatie\LaravelPdf\Support\pdf;

class DownloadInvoiceController
{
    public function __invoke(Invoice $invoice)
    {
        return pdf()
            ->view('pdf.invoice', compact('invoice'))
            ->name('invoice-2023-04-10.pdf')
            ->download();
    }
}

Using JavaScript

The JavaScript in your HTML will be executed by Chrome when the PDF is created. You could use this to have a JavaScript charting library render a chart.

Here's a simple example. If you have this Blade view...

<div id="target"></div>

<script>
    document.getElementById('target').innerHTML = 'hello';
</script>

... and render it with this code...

use Spatie\LaravelPdf\Facades\Pdf;

Pdf::view('your-view')->save($pathToPdf);

... the text hello will be visible in the PDF.

Testing PDFs

Generating a PDF can be slow, as an entire instance of Chrome needs to be started. In your tests, this slowness can be bothersome. That’s why the package ships with a PDF fake. No PDF generation will occur when using this fake, and your tests will run faster.

// in your test

use Spatie\LaravelPdf\Facades\Pdf;

beforeEach(function () {
    Pdf::fake();
});

When PDF generation is faked, you can use some powerful assertion methods.

You can use the assertSaved method to assert that a PDF was saved with specific properties. You should pass it a callable which will receive an instance of Spatie\LaravelPdf\PdfBuilder. If the callable returns true, the assertion will pass.

use Spatie\LaravelPdf\Facades\Pdf;
use Spatie\LaravelPdf\PdfBuilder;

Pdf::assertSaved(function (PdfBuilder $pdf) {
    return $pdf->downloadName === 'invoice.pdf'
        && str_contains($pdf->html, 'Your total for April is $10.00'));
});

The assertRespondedWithPdf method can be used to assert that a PDF was generated and returned as a response.

Imagine you have this route:

use Spatie\LaravelPdf\Facades\Pdf;

Route::get('download-invoice', function () {
    return pdf('pdf.invoice')->download('invoice-for-april-2022.pdf');
});

In your test for this route you can use the assertRespondedWithPdf to ensure that a PDF was generated and returned as a download. You can even make assertions on the content of the PDF.

use Spatie\LaravelPdf\Facades\Pdf;
use Spatie\LaravelPdf\PdfBuilder;

it('can download an invoice', function () {
    $this
        ->get('download-invoice')
        ->assertOk();
        
    Pdf::assertRespondedWithPdf(function (PdfBuilder $pdf) {
        return $pdf->downloadName === 'invoice-for-april-2022.pdf'
            && $pdf->isDownload()
            && str_contains($pdf->html, 'Your total for April is $10.00'));
    });
});

Generating PDFs on AWS Lambda

Generating PDFs locally can be resource-intensive. If you're having to generate a lot of PDFs or having trouble installing the necessary dependencies on your server, you may want to consider using AWS Lambda to generate your PDFs.

To generate PDFs on AWS Lambda, you must install these two packages in your app.

  • hammerstone/sidecar: this allows you to execute AWS Lambda functions from your Laravel application
  • wnx/sidecar-browsershot: this package contains a version of Browsershot that can run on AWS Lambda via Sidecar

With these two packages installed, you can generate PDFs on AWS Lambda like this:

Pdf::view('pdf.invoice', $data)
    ->generateOnLambda()
    ->save('invoice.pdf');

If you want to create all PDFs in your app on Lambda, you can set it as a default like this:

// typically, in a service provider

Pdf::default()->generateOnLambda();

In closing

I had fun coding this package, and I hope you will enjoy using it. There are many options that I didn’t cover in this post, such as formatting PDFS, adding headers and footers, using page breaks, and much more!

Laravel PDF uses headless Chrome to generate PDFs. This is a great solution, as you can use any CSS you want, and it will be rendered correctly. However, generating a PDF this way can be resource-intensive. If you don’t like this trade-off, a great alternative to check out is laravel-dompdf.

Laravel PDF is just one of many packages that we've made. You'll find an extensive list of Laravel and PHP packages we released previously on our company website. There's probably something there for your next project. If you like our open-source stuff, be sure to also look at our paid products that make working on open-source sustainable for our company.

Stay up to date with all things Laravel, PHP, and JavaScript.

You can follow me on these platforms:

On all these platforms, regularly share programming tips, and what I myself have learned in ongoing projects.

Every month I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

What are your thoughts on "Easily create PDFs in Laravel apps"?

Comments powered by Laravel Comments
Want to join the conversation? Log in or create an account to post a comment.