AI

/laravel-agent:pdf:make

Create PDF generation feature using spatie/laravel-pdf or barryvdh/laravel-dompdf

Overview

The /pdf:make command generates complete PDF generation features with templates, controllers, and views. It supports both modern spatie/laravel-pdf (Browsershot-based with CSS Grid/Flexbox support) and the simpler barryvdh/laravel-dompdf package.

Usage

/laravel-agent:pdf:make [PDF Type] [options]

Examples

# Create an invoice PDF generator
/laravel-agent:pdf:make Invoice

# Create a report with DomPDF
/laravel-agent:pdf:make Report --package=dompdf

# Create a certificate PDF
/laravel-agent:pdf:make Certificate

# Create a contract PDF
/laravel-agent:pdf:make Contract

What Gets Created

A complete PDF generation feature includes the following components:

Component Location Description
PDF Generator Class app/Pdf/ Main PDF generator with download, stream, and save methods
PDF Controller app/Http/Controllers/ Controller with download and stream endpoints
Main Template resources/views/pdf/[type]/template.blade.php Primary PDF layout and content
Header Template resources/views/pdf/[type]/header.blade.php Optional PDF header (Spatie only)
Footer Template resources/views/pdf/[type]/footer.blade.php Optional PDF footer (Spatie only)
Routes routes/web.php Download and view routes
Tests tests/Feature/ Feature tests for PDF generation

Package Options

The command supports two PDF generation packages:

Spatie Laravel PDF (Default)

  • Modern - Uses Chrome/Chromium for rendering via Browsershot
  • CSS Support - Full CSS Grid, Flexbox, and modern layout support
  • Headers/Footers - Separate header and footer templates
  • High Quality - Best for complex layouts and modern designs

Barryvdh DomPDF

  • Simple - No external dependencies, pure PHP
  • Lightweight - No Chromium installation required
  • Basic CSS - Limited CSS support, best for simple layouts
  • Easy Deploy - Works on any shared hosting

Example Output Structure

For /laravel-agent:pdf:make Invoice:

app/
├── Pdf/
│   └── InvoicePdf.php
└── Http/
    └── Controllers/
        └── InvoicePdfController.php

resources/views/pdf/
└── invoice/
    ├── template.blade.php
    ├── header.blade.php
    └── footer.blade.php

routes/
└── web.php (updated)

tests/Feature/
└── InvoicePdfTest.php

Generated PDF Class (Spatie)

<?php

declare(strict_types=1);

namespace App\Pdf;

use App\Models\Invoice;
use Spatie\LaravelPdf\Facades\Pdf;
use Spatie\LaravelPdf\PdfBuilder;

final class InvoicePdf
{
    public function __construct(
        private readonly Invoice $invoice,
    ) {}

    public function generate(): PdfBuilder
    {
        return Pdf::view('pdf.invoice.template', [
            'invoice' => $this->invoice,
            'company' => config('app.company'),
        ])
        ->format('a4')
        ->headerView('pdf.invoice.header', ['invoice' => $this->invoice])
        ->footerView('pdf.invoice.footer');
    }

    public function download(): \Symfony\Component\HttpFoundation\Response
    {
        return $this->generate()
            ->name("invoice-{$this->invoice->number}.pdf")
            ->download();
    }

    public function save(string $path): void
    {
        $this->generate()->save($path);
    }

    public function stream(): \Symfony\Component\HttpFoundation\Response
    {
        return $this->generate()
            ->name("invoice-{$this->invoice->number}.pdf")
            ->inline();
    }
}

Generated PDF Class (DomPDF)

<?php

declare(strict_types=1);

namespace App\Pdf;

use App\Models\Invoice;
use Barryvdh\DomPDF\Facade\Pdf;

final class InvoicePdf
{
    public function __construct(
        private readonly Invoice $invoice,
    ) {}

    public function generate(): \Barryvdh\DomPDF\PDF
    {
        return Pdf::loadView('pdf.invoice.template', [
            'invoice' => $this->invoice,
            'company' => config('app.company'),
        ])
        ->setPaper('a4', 'portrait')
        ->setOptions([
            'isHtml5ParserEnabled' => true,
            'isRemoteEnabled' => true,
        ]);
    }

