Generar PDFs en Laravel o cualquier aplicación en PHP (CodeIgniter) con Dompdf

Video thumbnail

Una de las funcionalidades más útiles que puedes implementar en tus aplicaciones, sobre todo como las mías de venta de libros, es la de poder generar documentos PDF a partir de contenido HTML, este contenido HTML son mis libros, pero en tu caso, puedes ser cualquier cosa. 

Sin embargo, si lo quieres hacer de la manera completa, como hice yo, para generar este contenido depende de estilos personalizados, interacciones en JavaScript para darle el formato que se necesita entre otros.

Generar PDFs en Laravel es una de esas funcionalidades que tarde o temprano aparecen en cualquier proyecto serio: facturas, reportes, certificados… o, como en mi caso, libros completos generados desde contenido HTML.

Al principio todo parece sencillo: una vista Blade, DomPDF y listo. El problema llega cuando el contenido depende de JavaScript, estilos dinámicos o plugins de frontend. Ahí es donde el enfoque clásico empieza a romperse.

En esta guía te explico todas las opciones para generar PDFs en Laravel, sus límites reales y el enfoque que utilicé en producción para generar PDFs idénticos a lo que el usuario ve en pantalla.

En este artículo te muestro cómo resolví ese problema combinando render en navegador y generación final de PDF desde el backend en Laravel usando DomPDF.

Dificultades de Generar un PDF en PHP

Quería generar un PDF desde una serie de bloques de contenido (llamémoslos "secciones de libro") que se visualizan y editan en una aplicación educativa. Cada sección está compuesta por HTML que se renderiza en el navegador con sus estilos y scripts. El objetivo era que ese mismo contenido se pudiera exportar como PDF con la apariencia final, respetando los estilos, estructuras y orden de las secciones.

La dificultad principal estaba en que si simplemente renderizaba una vista Blade y la pasaba a DomPDF, perdía muchas de las ventajas que podemos tener cuando mediante JS se da formato a el contenido HTML, en mi caso, el plugin de highlights para darle formato al código, por lo tanto, tuve que implementar una lógica adicional para renderizar el contenido HTML y guardarlo ya renderizado, ya listo y aplicado el estilo queda bellísimo.

Opciones para generar PDFs en Laravel

Laravel no genera PDFs de forma nativa, pero el ecosistema ofrece librerías muy potentes. Las dos más usadas hoy son DomPDF y Spatie Laravel PDF.

DomPDF: la opción más usada

DomPDF, a través del paquete barryvdh/laravel-dompdf, es la opción más popular para generar PDFs en Laravel.

Qué hace bien:

  • Convierte HTML en PDF fácilmente
  • Funciona perfectamente con vistas Blade
  • Ideal para documentos simples o medianos
  • Muy rápido de implementar

Instalación y uso básico dompdf

El comando es:

$ composer require barryvdh/laravel-dompdf

Y su uso básico:

use Barryvdh\DomPDF\Facade\Pdf;
public function generarPdf()
{
   return Pdf::loadView('pdf.ejemplo')
       ->download('documento.pdf');
}

Dónde empieza a fallar

En cuanto el HTML:

  • Depende de JavaScript
  • Usa CSS complejo
  • Se renderiza dinámicamente en frontend

Al probar este enfoque, perdía todo el formato aplicado por JS, como el resaltado de código o estructuras generadas dinámicamente.

Spatie Laravel PDF y Browsershot

Spatie ofrece un enfoque distinto: usa Chromium vía Browsershot, lo que significa que el PDF se genera desde un navegador real.

Ventajas claras:

  • Soporta JavaScript
  • CSS moderno (Grid, Flexbox, Tailwind)
  • El PDF es fiel al render del navegador
$ composer require spatie/laravel-pdf
use Spatie\LaravelPdf\Facades\Pdf;
return Pdf::view('pdf.factura', $data)
   ->download('factura.pdf');

Inconvenientes

  • Mayor consumo de recursos
  • Dependencia de Chromium
  • Menos control fino cuando el documento es muy grande o por secciones

