Log Entry

Blob.toPdf Rendering Update

Feb 25, 2026 · 5 min read

If you generate invoices, statements, or letters from Apex, this update is worth treating like a mini migration.

The code call stays the same, but the renderer changes, and rendering changes are where teams get surprised.

Quick snapshot (What / Where / When / Why)

  • What: Blob.toPdf() now uses the Visualforce PDF rendering service.
  • Where: Lightning Experience and Salesforce Classic in Enterprise, Performance, Unlimited, and Developer editions.
  • When: enforced in Summer '26.
  • Why: better font support, better multibyte handling, and consistent behavior with Visualforce PDF rendering.

What stays the same

  • Blob.toPdf(String input) is still the API shape.
  • You still pass a String.
  • You still get back a Blob.

So this is not a refactor-heavy release. It is mostly a rendering-fidelity release.

How I would use this in a real org

I would do this in four steps:

  • Make font and print rules explicit in every PDF template string.
  • Add one canary PDF generator so you can compare before/after output reliably.
  • Run multilingual smoke tests (especially if you support non-Latin scripts).
  • Compare output for wrapping/page breaks before turning the update on in production.

That sequence catches almost all "it compiles but pagination changed" incidents.

Example 1: Minimal Blob.toPdf() + save as File

public with sharing class InvoicePdfService {
    public static Id generateInvoice(Id publishToRecordId, String invoiceNumber, Decimal total) {
        String html =
            '<html><body>' +
            '<h1>Invoice ' + String.escapeSingleQuotes(invoiceNumber) + '</h1>' +
            '<p>Date: ' + String.valueOf(Date.today()) + '</p>' +
            '<p>Total: $' + String.valueOf(total.setScale(2)) + '</p>' +
            '</body></html>';

        Blob pdfBlob = Blob.toPdf(html);

        ContentVersion cv = new ContentVersion(
            Title = 'Invoice-' + invoiceNumber,
            PathOnClient = 'invoice-' + invoiceNumber + '.pdf',
            VersionData = pdfBlob,
            FirstPublishLocationId = publishToRecordId
        );
        insert cv;

        return cv.Id;
    }
}

Example 2: Make layout deterministic (font + page rules)

String html =
    '<html><head><style>' +
    '@page { size: A4; margin: 16mm; }' +
    'body { font-family: Arial, sans-serif; font-size: 12px; line-height: 1.35; }' +
    '.section { page-break-inside: avoid; margin-bottom: 10px; }' +
    '.label { font-weight: 600; }' +
    '</style></head><body>' +
    '<div class="section"><p class="label">Billing Address</p><p>...</p></div>' +
    '<div class="section"><p class="label">Line Items</p><p>...</p></div>' +
    '</body></html>';

Blob pdfBlob = Blob.toPdf(html);

If you only do one thing for this update, do this.

Example 3: Multibyte smoke test you can run in UAT

String html =
    '<html><head><style>' +
    'body { font-family: sans-serif; font-size: 12px; }' +
    '</style></head><body>' +
    '<p>English: Invoice ready</p>' +
    '<p>日本語: 請求書を作成しました</p>' +
    '<p>العربية: تم إنشاء الفاتورة</p>' +
    '<p>ไทย: ออกใบแจ้งหนี้เรียบร้อยแล้ว</p>' +
    '</body></html>';

Blob pdfBlob = Blob.toPdf(html);

I use a snippet like this to validate that multilingual output still looks right after engine changes.

Example 4: Canary doc for before/after comparison

public with sharing class PdfCanaryService {
    public static Id generateCanary(Id publishToRecordId, String label) {
        String safeLabel = String.escapeSingleQuotes(label);

        String html =
            '<html><head><style>' +
            'body { font-family: Arial, sans-serif; font-size: 12px; line-height: 1.35; }' +
            '.block { margin-bottom: 8px; }' +
            '</style></head><body>' +
            '<div class="block">Label: ' + safeLabel + '</div>' +
            '<div class="block">Timestamp: ' + String.valueOf(Datetime.now()) + '</div>' +
            '<div class="block">Long wrap test: ConnectApi.RecordUi.getPicklistValuesByRecordType(...)</div>' +
            '</body></html>';

        Blob pdfBlob = Blob.toPdf(html);

        ContentVersion cv = new ContentVersion(
            Title = 'PDF-Canary-' + safeLabel,
            PathOnClient = 'pdf-canary-' + safeLabel + '.pdf',
            VersionData = pdfBlob,
            FirstPublishLocationId = publishToRecordId
        );
        insert cv;

        return cv.Id;
    }
}

Run this against a stable input set before and after activation, then compare page count and break positions.

Activation path (the practical click path)

In Setup:

  • Go to Release Updates.
  • Find Use Visualforce PDF Rendering Service with Apex Blob.toPdf().
  • Follow testing and activation there.

For your exact major-release timing, use Trust Status for your instance and check Maintenance.

Final take

This is a good platform change.

Just do not treat it like "no impact because method signature is unchanged." Treat it like a renderer swap, lock down typography, and verify output intentionally before Summer '26 enforcement.