Generate PDF in Laravel using DomPDF

CodeWithSdCodeCodeWithSdCode
4 min read

🧾 Introduction

In this tutorial, we’ll build a simple Laravel application that allows users to create multiple product orders with name, description, quantity, price, and image. On form submission, a detailed PDF receipt is generated using DomPDF with subtotals and grand total.

This is perfect for creating order receipts, restaurant bills, or sales invoices.

🔧 Prerequisites

  • Laravel 10 installed

  • Basic understanding of routes, controllers, and views

  • Composer installed

📦 Step 1: Install DomPDF Package

Run the following command:

composer require barryvdh/laravel-dompdf

Optionally, publish the config:

php artisan vendor:publish --provider="Barryvdh\DomPDF\ServiceProvider"

config/app.php

 'aliases' => Facade::defaultAliases()->merge([
        // 'Example' => App\Facades\Example::class,
        'Pdf' => Barryvdh\DomPDF\Facade\Pdf::class,
    ])->toArray(),

🛣️ Step 2: Define Routes

Edit your routes/web.php:

use App\Http\Controllers\OrderPDFController;

Route::get('/order', [OrderPDFController::class, 'showForm'])->name('order.form');
Route::post('/generate-pdf', [OrderPDFController::class, 'generatePDF'])->name('generate.pdf');

🧠 Step 3: Create the Controller

Run:

php artisan make:controller OrderPDFController

Then, in app/Http/Controllers/OrderPDFController.php:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;

class OrderPDFController extends Controller
{
    public function showForm()
    {
        return view('order.form');
    }

    public function generatePDF(Request $request)
    {
        $request->validate([
            'customer_name' => 'required|string|max:255',
            'products' => 'required|array|min:1',
            'products.*.name' => 'required|string|max:255',
            'products.*.description' => 'required|string',
            'products.*.quantity' => 'required|integer|min:1',
            'products.*.price' => 'required|numeric|min:1',
            'products.*.image' => 'nullable|image|mimes:jpg,jpeg,png|max:2048',
        ]);

        $products = [];

        foreach ($request->products as $index => $product) {
            $imagePath = null;
            if (isset($product['image'])) {
                $filename = Str::random(10) . '.' . $product['image']->getClientOriginalExtension();
                $product['image']->move(public_path('temp'), $filename);
                $imagePath = public_path('temp/' . $filename);
            }

            $products[] = [
                'name' => $product['name'],
                'description' => $product['description'],
                'quantity' => $product['quantity'],
                'price' => $product['price'],
                'subtotal' => $product['price'] * $product['quantity'],
                'image' => $imagePath,
            ];
        }

        $grandTotal = collect($products)->sum('subtotal');

        $data = [
            'customer_name' => $request->customer_name,
            'products' => $products,
            'grand_total' => $grandTotal,
        ];

        // Generate PDF
        $fileName = 'order_' . time() . '.pdf';
        $filePath = public_path('pdf/' . $fileName);

        if (!File::exists(public_path('pdf'))) {
            File::makeDirectory(public_path('pdf'), 0755, true);
        }

        $pdf = Pdf::loadView('order.pdf', $data);
        $pdf->save($filePath);

        // Redirect with success + file path
        return redirect()->route('order.form')->with([
            'success' => 'Order created successfully!',
            'pdf_path' => asset('pdf/' . $fileName)
        ]);
    }
}

🖼️ Step 4: Create the Order Form View

Create resources/views/order/form.blade.php:

<!DOCTYPE html>
<html>

<head>
    <title>Create Multi-Item Order PDF</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
    <style>
        .preview-img {
            width: 100px;
            height: auto;
            margin: 5px;
        }
    </style>
</head>

