Generate PDFs in Laravel or any PHP application (CodeIgniter) with Dompdf
Content Index
- Difficulties of Generating a PDF in PHP
- Options for generating PDFs in Laravel
- DomPDF: the most used option
- Installation and basic use of dompdf
- Spatie Laravel PDF and Browsershot
- Disadvantages
- The real problem when generating PDFs from Blade
- Real limitations of CSS and JavaScript in DomPDF
- My approach to generating PDFs faithful to the HTML content
- Render the content in the browser
- How I Generated My Book: Dividing the Process into Three Stages
- 1. Rendering the content in the browser
- 2. Saving sections (book sections)
- Save the final HTML by sections
- 3. Assembly and generation of the PDF
- Benefits of this approach
- Generating complex PDFs in Laravel step by step
- Advantages of separating display and export
- When to use DomPDF and when to use Spatie PDF
- Common errors when generating PDFs in Laravel
- Frequently asked questions about generating PDFs in Laravel
- Conclusion
One of the most useful features you can implement in your applications, especially those like mine that sell books, is the ability to generate PDF documents from HTML content. This HTML content is my books, but in your case, it can be anything.
However, if you want to do it the complete way, as I did, generating this content depends on custom styles, JavaScript interactions to give it the necessary format, among other things.
Generating PDFs in Laravel is one of those features that sooner or later appear in any serious project: invoices, reports, certificates... or, as in my case, complete books generated from HTML content.
At first, everything seems simple: a Blade view, DomPDF, and you're done. The problem comes when the content depends on JavaScript, dynamic styles, or frontend plugins. That's where the classic approach starts to break down.
In this guide, I explain all the options for generating PDFs in Laravel, their real limits, and the approach I used in production to generate PDFs identical to what the user sees on the screen.
In this article, I show you how I solved that problem by combining browser rendering and final PDF generation from the backend in Laravel using DomPDF.
Difficulties of Generating a PDF in PHP
I wanted to generate a PDF from a series of content blocks (let's call them "book sections") that are displayed and edited in an educational application. Each section is composed of HTML that is rendered in the browser with its styles and scripts. The goal was for that same content to be exportable as a PDF with the final appearance, respecting the styles, structures, and order of the sections.
The main difficulty was that if I simply rendered a Blade view and passed it to DomPDF, I would lose many of the advantages we can have when JS formats the HTML content. In my case, the highlights plugin to format the code. Therefore, I had to implement additional logic to render the HTML content and save it already rendered, ready and with the style applied, it looks beautiful.
Options for generating PDFs in Laravel
Laravel does not generate PDFs natively, but the ecosystem offers very powerful libraries. The two most used today are DomPDF and Spatie Laravel PDF.
DomPDF: the most used option
DomPDF, through the barryvdh/laravel-dompdf package, is the most popular option for generating PDFs in Laravel.
What it does well:
- Easily converts HTML to PDF
- Works perfectly with Blade views
- Ideal for simple or medium-sized documents
- Very quick to implement
Installation and basic use of dompdf
The command is:
$ composer require barryvdh/laravel-dompdfAnd its basic use:
use Barryvdh\DomPDF\Facade\Pdf;
public function generatePdf()
{
return Pdf::loadView('pdf.example')
->download('document.pdf');
}Where it starts to fail
As soon as the HTML:
- Depends on JavaScript
- Uses complex CSS
- Is rendered dynamically on the frontend
When I tried this approach, I lost all the formatting applied by JS, such as code highlighting or dynamically generated structures.
Spatie Laravel PDF and Browsershot
Spatie offers a different approach: it uses Chromium via Browsershot, which means the PDF is generated from a real browser.
Clear advantages:
- Supports JavaScript
- Modern CSS (Grid, Flexbox, Tailwind)
- The PDF is faithful to the browser's render
$ composer require spatie/laravel-pdfuse Spatie\LaravelPdf\Facades\Pdf;
return Pdf::view('pdf.invoice', $data)
->download('invoice.pdf');Disadvantages
- Higher resource consumption
- Dependency on Chromium
- Less fine control when the document is very large or in sections
The real problem when generating PDFs from Blade
Here is the point that classic tutorials don't explain well.
When HTML is generated only from Blade:
- There is no JavaScript
- CSS has limitations
- The result rarely matches what the user sees
Working with an educational application for selling books, each chapter:
- Is rendered in the browser
- Applies dynamic styles
- Uses JavaScript plugins (code highlighting, visual adjustments)
When I tried to generate the PDF directly from a Blade view, the result was nothing like the final version.
Real limitations of CSS and JavaScript in DomPDF
DomPDF:
- Does not execute JavaScript
- Supports limited CSS
- Does not interpret dynamic interactions
That's why using it directly on Blade views only works for static HTML.
My approach to generating PDFs faithful to the HTML content
The solution was to completely separate the display from the export.
That was the turning point of the project.
Instead of “Blade → PDF”, I designed a three-stage flow.
Render the content in the browser
First, each section of the book is rendered in the browser.
Here I take full advantage of the frontend's power:
- JavaScript
- Plugins
- Dynamic styles
- Final visual adjustments
When the content looks exactly as I want, I capture the final HTML of the container.
At that moment it is no longer “theoretical HTML”, it is real, processed, and definitive HTML.
How I Generated My Book: Dividing the Process into Three Stages
The key to achieving what was mentioned was to divide the process into three very clear stages:
1. Rendering the content in the browser
Each section of the book is displayed in the browser. This rendering allows applying JavaScript logic, dynamic styles, interactive components, and everything that cannot be rendered directly from the backend.
At this stage, I take full advantage of the browser's power to give each section its final visual appearance. Once it looks just as I want it on the screen, I take the HTML content of the main container and send it to the backend in Laravel to save it.
html.blade.php
@foreach ($book->sections()->where('orden', '>=', 1)->get() as $key => $s)
<section class="page-break book-chapter" id="{{ $s->id }}"
data-chapter="chapter{{ $key + 1 }}.xhtml">
<h1><span class="underline">{{ $book->language == 'spanish' ? 'Chapter' : 'Chapter' }}
{{ $key + 1 }}</span>: {{ $s->title }}</h1>
{!! preg_replace('/\s*(width|height)="\d*"/i', '', $s->content) !!}
</section>
@endforeach2. Saving sections (book sections)
Each block of HTML that represents a section is saved in the database. I assign an order to each section, so I can later reconstruct the complete HTML document in the correct order.
At this stage, I don't try to generate the PDF yet. I only focus on ensuring that the final content—already transformed and ready—is saved as static HTML. This allows me to completely decouple the display process from the export process.
html.blade.php
// saves the content of the book, each section
document.documentElement.querySelectorAll(".book-chapter").forEach((s) => {
axios.post("{{ route('web-book-generate-section-save', 55) }}".replace(55, s.id), {
//"document": `${s.outerHTML}`
"document": `<html>${document.documentElement.querySelector("head").outerHTML}<body>${s.outerHTML}${document.documentElement.querySelector("footer").outerHTML}</body></html>`
})
// console.log(`${s.outerHTML}</body></html>`)
})In addition, having the sections saved separately allows me to make adjustments, debug, or reorder the content without affecting other parts of the flow.
Save the final HTML by sections
Each chapter or section:
- Is saved as static HTML in the database
- Has a defined order
- Can be debugged independently
I can correct one section without touching the rest of the document.
In addition, this decoupling allowed me not to depend on the PDF engine to interpret complex CSS.
3. Assembly and generation of the PDF
Then, with all the content ready, I use the DomPDF package in Laravel (although it is a package for PHP), and the downloadable file is generated.
$html = "<html>";
// gets the BODY/foreach of ALL the chapters
foreach ($this->book->sections()->where('orden', '>', 0)->orderBy('orden')->get() as $s) {
$sectionHtml = $s->content_render;
// Clean <html>, <head>, <body> tags using DOMDocument
$doc = new \DOMDocument();
@$doc->loadHTML(mb_convert_encoding($sectionHtml, 'HTML-ENTITIES', 'UTF-8'));
$body = $doc->getElementsByTagName('body')->item(0);
$content = $body ? $doc->saveHTML($body) : $sectionHtml;
// Removes <body> tags leaving only the inner content
$content = preg_replace('/<\/?body[^>]*>/', '', $content);
$html .= $content;
}
// closes the HTML doc
$html .= '</body></html>';
//**** HTML REPLACEMENTS
// DOOM PDF needs the url to be absolute
$html = str_replace("/images/example/libros/", URL('/') . "/images/example/libros/", $html);
// I remove the reference to chapterX.xhtml from the epub format
$html = preg_replace('/chapter\d+\.xhtml#/', '#', $html);
// dd($html );
$pdf = Pdf::loadHTML($html)
->set_option("isRemoteEnabled", true)
->set_option('isHtml5ParserEnabled', true)
->set_option('isPhpEnabled', true)
->setPaper('letter', 'portrait');This last step, you can give it the format you want. I use it to replace relative references with absolute ones for images, remove the HEAD and HTML tags from each of the sections, which I register as a complete HTML document, among others, to finally export it as you can see in the previous code.
With all the content already processed, the backend just does its job:
- Retrieve sections in order
- Clean up duplicate tags
- Assemble the final HTML
- Generate the PDF with DomPDF
Here I use DOMDocument to remove repeated <html>, <head>, and <body> tags, normalize the content, and ensure compatibility.
The result is a PDF identical to what the user sees on the screen, with no surprises.
Benefits of this approach
This flow gave me several clear advantages:
- I can use the browser to render content with JavaScript before saving it.
- The real styles and structures that the user sees are respected.
- COMPLETELY customizable in style.
- I don't depend on a PDF engine to interpret complex CSS.
- The PDF is faithful to what the user sees, as it is based on already processed HTML.
It's easy to debug, as each section can be reviewed individually.
Generating complex PDFs in Laravel step by step
Summarizing the complete flow:
- Rendering the content in the browser
- JavaScript is executed
- Final styles are applied
- The visual result is validated
- Persistence of the processed HTML
- Already rendered HTML
- Saved by sections
- Totally decoupled
- Final generation of the PDF
- Document assembly
- Replacement of relative paths
- Export with DomPDF
This approach scales very well when the PDF grows or becomes complex.
Advantages of separating display and export
This system gave me very clear advantages:
- I use JavaScript without limitations
- The PDF respects the final design exactly
- Debugging by sections
- Cleaner architecture
- Greater control of the result
In real projects, this difference is huge.
When to use DomPDF and when to use Spatie PDF
Use DomPDF if:
- The HTML is already processed
- You need fine control
- You generate large PDFs
- You want fewer dependencies
Use Spatie PDF if:
- You want 100% browser rendering
- The document is not huge
- You don't mind depending on Chromium
In my case, DomPDF was perfect once the frontend rendering was separated.
Common errors when generating PDFs in Laravel
Some very common errors:
- Relying on DomPDF to execute JavaScript
- Not using absolute paths for images
- Generating giant PDFs in a single view
- Mixing visual rendering and export
Avoiding these errors makes the difference between an “acceptable” PDF and a professional one.
Tips for large PDFs and real projects
- Divide the content by sections
- Save already rendered HTML
- Avoid complex logic in the PDF
- Debug each part separately
- Think in terms of architecture, not just code
Frequently asked questions about generating PDFs in Laravel
- What is the best library for generating PDFs in Laravel?
- It depends on the case. DomPDF for control and scalability, Spatie PDF for full browser rendering.
- Does DomPDF support JavaScript?
- No. That's why it's key to work with already processed HTML.
- How to generate PDFs faithful to the final design?
- By separating the frontend rendering from the backend export.
- Can large PDFs be generated without problems?
- Yes, if the content is divided by sections and assembled correctly.
Conclusion
Generating PDFs in Laravel goes far beyond converting a Blade view into a downloadable file.
When the content is dynamic, complex, or depends on JavaScript, the classic approach falls short. In my experience, decoupling display and export is the key to generating professional, scalable, and faithful-to-the-real-design PDFs.
If you are building something more than a basic PDF, this approach will save you a lot of headaches.
I agree to receive announcements of interest about this Blog.
Learn how to generate PDFs in Laravel using DomPDF and rendered HTML. A real solution for PDFs that accurately reflect your design, even with complex JavaScript and CSS.