Log Entry
Blob.toPdf() Rendering Changes in Summer '26
Blob.toPdf() looks like a tiny release note until you remember what PDFs are usually used for.
Invoices. Statements. Letters. Certificates. Forms someone will print, archive, email, or send to a customer.
Summer '26 enforces the release update that makes Blob.toPdf() use the Visualforce PDF rendering service. The Apex method signature is unchanged, but the renderer is not.
That is where the risk lives.
Quick snapshot (What / Where / When / Why)
- What:
Blob.toPdf()uses the Visualforce PDF rendering service. - Where: Lightning Experience and Salesforce Classic in Enterprise, Performance, Unlimited, and Developer editions.
- When: enforced in Summer '26.
- Still true:
Blob.toPdf(String input)still accepts a string and returns aBlob. - Why it matters: the default font changes, more fonts are available, multibyte characters are better supported, and line/page breaks can move.
The practical version:
Your code might not change, but your PDF can.
The risky version: implicit everything
This is the kind of code I would audit first.
public with sharing class InvoicePdfService {
public static Id createInvoice(Id parentId, String invoiceNumber, Decimal total) {
String safeInvoiceNumber = invoiceNumber == null ? '' : invoiceNumber.escapeHtml4();
String html =
'<html><body>' +
'<h1>Invoice ' + safeInvoiceNumber + '</h1>' +
'<p>Total: $' + String.valueOf(total.setScale(2)) + '</p>' +
'<p>Thank you for your business.</p>' +
'</body></html>';
Blob pdf = Blob.toPdf(html);
ContentVersion file = new ContentVersion(
Title = 'Invoice-' + invoiceNumber,
PathOnClient = 'invoice-' + invoiceNumber + '.pdf',
VersionData = pdf,
FirstPublishLocationId = parentId
);
insert file;
return file.Id;
}
}
The call is valid, but the document is fragile.
There is no explicit font, no page size, no margin, no line-height, no table sizing, and no page-break behaviour. If the default renderer changes from one font family to another, the invoice can wrap differently.
That is not theoretical here: Salesforce notes that the Visualforce PDF renderer changes the default font from sans-serif to serif.
That can mean:
- totals move to the next page
- address blocks become taller
- a signature line splits awkwardly
- a table row breaks across pages
- a one-page customer letter becomes two pages
That is not a compile problem. It is a document QA problem.
Example 1: Lock down the boring layout rules
If you want the old shape to stay close, make the page explicit.
String safeInvoiceNumber = invoiceNumber == null ? '' : invoiceNumber.escapeHtml4();
String safeAccountName = accountName == null ? '' : accountName.escapeHtml4();
String html =
'<html><head><style>' +
'@page { size: A4; margin: 16mm 14mm; }' +
'body { font-family: sans-serif; font-size: 11px; line-height: 1.35; color: #111; }' +
'h1 { font-size: 18px; margin: 0 0 10px; }' +
'p { margin: 0 0 6px; }' +
'.meta { margin-bottom: 14px; }' +
'.total { font-size: 14px; font-weight: bold; margin-top: 12px; }' +
'</style></head><body>' +
'<h1>Invoice ' + safeInvoiceNumber + '</h1>' +
'<div class="meta">' +
'<p>Date: ' + String.valueOf(Date.today()) + '</p>' +
'<p>Account: ' + safeAccountName + '</p>' +
'</div>' +
'<p class="total">Total: $' + String.valueOf(total.setScale(2)) + '</p>' +
'</body></html>';
Blob pdf = Blob.toPdf(html);
The key bit is not the exact visual style. It is that the renderer no longer gets to choose the basics for you.
Use sans-serif if you want to preserve a sans-serif look. The Visualforce PDF renderer has its own supported font names, so boring generic names are safer than assuming browser-style font resolution.
Example 2: Fix the line-item table before it shifts
Invoices and statements usually break because of tables, not headings.
String html =
'<html><head><style>' +
'@page { size: A4; margin: 14mm; }' +
'body { font-family: sans-serif; font-size: 10px; line-height: 1.3; }' +
'table { width: 100%; border-collapse: collapse; table-layout: fixed; }' +
'th, td { border-bottom: 1px solid #ccc; padding: 4px 3px; vertical-align: top; }' +
'th { font-weight: bold; text-align: left; }' +
'.sku { width: 18%; }' +
'.description { width: 52%; }' +
'.qty { width: 10%; text-align: right; }' +
'.amount { width: 20%; text-align: right; }' +
'tr { page-break-inside: avoid; }' +
'</style></head><body>' +
'<h1>Statement</h1>' +
'<table>' +
'<thead><tr>' +
'<th class="sku">SKU</th>' +
'<th class="description">Description</th>' +
'<th class="qty">Qty</th>' +
'<th class="amount">Amount</th>' +
'</tr></thead>' +
'<tbody>' +
'<tr>' +
'<td class="sku">SUB-001</td>' +
'<td class="description">Annual platform subscription with implementation support</td>' +
'<td class="qty">1</td>' +
'<td class="amount">$4,800.00</td>' +
'</tr>' +
'</tbody></table>' +
'</body></html>';
Blob pdf = Blob.toPdf(html);
Concrete rule: do not let table columns auto-negotiate if the PDF has contractual or financial meaning.
Set widths. Set padding. Set font size. Avoid row splits where they would look broken.
Example 3: Multibyte output needs an explicit font
One of the good parts of the renderer change is better multibyte character support.
Still, do not leave multilingual documents to chance.
String html =
'<html><head><meta charset="UTF-8" /><style>' +
'@page { size: A4; margin: 18mm; }' +
'body { font-family: "Arial Unicode MS"; font-size: 11px; line-height: 1.5; }' +
'.latin-label { font-family: sans-serif; font-weight: bold; }' +
'.sample { margin-bottom: 8px; }' +
'</style></head><body>' +
'<h1 class="latin-label">Multilingual Smoke Test</h1>' +
'<p class="sample"><span class="latin-label">English:</span> Invoice ready</p>' +
'<p class="sample"><span class="latin-label">Japanese:</span> 請求書を作成しました</p>' +
'<p class="sample"><span class="latin-label">Chinese:</span> 发票已创建</p>' +
'<p class="sample"><span class="latin-label">Arabic:</span> تم إنشاء الفاتورة</p>' +
'<p class="sample"><span class="latin-label">Thai:</span> ออกใบแจ้งหนี้เรียบร้อยแล้ว</p>' +
'</body></html>';
Blob pdf = Blob.toPdf(html);
This is the smoke test I would actually keep around.
If your org sends documents in Japanese, Chinese, Arabic, Thai, Hindi, or any other non-Latin script, put real production-like strings in the canary document. Do not use a fake "hello world" and call it done.
Also test bold and italic expectations. The Visualforce PDF font rules are not the same as a browser's font rules.
Example 4: Long text and page-break canary
The release note specifically calls out possible line and page break differences. So test line and page breaks directly.
public with sharing class PdfCanaryService {
public static Id createLayoutCanary(Id parentId, String label) {
String fileLabel = String.isBlank(label) ? 'default' : label.left(40);
String safeLabel = fileLabel.escapeHtml4();
String html =
'<html><head><style>' +
'@page { size: A4; margin: 16mm; }' +
'body { font-family: sans-serif; font-size: 11px; line-height: 1.35; }' +
'.block { margin-bottom: 10px; page-break-inside: avoid; }' +
'.box { border: 1px solid #999; padding: 8px; }' +
'.narrow { width: 220px; }' +
'</style></head><body>' +
'<h1>PDF Canary: ' + safeLabel + '</h1>' +
'<div class="block box narrow">' +
'Long wrapping text: Acme International Services Limited, ' +
'Implementation and Support Renewal, Europe Middle East Africa Region' +
'</div>' +
'<div class="block box">' +
'Address block:<br />' +
'221B Long Example Street<br />' +
'Very Long District Name That Might Wrap<br />' +
'London<br />United Kingdom' +
'</div>' +
'<div class="block box">' +
'Terms: Payment due within thirty days of invoice date unless otherwise agreed.' +
'</div>' +
'</body></html>';
Blob pdf = Blob.toPdf(html);
ContentVersion file = new ContentVersion(
Title = 'PDF-Canary-' + safeLabel,
PathOnClient = 'pdf-canary-' + safeLabel + '.pdf',
VersionData = pdf,
FirstPublishLocationId = parentId
);
insert file;
return file.Id;
}
}
Run the canary before and after enabling the release update.
Compare:
- page count
- first page bottom content
- long address wrapping
- table row splits
- header and footer position
- multilingual text
- bold/italic rendering
This is not fancy, but it catches the bugs people actually complain about.
Example 5: A safer reusable wrapper
If your org has several Blob.toPdf() call sites, centralise the boring HTML shell.
public with sharing class PdfHtml {
public static String wrapA4(String title, String bodyHtml) {
String safeTitle = title == null ? '' : title.escapeHtml4();
return
'<html><head><meta charset="UTF-8" /><style>' +
'@page { size: A4; margin: 16mm 14mm; }' +
'body { font-family: sans-serif; font-size: 11px; line-height: 1.35; color: #111; }' +
'h1 { font-size: 18px; margin: 0 0 12px; }' +
'table { width: 100%; border-collapse: collapse; table-layout: fixed; }' +
'th, td { padding: 4px 3px; border-bottom: 1px solid #ccc; vertical-align: top; }' +
'tr, .keep-together { page-break-inside: avoid; }' +
'</style></head><body>' +
'<h1>' + safeTitle + '</h1>' +
bodyHtml +
'</body></html>';
}
}
Then the call site becomes smaller and more consistent:
String safeInvoiceNumber = invoiceNumber == null ? '' : invoiceNumber.escapeHtml4();
String body =
'<p>Invoice number: ' + safeInvoiceNumber + '</p>' +
'<p>Total: $' + String.valueOf(total.setScale(2)) + '</p>';
Blob pdf = Blob.toPdf(PdfHtml.wrapA4('Invoice', body));
This is not an abstraction for its own sake. It prevents five teams from each discovering the same PDF rendering lesson slightly differently.
What I would audit
Search for every call site.
rg -n "Blob\\.toPdf" force-app/main/default
Then review each one for:
- missing
font-family - missing
@pagesize and margins - generated tables without fixed widths
- long free-text fields
- non-Latin customer, address, or product text
- documents that are expected to remain one page
- documents that are signed, printed, archived, or sent externally
Prioritise customer-facing and compliance-facing documents first.
An internal debug PDF can be slightly ugly. An invoice that changes totals onto a second page is a different kind of day.
Activation path
In Setup:
- Go to Release Updates.
- Find Use Visualforce PDF Rendering Service with Apex Blob.toPdf().
- Follow the testing and activation steps.
For exact org timing, use Trust Status, search for your instance, and check Maintenance.
Source notes
- Use Visualforce PDF Rendering Service with Apex Blob.toPdf()
- Visualforce Developer Guide PDF
- How to Format a Salesforce Visualforce Page Rendered as a PDF Using CSS
Final take
This is a good platform change, especially for multilingual documents.
But do not treat it like a harmless implementation detail.
The method signature staying the same is almost the trap. Rendering changes are visual changes, and visual changes need visual testing.
Make fonts explicit, lock down page rules, create a canary PDF, and compare the documents before Summer '26 does it for you.