El problema real al generar PDFs desde Blade

Aquí está el punto que no explican bien los tutoriales clásicos.

Cuando el HTML se genera únicamente desde Blade:

  • No hay JavaScript
  • El CSS tiene limitaciones
  • El resultado rara vez coincide con lo que ve el usuario

Trabajando con una aplicación educativa para venta de libros, cada capítulo:

  • Se renderiza en el navegador
  • Aplica estilos dinámicos
  • Usa plugins JavaScript (highlight de código, ajustes visuales)

Cuando intenté generar el PDF directamente desde una vista Blade, el resultado no se parecía en nada a la versión final.

Limitaciones reales de CSS y JavaScript en DomPDF

DomPDF:

  • No ejecuta JavaScript
  • Soporta CSS limitado
  • No interpreta interacciones dinámicas

Por eso, usarlo directamente sobre vistas Blade solo funciona para HTML estático.

Mi enfoque para generar PDFs fieles al contenido HTML

La solución fue separar completamente la visualización de la exportación.

Ese fue el punto de inflexión del proyecto.

En lugar de “Blade → PDF”, diseñé un flujo en tres etapas.

Renderizar el contenido en el navegador

Primero, cada sección del libro se renderiza en el navegador.

Aquí aprovecho todo el poder del frontend:

  • JavaScript
  • Plugins
  • Estilos dinámicos
  • Ajustes visuales finales

Cuando el contenido se ve exactamente como quiero, capturo el HTML final del contenedor.

En ese momento ya no es “HTML teórico”, es HTML real, procesado y definitivo.

Como Generé mi libro: Dividir el proceso en tres etapas

La clave para lograr lo comentado, fue dividir el proceso en tres etapas muy claras:

1. Render del contenido en el navegador

Cada sección del libro es visualizada en el navegador. Este render permite aplicar lógica JavaScript, estilos dinámicos, componentes interactivos y todo lo que no se puede renderizar directamente desde el backend.

En esta etapa aprovecho todo el poder del navegador para dejar cada sección con su aspecto visual final. Una vez que se ve tal como quiero en pantalla, tomo el contenido HTML del contenedor principal y lo envío al backend en Laravel para guardarlo.

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. Guardado de secciones (book sections)

Cada bloque de HTML que representa una sección se guarda en base de datos. A cada sección le asigno un orden, para poder luego reconstruir el documento HTML completo en el orden correcto.

