Generate PDF in Laravel using DomPDF


🧾 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.
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.