anghelutav / laravel-pdf-templates
PDF templates for Laravel: visual designer (pdfme) or HTML/CSS with placeholders, filled with JSON. Admin UI, test page and a self-hosted API.
Package info
github.com/AnghelutaV/laravel-pdf-templates
pkg:composer/anghelutav/laravel-pdf-templates
Requires
- php: ^8.2
- barryvdh/laravel-dompdf: ^3.0
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.5|^11.0|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
PDF templates for Laravel, stored in the database, filled with JSON and rendered from your code or over HTTP. You can use it instead of a hosted service such as apitemplate.io.
There are two template modes:
- WYSIWYG: a visual designer (pdfme) for fields on the page, tables, images, QR/barcodes and a background PDF.
- HTML + CSS: code with
{placeholders}, loops, conditions and filters, rendered by dompdf.
It comes with a standalone admin UI (like log-viewer or Telescope) in light and dark themes. The UI has a template list, the designer, an HTML editor with live preview, and a JSON test page that shows the fields each template expects.
Requirements
- PHP 8.2+ and Laravel 11, 12 or 13. Laravel 13 needs PHP 8.3+.
- Node.js 18+, only to render WYSIWYG templates on the server (in code or through the API). The browser designer and the HTML mode don't need Node.
Installation
composer require anghelutav/laravel-pdf-templates php artisan pdf-templates:install --examples
This publishes config/pdf-templates.php, creates the pdf_templates table and adds three example templates.
Open /pdf-templates. In the local environment it's open to everyone. Everywhere else, define who may use it, for example in AppServiceProvider::boot():
Gate::define('viewPdfTemplates', fn ($user = null) => $user?->is_admin);
Usage in code
use AnghelutaV\PdfTemplates\Facades\PdfTemplates; // From a controller: shown inline in the browser return PdfTemplates::make('invoice', $data); // Download, store, base64, mail attachment return PdfTemplates::make('invoice', $data)->filename("invoice-{$order->id}")->download(); PdfTemplates::make('invoice', $data)->save("invoices/{$order->id}.pdf", 's3'); $base64 = PdfTemplates::make('invoice', $data)->base64(); // Raw bytes $bytes = PdfTemplates::render('invoice', $data);
$data is an array or a raw JSON string. A list of objects ([{…}, {…}]) renders several documents into one PDF. make() renders once, so you can call save() and then download() on the same object.
Mail attachments
public function attachments(): array { return [PdfTemplates::make('invoice', $this->order->toPdfData())->filename('invoice')->attachment()]; }
Let users pick a template
<x-pdf-templates::select name="pdf_template" :value="old('pdf_template', $order->pdf_template)" placeholder="—" class="form-select" />
PdfTemplates::options(); // ['invoice' => 'Invoice', ...] active templates PdfTemplates::options('html'); // only one mode
What data does a template expect?
PdfTemplates::fields('invoice'); // HTML: ['title' => 'value', 'items' => 'list', 'items.*.name' => 'value', 'paid' => 'condition'] // WYSIWYG: ['client_name' => 'text', 'items' => 'table', 'payment_link' => 'qrcode']
The same list is shown on each template's test page, and by php artisan pdf-templates:render invoice --fields.
HTTP API
Set a key in .env. You can generate one with php -r "echo bin2hex(random_bytes(32));".
PDF_TEMPLATES_API_KEY=your-long-random-key
curl -X POST https://your-app.test/api/pdf-templates/invoice \ -H "X-API-KEY: $PDF_TEMPLATES_API_KEY" \ -H "Content-Type: application/json" \ --data @data.json -o invoice.pdf
- Request body: the JSON data.
- Response:
application/pdf. Optional query parameters:?download=1and?filename=name. - Errors come back as JSON:
| Status | Meaning |
|---|---|
401 |
wrong or missing key |
403 |
API disabled (no key set) |
404 |
unknown or inactive template |
422 |
invalid JSON or template syntax |
The API is stateless: no session and no CSRF.
Template syntax (HTML mode)
| Syntax | Meaning |
|---|---|
{name} {client.address} {items.0.name} |
escaped value, dot paths |
{#each items} … {else} empty {/each} |
loop; inside: {name}, {this}, {@number}, {@index}, {@first}, {@last}, {@root.x} |
{#if paid} … {else} … {/if}, {#unless paid} … {/unless} |
conditions; these count as false: null, "", 0, "false", [] |
{total|number:2} {date|date:d.m.Y} {name|upper} {name|lower} |
filters, which can be chained |
{phone|default:—} {notes|nl2br} {html|raw} |
fallback value, line breaks, unescaped HTML |
Templates can't run PHP. dompdf runs with remote resources disabled, so images must be data: URIs or files under public/; same-host URLs are mapped to local files. Fonts: DejaVu Sans by default, and more TTF fonts can be added in html.fonts.
WYSIWYG data
Each designer field has a name, which is its key in the JSON. Values are strings, except:
- table:
[["a","b"],["c","d"]] - list:
["x","y"] - multi-variable text:
{"var":"value"} - image:
data:image/png;base64,…
The Page… menu in the designer resizes the page and scales every field to the new size.
Rendering WYSIWYG on the server
pdfme only runs in JavaScript, so server-side generation goes through the bundled Node renderer:
php artisan pdf-templates:renderer # installs npm deps on first run, listens on 127.0.0.1:3311
PDFME_RENDERER_URL=http://127.0.0.1:3311 PDFME_RENDERER_TOKEN=optional-shared-secret
In production, run it under supervisor or systemd. You can also copy vendor/anghelutav/laravel-pdf-templates/renderer anywhere and run npm install && npm start (env: HOST, PORT, PDFME_RENDERER_TOKEN).
Templates in git: export and import
Templates live in the database. To keep them in version control and ship them with deploys:
php artisan pdf-templates:export # database/pdf-templates/<slug>/… php artisan pdf-templates:import # on deploy, keeps templates that already exist php artisan pdf-templates:import --force # overwrite
Commands
| Command | Does |
|---|---|
pdf-templates:install [--examples] |
publish config, migrate, add examples |
pdf-templates:examples [--force] |
add the example templates |
pdf-templates:render {slug} {output?} [--data=file.json] [--fields] |
render from the console, or list the fields |
pdf-templates:export {path?} [--slug=*] |
templates → files |
pdf-templates:import {path?} [--force] |
files → templates |
pdf-templates:renderer [--host] [--port] [--install] |
run the Node renderer |
Configuration
config/pdf-templates.php:
| Key | Default | |
|---|---|---|
routes.prefix / routes.middleware |
pdf-templates / ['web'] |
admin UI URL and middleware |
gate |
viewPdfTemplates |
null = no gate check |
layout / section |
null / content |
render inside your own admin layout (it must have @stack('styles') and @stack('scripts')) |
back_url |
null |
back link in the UI header |
back_label |
null |
back label link UI header |
api.prefix / api.middleware / api.key |
api/pdf-templates / ['api', 'throttle:60,1'] / env |
HTTP API |
table |
pdf_templates |
database table |
pdfme.* |
pdfme version, CDN, renderer URL and token | |
html.* |
default font, extra fonts, number separators, extra dompdf chroot dirs |
To publish the views or translations (en, ro): php artisan vendor:publish --tag=pdf-templates-views or --tag=pdf-templates-lang.
Testing
composer test
License
MIT