En esta etapa no intento generar el PDF aún. Solo me enfoco en asegurar que el contenido final —ya transformado y listo— quede guardado como HTML estático. Esto me permite desacoplar completamente el proceso de visualización del de exportación.

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>`)
})

Además, tener las secciones guardadas por separado me permite hacer ajustes, debug o reordenar el contenido sin afectar otras partes del flujo.

Guardar el HTML final por secciones

Cada capítulo o sección:

  • Se guarda como HTML estático en base de datos
  • Tiene un orden definido
  • Puede debuggearse de forma independiente

Puedo corregir una sección sin tocar el resto del documento.

Además, este desacoplamiento me permitió no depender del motor de PDF para interpretar CSS complejo.

3. Ensamblado y generación del PDF

Luego, con todo el contenido listo, uso el paquete de DomPDF en Laravel (aunque es un paquete para PHP), y se genera el archivo descargable.

 $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');

Este último paso, ya le das el formato que quieras, yo lo uso para reemplazar referencias relativas a absolutas para las imágenes, remover el la etiqueta HEAD y HTML de cada uno de las secciones, que las registro como un documento HTML completo, entre otros para finalmente exportarlo tal cual puedes ver en el código anterior.

Con todo el contenido ya procesado, el backend solo hace su trabajo:

  • Recuperar secciones en orden
  • Limpiar etiquetas duplicadas
  • Ensamblar el HTML final
  • Generar el PDF con DomPDF

Aquí uso DOMDocument para eliminar <html>, <head> y <body> repetidos, normalizar el contenido y asegurar compatibilidad.

El resultado es un PDF idéntico a lo que el usuario ve en pantalla, sin sorpresas.

Beneficios de este enfoque

Este flujo me dio varias ventajas claras:

  1. Puedo usar el navegador para renderizar contenido con JavaScript antes de guardarlo.
  2. Se respetan los estilos y estructuras reales que ve el usuario.
  3. COMPLETAMENTE personalizable en el estilo.
  4. No dependo de un motor de PDF para interpretar CSS complejo.
  5. El PDF es fiel a lo que ve el usuario, ya que se basa en HTML ya procesado.

Es fácil debuggear, ya que cada sección puede revisarse de forma individual.

Generar PDFs complejos en Laravel paso a paso

Resumiendo el flujo completo:

  1. Render del contenido en el navegador
    1. Se ejecuta JavaScript
    2. Se aplican estilos finales
    3. Se valida el resultado visual
  2. Persistencia del HTML procesado
    1. HTML ya renderizado
    2. Guardado por secciones
    3. Totalmente desacoplado
  3. Generación final del PDF
    1. Ensamblado del documento
    2. Reemplazo de rutas relativas
    3. Exportación con DomPDF

Este enfoque escala muy bien cuando el PDF crece o se vuelve complejo.

Ventajas de separar visualización y exportación

Este sistema me dio ventajas muy claras:

  • Uso JavaScript sin limitaciones
  • El PDF respeta exactamente el diseño final
  • Debug por secciones
  • Arquitectura más limpia
  • Mayor control del resultado

En proyectos reales, esta diferencia es enorme.

Cuándo usar DomPDF y cuándo usar Spatie PDF

Usa DomPDF si:

  • El HTML ya está procesado
  • Necesitas control fino
  • Generas PDFs grandes
  • Quieres menos dependencias

Usa Spatie PDF si:

  • Quieres render 100% navegador
  • El documento no es gigante
  • No te importa depender de Chromium

En mi caso, DomPDF fue perfecto una vez separado el render del frontend.

Errores comunes al generar PDFs en Laravel

Algunos errores muy habituales:

  • Confiar en DomPDF para ejecutar JavaScript
  • No usar rutas absolutas para imágenes
  • Generar PDFs gigantes en una sola vista
  • Mezclar render visual y exportación

Evitar estos errores marca la diferencia entre un PDF “aceptable” y uno profesional.

Consejos para PDFs grandes y proyectos reales

  • Divide el contenido por secciones
  • Guarda HTML ya renderizado
  • Evita lógica compleja en el PDF
  • Debuggea cada parte por separado
  • Piensa en arquitectura, no solo en código

Preguntas frecuentes sobre generar PDFs en Laravel

  • ¿Cuál es la mejor librería para generar PDFs en Laravel?
    • Depende del caso. DomPDF para control y escalabilidad, Spatie PDF para render completo en navegador.
  • ¿DomPDF soporta JavaScript?
    • No. Por eso es clave trabajar con HTML ya procesado.
  • ¿Cómo generar PDFs fieles al diseño final?
    • Separando el render frontend de la exportación backend.
  • ¿Se pueden generar PDFs grandes sin problemas?
    • Sí, si el contenido se divide por secciones y se ensambla correctamente.

Conclusión

Generar PDFs en Laravel va mucho más allá de convertir una vista Blade en un archivo descargable.

Cuando el contenido es dinámico, complejo o depende de JavaScript, el enfoque clásico se queda corto. En mi experiencia, desacoplar visualización y exportación es la clave para generar PDFs profesionales, escalables y fieles al diseño real.

Si estás construyendo algo más que un PDF básico, este enfoque te va a ahorrar muchos dolores de cabeza.

Acepto recibir anuncios de interes sobre este Blog.

Aprende cómo generar PDFs en Laravel usando DomPDF y HTML renderizado. Solución real para PDFs fieles al diseño, con JavaScript y CSS complejo.

| 👤 Andrés Cruz

🇺🇸 In english