    public function download(): \Symfony\Component\HttpFoundation\Response
    {
        return $this->generate()->download("invoice-{$this->invoice->number}.pdf");
    }

    public function stream(): \Symfony\Component\HttpFoundation\Response
    {
        return $this->generate()->stream("invoice-{$this->invoice->number}.pdf");
    }

    public function save(string $path): void
    {
        $this->generate()->save($path);
    }
}

Generated Controller

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\Invoice;
use App\Pdf\InvoicePdf;
use Illuminate\Http\Request;

final class InvoicePdfController extends Controller
{
    public function download(Invoice $invoice)
    {
        $this->authorize('view', $invoice);

        return (new InvoicePdf($invoice))->download();
    }

    public function stream(Invoice $invoice)
    {
        $this->authorize('view', $invoice);

        return (new InvoicePdf($invoice))->stream();
    }
}

Generated Blade Template

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Invoice #</title>
    <style>
        * { font-family: 'DejaVu Sans', sans-serif; }
        body { font-size: 12px; line-height: 1.4; color: #333; }
        .header { border-bottom: 2px solid #333; padding-bottom: 20px; margin-bottom: 20px; }
        .company-name { font-size: 24px; font-weight: bold; }
        table { width: 100%; border-collapse: collapse; }
        th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
        th { background: #f5f5f5; }
        .total-row { font-weight: bold; font-size: 14px; }
        .text-right { text-align: right; }
        .footer { margin-top: 40px; font-size: 10px; color: #666; text-align: center; }
    </style>
</head>
<body>
    <div class="header">
        <div class="company-name"></div>
        <div></div>
    </div>

    <h1>Invoice #</h1>
    <p><strong>Date:</strong> </p>
    <p><strong>Due:</strong> </p>

    <h2>Bill To</h2>
    <p><br></p>

    <table>
        <thead>
            <tr>
                <th>Description</th>
                <th class="text-right">Qty</th>
                <th class="text-right">Price</th>
                <th class="text-right">Total</th>
            </tr>
        </thead>
        <tbody>
            @foreach($invoice->items as $item)
            <tr>
                <td></td>
                <td class="text-right"></td>
                <td class="text-right"></td>
                <td class="text-right"></td>
            </tr>
            @endforeach
            <tr class="total-row">
                <td colspan="3" class="text-right">Total</td>
                <td class="text-right"></td>
            </tr>
        </tbody>
    </table>

    <div class="footer">
        <p>Thank you for your business!</p>
    </div>
</body>
</html>

Generated Routes

<?php

Route::middleware('auth')->group(function () {
    Route::get('/invoices/{invoice}/pdf', [InvoicePdfController::class, 'download'])
        ->name('invoices.pdf.download');
    Route::get('/invoices/{invoice}/pdf/view', [InvoicePdfController::class, 'stream'])
        ->name('invoices.pdf.stream');
});

Usage Examples

<?php

// Download PDF
return (new InvoicePdf($invoice))->download();

// Stream (view in browser)
return (new InvoicePdf($invoice))->stream();

// Save to storage
(new InvoicePdf($invoice))->save(storage_path('app/pdfs/invoice-123.pdf'));

Best Practices

  1. Choose the right package - Use Spatie for modern layouts, DomPDF for simple documents
  2. Use web-safe fonts - DejaVu Sans is included with both packages
  3. Test PDF rendering - Different packages render CSS differently
  4. Optimize images - Large images increase PDF file size significantly
  5. Add authorization - Always verify users can access the PDF data
  6. Consider async generation - Queue large PDFs to avoid timeouts

Common PDF Types

  • Invoice - Billing documents with line items and totals
  • Report - Analytics, summaries, and data visualizations
  • Certificate - Completion certificates, awards, diplomas
  • Contract - Legal agreements, terms and conditions
  • Receipt - Purchase confirmations and transaction records
  • Label - Shipping labels, product labels, badges

Next Steps

  1. Customize the PDF template in resources/views/pdf/[type]/
  2. Add company configuration to config/app.php
  3. Test with sample data to verify rendering
  4. Add custom fonts if needed (see package documentation)
  5. Configure paper size and orientation in the generator class

See Also