Template Editor
Cloud templates are Blade documents stored in BladePDF and rendered by their template id. You author them in the dashboard's template editor — layout, settings, and assets all live with the template, so your Laravel app only sends data. This page covers how the editor works, the asset:/// scheme, and the exact Blade syntax the cloud renderer supports.
When to use cloud templates
Cloud templates are a good fit when:
- Non-developers need to edit document layout without a deploy.
- The same document is rendered from several apps or languages.
- You want template changes, versioning, and assets managed in one place.
If your template already lives in your Laravel views, keep using local Blade views with fromView() instead. The two approaches share the same rendering engine.
The editor
The template editor is split into four areas:
- Editor — the Blade source of the document.
- Settings — page format, margins, orientation, and other render options stored with the template.
- Assets — images, CSS, and fonts uploaded and referenced with the
asset:///scheme. - Preview — a live render using sample context so you can see the PDF while you edit.
Drafts, publishing, and versions
Editing a template updates a draft. Renders always use the published version, so your changes are not live until you publish. This lets you iterate safely against production traffic.
- Save stores the current draft without affecting live renders.
- Publish promotes the draft to the live version used by
templateId. - Restore rolls back to an earlier published version.
- Duplicate creates a copy to experiment with or branch from.
- Archive / Restore retires a template without deleting it, and brings it back if needed.
- Download exports the template source, and Star pins frequently used templates.
A render targeting a template that has never been published will fail. Publish at least once before calling fromTemplate() with its id.
Template settings
A cloud template owns its own page setup. Format, margins, orientation, background, header and footer, and wait behavior are configured in the editor's Settings panel and stored with the template — not sent per request.
Because layout is part of the stored template, withHeader() / withFooter() HTML overrides are ignored for fromTemplate() renders. Configure headers and footers in the editor instead. Those overrides are only for local view and raw HTML renders.
Context data
Templates receive a JSON context object — the data your document renders. From Laravel you pass it as the second argument to fromTemplate(); over the REST API it is the context.json file. Because context crosses the network as JSON, values must be JSON-serializable (arrays and scalars), so convert models with toArray() first.
1{
2 "invoice": { "number": "INV-0001", "total": "€1,240.00" },
3 "customer": { "name": "Acme GmbH" },
4 "items": [
5 { "description": "Design retainer", "amount": "€1,000.00" },
6 { "description": "Hosting", "amount": "€240.00" }
7 ]
8}
Set a sample context in the editor so the live preview has realistic data to render. The sample is only used for previews — real renders use the context you send.
Assets and the asset:/// scheme
Upload images, fonts, and stylesheets in the Assets panel, then reference them from your template with the asset:/// scheme. At render time BladePDF resolves each reference to the stored asset.
1<img src="asset:///logo.svg" alt="Logo">
2
3<style>
4 @font-face {
5 font-family: "Brand";
6 src: url("asset:///fonts/brand.woff2");
7 }
8 .hero { background-image: url("asset:///hero.png"); }
9</style>
- Asset names are simple file names — letters, numbers, dots, underscores, and hyphens (a name may include folder-style segments such as
fonts/brand.woff2). - Uploaded assets count against your workspace storage quota.
- The same
asset:///reference can be swapped for a single render from Laravel withoverrideAsset()— useful for per-tenant logos.
1return BladePDF::fromTemplate('invoice.standard', $context)
2 ->overrideAsset('logo.svg', public_path("tenants/{$tenant->id}/logo.svg"))
3 ->render()
4 ->download('invoice.pdf');
Supported Blade syntax
The cloud renderer is a sandboxed, Blade-compatible engine. It supports the template-oriented parts of Blade:
| Group | Supported |
|---|---|
| Echoes | {{ }}, {!! !!}, @{{ }}, @@directive |
| Comments & verbatim | {{-- --}}, @verbatim … @endverbatim |
| Conditionals | @if, @elseif, @else, @unless, @isset, @empty, @switch/@case/@default |
| Loops | @foreach, @forelse, @for, @while, @break, @continue (with $loop) |
| Includes | @include, @includeIf, @includeWhen, @includeUnless, @includeFirst, @each |
| Layouts & sections | @extends, @section, @yield, @parent, @show, @hasSection, @sectionMissing |
| Stacks | @push, @prepend, @stack, @once |
| Helpers | @json, @js, @class, @style |
Unsupported syntax
Anything that would execute arbitrary code or reach into the Laravel runtime is not available in cloud templates:
- PHP execution —
@php,<?php,<?=,@inject,@use. - Laravel runtime helpers — auth, policies,
env, Vite, Livewire, session, and routing directives. - Blade components & slots —
<x-*>,<x-slot>,@props,@aware. - Arbitrary method calls on objects passed in via context.
The pattern is simple: do computation in your application, not the template. Format currency, dates, and totals before you send the context, and pass the finished strings and arrays. Use @include and @extends for reuse instead of components.
Templates run without eval, new Function, or a VM. A safe expression parser blocks dangerous keys like __proto__ and constructor, and hard limits cap output size, render time, loop iterations, include depth, and expression complexity. Keep templates focused on presentation and they will stay well within these limits.
A complete example
A layout, a document that extends it, an asset, a loop, and safe echoes:
1<!doctype html>
2<html>
3<head>
4 <meta charset="utf-8">
5 <style>
6 body { font-family: sans-serif; color: #111827; padding: 40px; }
7 h1 { color: #ff2d20; }
8 table { width: 100%; border-collapse: collapse; margin-top: 24px; }
9 th, td { text-align: left; padding: 8px; border-bottom: 1px solid #e5e7eb; }
10 </style>
11</head>
12<body>
13 <img src="asset:///logo.svg" height="40" alt="Logo">
14 @yield('content')
15</body>
16</html>
1@extends('layouts.base')
2
3@section('content')
4 <h1>Invoice {{ $invoice['number'] }}</h1>
5 <p>Billed to: {{ $customer['name'] }}</p>
6
7 <table>
8 <thead>
9 <tr><th>Description</th><th>Amount</th></tr>
10 </thead>
11 <tbody>
12 @forelse ($items as $item)
13 <tr>
14 <td>{{ $item['description'] }}</td>
15 <td>{{ $item['amount'] }}</td>
16 </tr>
17 @empty
18 <tr><td colspan="2">No line items</td></tr>
19 @endforelse
20 </tbody>
21 </table>
22
23 <p><strong>Total: {{ $invoice['total'] }}</strong></p>
24@endsection
Render it from Laravel
Once published, render the template by its id and pass the context:
1return BladePDF::fromTemplate('invoice.standard', [
2 'invoice' => $invoice->toArray(),
3 'customer' => $invoice->customer->toArray(),
4 'items' => $invoice->items->toArray(),
5])
6 ->reference($invoice->uuid)
7 ->storePdf()
8 ->render()
9 ->download("invoice-{$invoice->number}.pdf");
See Blade Templates for how local and cloud renders compare, and the facade reference for every builder method.