Generate a professional PDF in Laravel or any PHP application (CodeIgniter)
One of the most useful features you can implement in your applications, especially those like my book sales applications, 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 completely, as I did, generating this content relies on custom styles, JavaScript interactions to format it, and more.
In this article, I show you how I solved this problem by combining in-browser rendering and final PDF generation from the backend in Laravel using DomPDF.
The Problem
I wanted to generate a PDF from a series of content blocks (let's call them "book sections") that are viewed 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 to be able to export that same content as a PDF with the final appearance, respecting the styles, structure, and order of the sections.
The main difficulty was that if I simply rendered a Blade view and passed it to DomPDF, I lost many of the advantages that we can have when formatting HTML content using JS, 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, once ready and the style applied, it looks beautiful.
Divide the process into three stages
The key to achieving what was discussed was to divide the process into three very clear stages:
1. Render content in the browser
Each section of the book is rendered in the browser. This render allows for the application of JavaScript logic, dynamic styles, interactive components, and anything else that can't be rendered directly from the backend.
At this stage, I leverage the full power of the browser to create each section with its final visual appearance. Once it looks exactly the way I want it on screen, I grab the HTML content from the main container and send it to the Laravel backend 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' ? 'Capítulo' : 'Chapter' }}
{{ $key + 1 }}</span>: {{ $s->title }}</h1>
{!! preg_replace('/\s*(width|height)="\d*"/i', '', $s->content) !!}
</section>
@endforeach
2. Saving sections (book sections)
Each HTML block representing a section is saved in a database. I assign an order to each section so I can later reconstruct the entire HTML document in the correct order.
At this stage, I don't attempt to generate the PDF yet. I focus only 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
// guarda el contenido del libro, cada seccion
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>`)
})
Additionally, having sections saved separately allows me to make adjustments, debug, or reorder content without affecting other parts of the flow.
3. Assembling and generating 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>";
// obtiene el BODY/foreach de TODOS los capitulos
foreach ($this->book->sections()->where('orden', '>', 0)->orderBy('orden')->get() as $s) {
$sectionHtml = $s->content_render;
// Limpiar etiquetas <html>, <head>, <body> usando 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;
// Elimina <body> tags dejando solo el contenido interior
$content = preg_replace('/<\/?body[^>]*>/', '', $content);
$html .= $content;
}
// cierra el doc HTML
$html .= '</body></html>';
//**** REEMPLAZOS del HTML
// DOOM PDF neceita que la url sea absoluta
$html = str_replace("/images/example/libros/", URL('/') . "/images/example/libros/", $html);
// elimino la referencia a chapterX.xhtml del formato epub
$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 give it the format you want, I use it to replace relative references with absolute ones for the images, remove the HEAD tag and HTML 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.
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 actual styles and structures the user sees are respected.
- The styling is fully customizable.
- I don't rely on a PDF engine to interpret complex CSS.
- The PDF is faithful to what the user sees, as it is based on pre-rendered HTML.
It is easy to debug, since each section can be reviewed individually.
I agree to receive announcements of interest about this Blog.
I'll explain in general terms the steps you need to follow to generate a PDF from a PHP web app.
- Andrés Cruz