PHPackages                             sarder/pdfstudio - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [PDF &amp; Document Generation](/categories/documents)
4. /
5. sarder/pdfstudio

ActiveLibrary[PDF &amp; Document Generation](/categories/documents)

sarder/pdfstudio
================

Design, preview, and generate PDFs using HTML, TailwindCSS or Bootstrap in Laravel

v2.0.0(3mo ago)1591.2k↓18.2%8MITPHPPHP ^8.2CI passing

Since Mar 6Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/sarderiftekhar/pdf-studio)[ Packagist](https://packagist.org/packages/sarder/pdfstudio)[ RSS](/packages/sarder-pdfstudio/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (11)Versions (9)Used By (0)

PDF Studio for Laravel
======================

[](#pdf-studio-for-laravel)

Design, preview, and generate PDFs using HTML and TailwindCSS in Laravel.

[![Tests](https://camo.githubusercontent.com/12537c9800513c39a28e0028dd29ed26f8cea631d78940fa842a5e16e9836c94/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d35323725323070617373696e672d627269676874677265656e)](tests)[![PHPStan](https://camo.githubusercontent.com/6260b9f10635192f2139ab2bc42751142251ed05e7f79ab1e49910e95b1c6daf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068707374616e2d6c6576656c253230362d626c7565)](phpstan.neon)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](LICENSE)

> **Free and open source.** All features — including template versioning, workspaces, the visual builder, and the hosted rendering API — are available under the MIT license with no key, subscription, or license fee required.

📖 **[Full User Guide](docs/user-guide.html)** — detailed examples, page layouts, framework integrations (Livewire, Vue, React, Node.js, vanilla JS), and troubleshooting.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Drivers](#drivers)
- [Template Registry](#template-registry)
- [Blade Directives](#blade-directives)
- [Queue / Async Rendering](#queue--async-rendering)
- [Preview Routes](#preview-routes)
- [Tailwind CSS](#tailwind-css)
- [Template Versioning](#template-versioning)
- [Workspaces &amp; Access Control](#workspaces--access-control)
- [Visual Builder](#visual-builder)
- [SaaS: Hosted Rendering API](#saas-hosted-rendering-api)
- [SaaS: Usage Metering](#saas-usage-metering)
- [SaaS: Analytics](#saas-analytics)
- [PDF Merging](#pdf-merging)
- [PDF Post-Processing](#pdf-post-processing)
- [Watermarking](#watermarking)
- [Password Protection](#password-protection)
- [AcroForm Fill](#acroform-fill)
- [Livewire / Filament](#livewire--filament)
- [Render Caching](#render-caching)
- [Auto-Height Paper](#auto-height-paper)
- [Header/Footer Per-Page Control](#headerfooter-per-page-control)
- [Dependency Installer](#dependency-installer)
- [Diagnostics](#diagnostics)
- [Configuration Reference](#configuration-reference)
- [Testing](#testing)

---

Requirements
------------

[](#requirements)

- **PHP** &gt;= 8.2
- **Laravel** 11.x, 12.x, or 13.x

### Optional Dependencies

[](#optional-dependencies)

DriverPackageRequired ForChromium`spatie/browsershot` ^5.2Full CSS/TailwindCSS fidelity (recommended)CloudflareCloudflare Browser RenderingManaged remote Chromium renderingGotenbergSelf-hosted Gotenberg serviceRemote/self-hosted Chromium renderingWeasyPrintSystem `weasyprint` binaryPrint-native rendering, attachments, PDF variantsdompdf`dompdf/dompdf` ^2.0|^3.0Zero external dependencies, limited CSSwkhtmltopdfSystem binaryGood CSS fidelity, no Node.js needed**PDF Manipulation (optional):**

PackageRequired For`setasign/fpdi` ^2.3PDF merging, watermarking`mikehaertl/php-pdftk` ^4.0AcroForm fill, password protection> **Note:** The Chromium driver requires Node.js and a Chromium/Chrome binary on the server.

---

Installation
------------

[](#installation)

```
composer require sarder/pdfstudio
```

Publish the config file:

```
php artisan vendor:publish --tag=pdf-studio-config
```

Install optional dependencies for the features you need:

```
# Interactive — pick features from a list
php artisan pdf-studio:install

# Install all optional dependencies at once
php artisan pdf-studio:install --all
```

Or install them individually:

```
composer require spatie/browsershot          # Chromium PDF driver
composer require dompdf/dompdf               # Dompdf PDF driver
composer require setasign/fpdi               # Merge, watermark, split PDFs
composer require mikehaertl/php-pdftk        # AcroForm fill, password protection
composer require picqer/php-barcode-generator # @barcode Blade directive
composer require chillerlan/php-qrcode       # @qrcode Blade directive
```

> **Note:** PDF thumbnail generation also requires the `imagick` PHP extension, which must be installed separately (not via Composer).

If you're using Pro or SaaS features, publish and run migrations:

```
php artisan vendor:publish --tag=pdf-studio-migrations
php artisan migrate
```

---

Quick Start
-----------

[](#quick-start)

The `Pdf` facade is auto-discovered. No manual registration needed.

```
use PdfStudio\Laravel\Facades\Pdf;

// Download immediately
return Pdf::view('invoices.show')
    ->data(['invoice' => $invoice])
    ->download('invoice.pdf');

// Stream inline in browser
return Pdf::view('reports.quarterly')
    ->data(['report' => $report])
    ->inline('report.pdf');

// Save to Storage
Pdf::view('statements.monthly')
    ->data(['account' => $account])
    ->driver('chromium')
    ->format('A4')
    ->landscape()
    ->save('statements/2024-01.pdf', 's3');

// Render from inline HTML
$result = Pdf::html('Hello World')->render();
echo $result->bytes;        // file size in bytes
echo $result->renderTimeMs; // render duration
```

---

Drivers
-------

[](#drivers)

Detailed driver selection and troubleshooting guide:

- [Driver Guide](docs/driver-guide.md)
- [Existing PDF Workflows](docs/existing-pdf-workflows.md)

DriverRuntimeNodeCSS FidelityBest For`chromium` (default)Local Chrome + BrowsershotYesFullHigh-fidelity local rendering`cloudflare`Cloudflare Browser RenderingNo local NodeFullManaged remote rendering`gotenberg`Remote Gotenberg serviceNo local NodeFullSelf-hosted remote rendering`weasyprint`System `weasyprint` binaryNoPrint-nativeTagged PDFs, attachments, PDF variants`wkhtmltopdf`System binaryNoGoodLegacy compatibility`dompdf``dompdf/dompdf`NoLimitedZero external services`fake`Built-inNoN/ATesting onlyInstall your preferred driver:

```
# Chromium (recommended for TailwindCSS)
composer require spatie/browsershot

# dompdf (zero external dependencies)
composer require dompdf/dompdf
```

Set the default in config:

```
// config/pdf-studio.php
'default_driver' => 'chromium',
```

Switch per-render:

```
Pdf::view('report')->driver('dompdf')->download('report.pdf');
```

Remote driver examples:

```
Pdf::view('report')->driver('cloudflare')->download('report.pdf');
Pdf::view('report')->driver('gotenberg')->download('report.pdf');
Pdf::view('report')->driver('weasyprint')->pdfVariant('pdf/a-3u')->download('report.pdf');
```

Cloudflare config:

```
'drivers' => [
    'cloudflare' => [
        'account_id' => env('PDF_STUDIO_CLOUDFLARE_ACCOUNT_ID'),
        'api_token' => env('PDF_STUDIO_CLOUDFLARE_API_TOKEN'),
    ],
],
```

Gotenberg config:

```
'drivers' => [
    'gotenberg' => [
        'url' => env('PDF_STUDIO_GOTENBERG_URL', 'http://127.0.0.1:3000'),
    ],
],
```

WeasyPrint config:

```
'drivers' => [
    'weasyprint' => [
        'binary' => env('PDF_STUDIO_WEASYPRINT_BINARY', 'weasyprint'),
    ],
],
```

### Modern Render Options

[](#modern-render-options)

```
Pdf::view('reports.quarterly')
    ->driver('chromium')
    ->pageRanges('1-3')
    ->preferCssPageSize()
    ->scale(0.95)
    ->waitForFonts()
    ->waitForNetworkIdle()
    ->waitDelay(750)
    ->waitForSelector('#report-ready', ['visible' => true])
    ->taggedPdf()
    ->outline()
    ->metadata([
        'title' => 'Quarterly Report',
        'author' => 'PDF Studio',
    ])
    ->download('quarterly-report.pdf');
```

---

Template Registry
-----------------

[](#template-registry)

Register named templates with default options and data providers:

```
// config/pdf-studio.php
'templates' => [
    'invoice' => [
        'view'            => 'pdf.invoice',
        'description'     => 'Customer invoice',
        'default_options' => ['format' => 'A4'],
        'data_provider'   => App\Pdf\InvoiceDataProvider::class,
    ],
],
```

Use a registered template:

```
Pdf::template('invoice')->data(['id' => 123])->download('invoice.pdf');
```

List all registered templates:

```
php artisan pdf-studio:templates
```

---

Blade Directives
----------------

[](#blade-directives)

```
{{-- Force a page break --}}
@pageBreak

{{-- Page break before content --}}
@pageBreakBefore

{{-- Prevent content from splitting across pages --}}
@avoidBreak
    Keep this together on one page
@endAvoidBreak

{{-- Show/hide based on condition (CSS-based, prints correctly) --}}
@showIf($invoice->isPaid())
    PAID
@endShowIf

{{-- Wrap content to avoid mid-section breaks --}}
@keepTogether
    ...
@endKeepTogether

{{-- Page number footer (Chromium only) --}}
@pageNumber(['format' => 'Page {page} of {total}'])
```

---

Queue / Async Rendering
-----------------------

[](#queue--async-rendering)

Dispatch a render job to a queue:

```
use PdfStudio\Laravel\Jobs\RenderPdfJob;

RenderPdfJob::dispatch(
    view:       'invoices.show',
    data:       ['invoice' => $invoice->toArray()],
    outputPath: 'invoices/inv-001.pdf',
    disk:       's3',
    driver:     'chromium',
);
```

Batch rendering:

```
Pdf::batch([
    ['view' => 'invoices.show', 'data' => $inv1->toArray(), 'outputPath' => 'inv-1.pdf'],
    ['view' => 'invoices.show', 'data' => $inv2->toArray(), 'outputPath' => 'inv-2.pdf'],
], driver: 'dompdf', disk: 's3');
```

Compose multiple sections independently and merge them into one PDF:

```
$result = Pdf::compose([
    ['html' => 'Cover'],
    [
        'view' => 'pdf.invoice',
        'data' => ['invoice' => $invoice],
        'options' => [
            'format' => 'A4',
            'metadata' => ['title' => 'Invoice section'],
        ],
    ],
], driver: 'chromium');
```

---

Preview Routes
--------------

[](#preview-routes)

Enable browser-based template preview (disabled in production by default):

```
// config/pdf-studio.php
'preview' => [
    'enabled'     => true,
    'middleware'  => ['web', 'auth'],
],
```

Visit:

- `GET /pdf-studio/preview/{template}?format=html` — HTML preview
- `GET /pdf-studio/preview/{template}?format=pdf` — PDF preview

---

Tailwind CSS
------------

[](#tailwind-css)

Point PDF Studio at your Tailwind binary and config:

```
// config/pdf-studio.php
'tailwind' => [
    'binary' => env('TAILWIND_BINARY', base_path('node_modules/.bin/tailwindcss')),
    'config' => base_path('tailwind.config.js'),
],
```

Compiled CSS is cached automatically. Clear the cache:

```
php artisan pdf-studio:cache-clear
```

Fonts &amp; Assets
------------------

[](#fonts--assets)

Register local fonts once and PDF Studio will embed them as generated `@font-face` CSS during rendering:

```
'fonts' => [
    'inter' => [
        'family' => 'Inter',
        'sources' => [
            resource_path('fonts/Inter-Regular.ttf'),
        ],
        'weight' => '400',
        'style' => 'normal',
    ],
],
```

Asset policy can inline local assets and optionally block remote ones up front:

```
'assets' => [
    'inline_local' => true,
    'allow_remote' => false,
    'allowed_hosts' => [
        'assets.example.com',
        'cdn.example.com',
    ],
],
```

This helps avoid renderer-specific failures around local file paths, missing images, font files, or remote asset fetches. The resolver covers:

- ``
- ``
- CSS `url(...)` references inside linked stylesheets
- CSS `url(...)` references inside inline `` blocks

---

Template Versioning
-------------------

[](#template-versioning)

> **Requires:** migrations (`php artisan vendor:publish --tag=pdf-studio-migrations && php artisan migrate`).

Save a snapshot of a template definition at any point:

```
use PdfStudio\Laravel\Contracts\TemplateVersionServiceContract;

$versioning = app(TemplateVersionServiceContract::class);

// Save current version
$version = $versioning->create(
    definition:  $registry->get('invoice'),
    author:      'Jane Smith',
    changeNotes: 'Updated payment section layout',
);

// List version history
$versions = $versioning->list('invoice');
// Returns Collection ordered newest first

// Restore a previous version
$definition = $versioning->restore('invoice', versionNumber: 3);

// Diff two versions
$changes = $versioning->diff('invoice', fromVersion: 2, toVersion: 3);
// Returns array of changed field names
```

---

Workspaces &amp; Access Control
-------------------------------

[](#workspaces--access-control)

> **Requires:** migrations (`php artisan vendor:publish --tag=pdf-studio-migrations && php artisan migrate`).

```
use PdfStudio\Laravel\Models\Workspace;
use PdfStudio\Laravel\Models\WorkspaceMember;
use PdfStudio\Laravel\Contracts\AccessControlContract;

// Create a workspace
$workspace = Workspace::create(['name' => 'Acme Corp', 'slug' => 'acme']);

// Add members with roles: owner | admin | member | viewer
WorkspaceMember::create([
    'workspace_id' => $workspace->id,
    'user_id'      => $user->id,
    'role'         => 'admin',
]);

// Check access in code
$access = app(AccessControlContract::class);
$access->canAccess($user->id, $workspace->id);  // true/false
$access->canManage($user->id, $workspace->id);  // true for owner/admin

// Protect routes with middleware
Route::middleware('pdf-studio.workspace')->group(function () {
    // Route parameter must be named {workspace} (slug)
    Route::get('/workspaces/{workspace}/...', ...);
});
```

Scope projects to a workspace:

```
use PdfStudio\Laravel\Models\Project;

$project = Project::create([
    'workspace_id' => $workspace->id,
    'name'         => 'Q4 Reports',
    'slug'         => 'q4-reports',
]);
```

---

Visual Builder
--------------

[](#visual-builder)

> **Requires:** `pdf-studio.preview.enabled = true`

The visual builder lets you define document layouts as a JSON schema of typed blocks, preview them as HTML, and export them to Blade templates.

### Block Schema

[](#block-schema)

```
use PdfStudio\Laravel\Builder\Schema\DocumentSchema;
use PdfStudio\Laravel\Builder\Schema\TextBlock;
use PdfStudio\Laravel\Builder\Schema\TableBlock;
use PdfStudio\Laravel\Builder\Schema\DataBinding;
use PdfStudio\Laravel\Builder\Schema\StyleTokens;

$schema = new DocumentSchema(
    blocks: [
        new TextBlock(content: 'Invoice', tag: 'h1', classes: 'text-2xl font-bold'),
        new TableBlock(
            headers: ['Item', 'Qty', 'Price'],
            rowBinding: new DataBinding(variable: 'items', path: 'items'),
            cellBindings: ['name', 'quantity', 'price'],
        ),
    ],
    styleTokens: new StyleTokens(
        primaryColor: '#1a1a1a',
        fontFamily: 'Inter, sans-serif',
    ),
);

// Serialize to JSON (store in DB, send to frontend)
$json = $schema->toJson();

// Restore from JSON
$schema = DocumentSchema::fromJson($json);
```

### Compile to HTML

[](#compile-to-html)

```
use PdfStudio\Laravel\Builder\Compiler\SchemaToHtmlCompiler;

$html = app(SchemaToHtmlCompiler::class)->compile($schema);
// Returns full HTML document ready to pass to PdfBuilder
```

### Export to Blade

[](#export-to-blade)

```
use PdfStudio\Laravel\Builder\Exporter\BladeExporter;

$blade = app(BladeExporter::class)->export($schema);
// Returns a Blade template string with @foreach loops for tables
file_put_contents(resource_path('views/pdf/invoice.blade.php'), $blade);
```

### Live Preview API

[](#live-preview-api)

```
POST /pdf-studio/builder/preview
Content-Type: application/json

{
    "schema": { ...DocumentSchema JSON... },
    "format": "html"   // or "pdf"
}

```

---

SaaS: Hosted Rendering API
--------------------------

[](#saas-hosted-rendering-api)

> **Requires:** `PDF_STUDIO_SAAS=true` in `.env` and migrations.

Enable in `.env`:

```
PDF_STUDIO_SAAS=true
```

### Issue an API Key

[](#issue-an-api-key)

```
use PdfStudio\Laravel\Models\Workspace;
use PdfStudio\Laravel\Models\ApiKey;

$workspace = Workspace::create(['name' => 'Acme Corp', 'slug' => 'acme']);

$generated = ApiKey::generate(); // ['key' => '...64 chars...', 'prefix' => '...8 chars...']

ApiKey::create([
    'workspace_id' => $workspace->id,
    'name'         => 'Production Key',
    'key'          => hash('sha256', $generated['key']), // store hash only
    'prefix'       => $generated['prefix'],
    'expires_at'   => now()->addYear(),                  // optional
]);

// Give $generated['key'] to your customer — it cannot be retrieved again
```

Revoke a key:

```
$apiKey->revoke();
```

### Render Endpoints

[](#render-endpoints)

All endpoints require:

```
Authorization: Bearer

```

**Sync — immediate PDF response:**

```
curl -X POST https://yourapp.com/api/pdf-studio/render \
  -H "Authorization: Bearer sk_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "html": "Invoice #42",
    "filename": "invoice-42.pdf"
  }'
# → application/pdf download
```

With a Blade view and options:

```
{
    "view": "pdf.invoice",
    "data": {"invoice": {"id": 42, "total": 1200}},
    "options": {"format": "A4", "landscape": false},
    "driver": "chromium"
}
```

**Async — queue a render job:**

```
curl -X POST https://yourapp.com/api/pdf-studio/render/async \
  -H "Authorization: Bearer sk_abc123..." \
  -d '{
    "view": "pdf.report",
    "data": {"month": "January"},
    "output_path": "reports/jan.pdf",
    "output_disk": "s3"
  }'
# → 202 {"id": "uuid-...", "status": "pending"}
```

**Poll job status:**

```
curl https://yourapp.com/api/pdf-studio/render/{uuid} \
  -H "Authorization: Bearer sk_abc123..."
# → {"id": "...", "status": "completed", "bytes": 14200, "render_time_ms": 312.5}
```

Status values: `pending` | `completed` | `failed`

---

SaaS: Usage Metering
--------------------

[](#saas-usage-metering)

Record usage events with idempotency (safe to call multiple times for the same job):

```
use PdfStudio\Laravel\Contracts\UsageMeterContract;

$meter = app(UsageMeterContract::class);

$meter->recordRender(
    workspaceId:  $workspace->id,
    jobId:        $job->id,          // idempotency key — won't double-count
    bytes:        $result->bytes,
    renderTimeMs: $result->renderTimeMs,
);
```

Each `recordRender` call dispatches a `BillableEvent`. Hook into it to integrate your billing provider:

```
// In AppServiceProvider::boot()
use PdfStudio\Laravel\Events\BillableEvent;

Event::listen(BillableEvent::class, function (BillableEvent $event) {
    // $event->workspaceId
    // $event->eventType  (e.g. 'render')
    // $event->quantity   (always 1 per render)
    // $event->metadata   (['bytes' => ..., 'render_time_ms' => ...])

    Stripe::meterEvent('pdf_render', [
        'stripe_customer_id' => Workspace::find($event->workspaceId)->stripe_id,
        'value' => $event->quantity,
    ]);
});
```

Query raw usage:

```
$records = $meter->getUsage($workspace->id, now()->startOfMonth(), now()->endOfMonth());

$summary = $meter->getSummary($workspace->id, now()->startOfMonth(), now()->endOfMonth());
// ['render' => 1432]
```

---

SaaS: Analytics
---------------

[](#saas-analytics)

Query render stats for a workspace over a date range:

```
use PdfStudio\Laravel\Contracts\AnalyticsServiceContract;

$analytics = app(AnalyticsServiceContract::class);

$stats = $analytics->getStats(
    workspaceId: $workspace->id,
    from:        now()->startOfMonth(),
    to:          now()->endOfMonth(),
);

// [
//   'total'              => 1432,
//   'completed'          => 1418,
//   'failed'             => 14,
//   'avg_render_time_ms' => 287.3,
//   'total_bytes'        => 48392104,
// ]
```

---

PDF Merging
-----------

[](#pdf-merging)

Merge multiple PDFs into a single document. Requires `setasign/fpdi`.

```
use PdfStudio\Laravel\Facades\Pdf;

// Merge file paths
$result = Pdf::merge([
    storage_path('pdf/cover.pdf'),
    storage_path('pdf/report.pdf'),
    storage_path('pdf/appendix.pdf'),
]);

// Merge PdfResult objects
$page1 = Pdf::html('Page 1')->render();
$page2 = Pdf::html('Page 2')->render();
$result = Pdf::merge([$page1, $page2]);

// Merge with Storage paths and page ranges
$result = Pdf::merge([
    ['path' => 'documents/report.pdf', 'disk' => 's3', 'pages' => '1-3,5'],
    storage_path('pdf/appendix.pdf'),
]);

$result->download('merged.pdf');
```

---

PDF Post-Processing
-------------------

[](#pdf-post-processing)

Detailed guide:

- [Existing PDF Workflows](docs/existing-pdf-workflows.md)

Operate on existing PDF bytes after rendering or on PDFs produced outside PDF Studio:

```
$isPdf = Pdf::isPdf($pdfBytes);

$summary = Pdf::inspectPdf($pdfBytes);

Pdf::assertPdf($pdfBytes, 'uploaded report');

$totalPages = Pdf::pageCount($pdfBytes);

$chunks = Pdf::chunk($pdfBytes, 25);

$parts = Pdf::split($pdfBytes, ['1-2', '3-5']);

$flattened = Pdf::flattenPdf($pdfBytes);

$embedded = Pdf::embedFiles($pdfBytes, [[
    'path' => storage_path('app/reports/source.csv'),
    'name' => 'source.csv',
    'mime' => 'text/csv',
]]);

$isStoredPdf = Pdf::isPdfFile(storage_path('app/reports/annual.pdf'));

$storedSummary = Pdf::inspectPdfFile(storage_path('app/reports/annual.pdf'));

Pdf::assertPdfFile(storage_path('app/reports/annual.pdf'), 'stored annual report');

$totalPages = Pdf::pageCountFile(storage_path('app/reports/annual.pdf'));

$plannedRanges = Pdf::chunkRangesFile(storage_path('app/reports/annual.pdf'), 25);

$chunkPlan = Pdf::chunkPlanFile(storage_path('app/reports/annual.pdf'), 25);

$fileChunks = Pdf::chunkFile(storage_path('app/reports/annual.pdf'), 25);
```

`isPdf()` and `isPdfFile()` provide a cheap preflight check before queueing or manipulating uploaded content. `inspectPdf()` and `inspectPdfFile()` provide a combined summary with validity, page-count information when available, and `byte_size` for transport/storage planning. `readPdfMetadata()` / `readPdfMetadataFile()` extract basic document-info metadata. `assertPdf()` and `assertPdfFile()` provide a fail-fast validation path when invalid input should stop the workflow immediately. `pageCount()` and `pageCountFile()` return integers. `chunkRanges()` / `chunkRangesFile()` return plain page-range strings. `chunkPlan()` / `chunkPlanFile()` return structured planning metadata per chunk. `split()`, `chunk()`, `chunkFile()`, `reorderPages()`, `rotatePages()`, and `removePages()` handle staged page editing. `flattenPdf()` / `flattenPdfFile()` and `embedFiles()` / `embedFilesIntoFile()` return a single `PdfResult`.

---

Watermarking
------------

[](#watermarking)

Add text or image watermarks to rendered PDFs. Requires `setasign/fpdi`.

```
// Text watermark
Pdf::html('Invoice')
    ->watermark('DRAFT', opacity: 0.3, fontSize: 72, position: 'center')
    ->download('invoice-draft.pdf');

// Image watermark
Pdf::view('report')
    ->watermarkImage(storage_path('images/logo.png'), opacity: 0.2, position: 'bottom-right')
    ->download('report.pdf');

// Watermark an existing PDF
$result = Pdf::watermarkPdf(file_get_contents('existing.pdf'))
    ->text('CONFIDENTIAL')
    ->opacity(0.5)
    ->rotation(-30)
    ->apply();
```

---

Password Protection
-------------------

[](#password-protection)

Protect PDFs with user/owner passwords. Requires `mikehaertl/php-pdftk`.

```
// Set both passwords
Pdf::html('Secret Report')
    ->protect(userPassword: 'user123', ownerPassword: 'admin456')
    ->download('protected.pdf');

// Owner password with restricted permissions
Pdf::view('contract')
    ->protect(
        ownerPassword: 'admin',
        permissions: ['Printing', 'CopyContents'],
    )
    ->save('contracts/signed.pdf');
```

---

AcroForm Fill
-------------

[](#acroform-fill)

Fill PDF form fields programmatically. Requires `mikehaertl/php-pdftk`.

```
// Fill form fields
$result = Pdf::acroform(storage_path('forms/application.pdf'))
    ->fill([
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'date' => '2024-01-15',
    ])
    ->flatten()
    ->output();

$result->download('application-filled.pdf');

// List available form fields
$fields = Pdf::acroform(storage_path('forms/application.pdf'))->fields();
// ['name', 'email', 'date', 'signature']
```

---

Livewire / Filament
-------------------

[](#livewire--filament)

Download PDFs from Livewire components without Livewire intercepting the response:

```
// In a Livewire component action
public function downloadInvoice()
{
    return Pdf::view('invoices.show')
        ->data(['invoice' => $this->invoice])
        ->livewireDownload('invoice.pdf');
}
```

Get base64 content for embedding:

```
$result = Pdf::html('Report')->render();
$base64 = $result->toBase64(); // or $result->base64()
```

---

Render Caching
--------------

[](#render-caching)

Cache rendered PDFs to avoid re-rendering identical content:

```
// Cache for 1 hour (3600 seconds)
$result = Pdf::html('Report')->cache(3600)->render();

// Second call returns cached result instantly (renderTimeMs = 0)
$result2 = Pdf::html('Report')->cache(3600)->render();

// Bypass cache for a specific render
$fresh = Pdf::html('Report')->cache(3600)->noCache()->render();
```

Configure defaults in config:

```
// config/pdf-studio.php
'render_cache' => [
    'enabled' => true,
    'store'   => null, // uses default cache store
    'ttl'     => 3600,
],
```

Clear render cache:

```
php artisan pdf-studio:cache-clear --render
```

---

Auto-Height Paper
-----------------

[](#auto-height-paper)

Automatically size the paper height to fit content (no page breaks):

```
// Auto-fit content height
Pdf::html('Long receipt...')
    ->contentFit()
    ->download('receipt.pdf');

// With maximum height cap (in pixels)
Pdf::view('receipt')
    ->contentFit(maxHeight: 3000)
    ->download('receipt.pdf');

// Alias
Pdf::html($html)->autoHeight()->render();
```

Supported by all drivers (Chromium, dompdf, wkhtmltopdf).

---

Header/Footer Per-Page Control
------------------------------

[](#headerfooter-per-page-control)

Control header and footer visibility on specific pages (Chromium and wkhtmltopdf):

```
// Hide header on the first page (e.g., cover page)
Pdf::view('report')
    ->headerExceptFirst()
    ->download('report.pdf');

// Hide footer on the last page
Pdf::view('report')
    ->footerExceptLast()
    ->download('report.pdf');

// Show header only on specific pages
Pdf::view('report')
    ->headerOnPages([2, 3, 4])
    ->download('report.pdf');

// Exclude header/footer from specific pages
Pdf::view('report')
    ->headerExcludePages([1, 5])
    ->footerExcludePages([1])
    ->download('report.pdf');
```

---

Dependency Installer
--------------------

[](#dependency-installer)

Install optional dependencies interactively or all at once:

```
# Interactive — choose which features to install
php artisan pdf-studio:install

# Install everything
php artisan pdf-studio:install --all
```

The command automatically skips packages that are already installed and shows real-time Composer output. After installation it reminds you about the `imagick` PHP extension if it's missing.

FeaturePackageChromium PDF driver`spatie/browsershot`Dompdf PDF driver`dompdf/dompdf`PDF manipulation (merge, watermark, split)`setasign/fpdi`Form filling &amp; password protection`mikehaertl/php-pdftk`Barcodes (`@barcode` directive)`picqer/php-barcode-generator`QR codes (`@qrcode` directive)`chillerlan/php-qrcode`---

Diagnostics
-----------

[](#diagnostics)

Run a health check on your PDF Studio installation:

```
php artisan pdf-studio:doctor
```

Checks:

- PHP version and memory limit
- DOM/XML extension
- temporary directory writability
- current default driver
- Node.js
- Cloudflare credential presence
- Gotenberg endpoint configuration and reachability when selected
- WeasyPrint availability
- dompdf, wkhtmltopdf, pdftk, FPDI, and Tailwind binary
- configured custom font paths
- asset policy summary
- a fake render pass

---

Configuration Reference
-----------------------

[](#configuration-reference)

```
// config/pdf-studio.php

return [
    'default_driver' => env('PDF_STUDIO_DRIVER', 'chromium'),

    'tailwind' => [
        'binary' => env('TAILWIND_BINARY'),
        'config' => null,
    ],

    'preview' => [
        'enabled'              => env('PDF_STUDIO_PREVIEW', false),
        'middleware'           => ['web', 'auth'],
        'environment_gate'     => true,
        'allowed_environments' => ['local', 'staging', 'testing'],
    ],

    'logging' => [
        'enabled' => env('PDF_STUDIO_LOGGING', false),
        'channel' => null,
    ],

    'pro' => [
        'enabled'    => env('PDF_STUDIO_PRO', false),
        'versioning' => ['enabled' => true, 'max_versions' => 50],
        'workspaces' => [
            'enabled' => true,
            'roles'   => ['owner', 'admin', 'member', 'viewer'],
        ],
    ],

    'saas' => [
        'enabled'  => env('PDF_STUDIO_SAAS', false),
        'api'      => [
            'prefix'     => 'api/pdf-studio',
            'middleware' => ['api'],
            'rate_limit' => 60,
        ],
        'metering' => ['enabled' => true],
    ],
];
```

---

Testing
-------

[](#testing)

```
composer test        # run all tests
composer analyse     # PHPStan level 6
composer lint        # Laravel Pint
```

### PdfFake (Testing Assertions)

[](#pdffake-testing-assertions)

Use `Pdf::fake()` in tests for fluent assertions without real PDF rendering:

```
use PdfStudio\Laravel\Facades\Pdf;

it('generates an invoice PDF', function () {
    $fake = Pdf::fake();

    // ... trigger the code that generates a PDF ...

    $fake->assertRendered();
    $fake->assertRenderedView('invoices.show');
    $fake->assertRenderedCount(1);
    $fake->assertDownloaded('invoice.pdf');
    $fake->assertSavedTo('invoices/inv-001.pdf', 's3');
    $fake->assertDriverWas('chromium');
    $fake->assertContains('Invoice');
    $fake->assertMerged();
    $fake->assertMergedCount(2);
    $fake->assertWatermarked();
    $fake->assertProtected();
    $fake->assertNothingRendered();
});
```

Or use the `fake` driver directly:

```
config(['pdf-studio.default_driver' => 'fake']);
```

---

Documentation
-------------

[](#documentation)

The README covers the full API surface. For deeper guidance see the **[User Guide](docs/user-guide.html)**, which includes:

- Page layout examples (paper sizes, margins, headers/footers, multi-column)
- Framework integration guides — Livewire, Vue 3, React, Node.js, vanilla JavaScript
- Troubleshooting — Tailwind class issues, image paths, custom fonts, page breaks, driver differences

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

51

—

FairBetter than 95% of packages

Maintenance88

Actively maintained with recent releases

Popularity37

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 85.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

3

Last Release

107d ago

Major Versions

v1.1.0 → v2.0.02026-03-08

PHP version history (2 changes)v1.0.0PHP ^8.1

v2.0.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/6baf5479f2d5506dc3cf77da7607fbbd0970781d7735f95b0d63062635d78b42?d=identicon)[sarderiftekhar](/maintainers/sarderiftekhar)

---

Top Contributors

[![sarder-hilbert](https://avatars.githubusercontent.com/u/49239638?v=4)](https://github.com/sarder-hilbert "sarder-hilbert (155 commits)")[![sarderiftekhar](https://avatars.githubusercontent.com/u/20506657?v=4)](https://github.com/sarderiftekhar "sarderiftekhar (26 commits)")

---

Tags

laravelpdftailwinddompdfchromiumhtml-to-pdf

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sarder-pdfstudio/health.svg)

```
[![Health](https://phpackages.com/badges/sarder-pdfstudio/health.svg)](https://phpackages.com/packages/sarder-pdfstudio)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.5k](/packages/larastan-larastan)[laravel/ai

The official AI SDK for Laravel.

9782.1M161](/packages/laravel-ai)[spatie/laravel-health

Monitor the health of a Laravel application

87411.3M152](/packages/spatie-laravel-health)[api-platform/laravel

API Platform support for Laravel

59156.3k11](/packages/api-platform-laravel)[simplestats-io/laravel-client

Analytics for Laravel. Track visitors, registrations, and payments. Discover which channels actually drive revenue, not just traffic. Server-side, GDPR compliant, ad-blocker proof.

5019.3k](/packages/simplestats-io-laravel-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