<body>
    <div class="container mt-5">
        <h2>Create Multi-Product Order</h2>

        {{-- Success Message --}}
        @if (session('success'))
            <div class="alert alert-success">
                {{ session('success') }}
            </div>

            <script>
                // Auto download PDF after redirect
                window.onload = function() {
                    const link = document.createElement('a');
                    link.href = "{{ session('pdf_path') }}";
                    link.download = "OrderReceipt.pdf";
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);
                }
            </script>
        @endif

        <form id="orderForm" action="{{ route('generate.pdf') }}" method="POST" enctype="multipart/form-data">
            @csrf

            <div class="mb-3">
                <label class="form-label">Customer Name</label>
                <input type="text" class="form-control" name="customer_name" required>
            </div>

            <div id="product-list">
                <div class="product-item border p-3 mb-3">
                    <h5>Product 1</h5>
                    <div class="mb-2"><input type="text" name="products[0][name]" class="form-control"
                            placeholder="Product Name" required></div>
                    <div class="mb-2">
                        <textarea name="products[0][description]" class="form-control" placeholder="Description" required></textarea>
                    </div>
                    <div class="mb-2"><input type="number" name="products[0][quantity]" class="form-control"
                            placeholder="Quantity" min="1" value="1" required></div>
                    <div class="mb-2"><input type="number" name="products[0][price]" class="form-control"
                            placeholder="Price per item" required></div>
                    <div class="mb-2"><input type="file" name="products[0][image]" class="form-control image-input"
                            accept="image/*">
                        <div class="image-preview mt-2"></div>
                    </div>
                </div>
            </div>

            <button type="button" class="btn btn-secondary mb-3" id="addProductBtn">Add Another Product</button>
            <button type="submit" class="btn btn-primary">Generate PDF</button>
        </form>
    </div>

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <script>
        let productIndex = 1;

        $('#addProductBtn').on('click', function() {
            const productHtml = `
    <div class="product-item border p-3 mb-3">
        <h5>Product ${productIndex + 1}</h5>
        <div class="mb-2"><input type="text" name="products[${productIndex}][name]" class="form-control" placeholder="Product Name" required></div>
        <div class="mb-2"><textarea name="products[${productIndex}][description]" class="form-control" placeholder="Description" required></textarea></div>
        <div class="mb-2"><input type="number" name="products[${productIndex}][quantity]" class="form-control" placeholder="Quantity" min="1" value="1" required></div>
        <div class="mb-2"><input type="number" name="products[${productIndex}][price]" class="form-control" placeholder="Price per item" required></div>
        <div class="mb-2"><input type="file" name="products[${productIndex}][image]" class="form-control image-input" accept="image/*"><div class="image-preview mt-2"></div></div>
    </div>`;
            $('#product-list').append(productHtml);
            productIndex++;
        });

        // Image preview
        $(document).on('change', '.image-input', function() {
            const preview = $(this).siblings('.image-preview');
            preview.html('');
            const file = this.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    preview.append(`<img src="${e.target.result}" class="preview-img">`);
                };
                reader.readAsDataURL(file);
            }
        });
    </script>
</body>

</html>

🧾 Step 5: Create the PDF View

Create resources/views/order/pdf.blade.php:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Order Receipt</title>
    <style>
        body {
            font-family: DejaVu Sans, sans-serif;
            padding: 20px;
            color: #333;
        }

        .header,
        .footer {
            text-align: center;
        }

        table {
            width: 100%;
            margin-top: 20px;
            border-collapse: collapse;
        }

        table,
        th,
        td {
            border: 1px solid #ccc;
            padding: 8px;
        }

        th {
            background-color: #f2f2f2;
        }

        .product-img {
            width: 70px;
            height: auto;
            margin-top: 5px;
        }

        .total-section {
            text-align: right;
            margin-top: 30px;
            font-weight: bold;
        }
    </style>
</head>

<body>
    <div class="header">
        <h2>Order Receipt</h2>
        <p><strong>Customer:</strong> {{ $customer_name }}</p>
        <p><strong>Date:</strong> {{ now()->format('d M, Y h:i A') }}</p>
    </div>

    <table>
        <thead>
            <tr>
                <th>Product</th>
                <th>Description</th>
                <th>Qty</th>
                <th>Price (₹)</th>
                <th>Subtotal (₹)</th>
            </tr>
        </thead>
        <tbody>
            @foreach ($products as $product)
                <tr>
                    <td>
                        <strong>{{ $product['name'] }}</strong><br>
                        @if ($product['image'])
                            <img src="{{ $product['image'] }}" class="product-img" alt="">
                        @endif
                    </td>
                    <td>{{ $product['description'] }}</td>
                    <td>{{ $product['quantity'] }}</td>
                    <td>{{ number_format($product['price'], 2) }}</td>
                    <td>{{ number_format($product['subtotal'], 2) }}</td>
                </tr>
            @endforeach
        </tbody>
    </table>

    <div class="total-section">
        Grand Total: ₹{{ number_format($grand_total, 2) }}
    </div>

    <div class="footer">
        <p>This is a computer-generated receipt.</p>
    </div>
</body>

</html>

✅ Final Result

Now visit: http://127.0.0.1:8000/order

  • Add multiple products like Dosa, Upma, Pav Bhaji

  • Upload images

  • Click “Generate PDF”

  • You'll receive a beautifully formatted order receipt!

🏁 Conclusion

In this post, we learned how to:

✅ Build a dynamic multi-product form
✅ Handle file uploads in Laravel
✅ Generate a PDF receipt with images, pricing, and totals using DomPDF

This is great for eCommerce, restaurant billing, or printable invoices.

Let me know if you want to extend this tutorial to also send the PDF via email, or save the order to the database.

0
Subscribe to my newsletter

Read articles from CodeWithSdCode directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

CodeWithSdCode
CodeWithSdCode

I’m SdCode, a passionate Laravel developer sharing simple tutorials and practical coding tips to help beginners and intermediate devs grow their skills and build great projects.