PHPackages                             johind/collate - 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. johind/collate

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

johind/collate
==============

Laravel package for manipulating PDFs — merge, split, extract pages, watermark, encrypt, and optimize PDFs using qpdf.

v1.5.1(1mo ago)82↓100%MITPHPPHP ^8.4 || ^8.5CI passing

Since Mar 11Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/johind/laravel-collate)[ Packagist](https://packagist.org/packages/johind/collate)[ Docs](https://github.com/johind/collate)[ GitHub Sponsors](https://github.com/johind)[ RSS](/packages/johind-collate/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (13)Versions (12)Used By (0)

Collate: PDF Processing for Laravel
===================================

[](#collate-pdf-processing-for-laravel)

[![Tests](https://github.com/johind/laravel-collate/actions/workflows/run-tests.yml/badge.svg)](https://github.com/johind/laravel-collate/actions/workflows/run-tests.yml)[![Packagist License](https://camo.githubusercontent.com/e60623f508586f049d48cfb8396ee411b0c9bc3be174381a1893c37462a3c1e5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e63652d4d49542d626c7565)](http://choosealicense.com/licenses/mit/)[![Latest Stable Version](https://camo.githubusercontent.com/aed3b58552070d19839316351c8b6b3cca91d8779a610f44de92076b17892e82/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a6f68696e642f636f6c6c6174653f6c6162656c3d537461626c65)](https://packagist.org/packages/johind/collate)[![Total Downloads](https://camo.githubusercontent.com/70dd3a3d150ad1ce24dca4eff519b61f9c02f2b1f7cc9d20927e177d95b1e53f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6a6f68696e642f636f6c6c6174652e7376673f6c6162656c3d446f776e6c6f616473)](https://packagist.org/packages/johind/collate)

Collate is a Laravel package that provides a fluent API for processing PDFs.

Powered by [qpdf](https://qpdf.readthedocs.io/), it supports common operations including merging, splitting, extracting pages, watermarking, encryption, editing metadata, and web optimisation.

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

[](#requirements)

- PHP 8.4+
- Laravel 11, 12, or 13
- [qpdf](https://qpdf.readthedocs.io/) v11.7.1 or higher installed on your system

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

[](#installation)

Install the package via Composer:

```
composer require johind/collate
```

Then run the install command to publish the configuration and verify that `qpdf` is available:

```
php artisan collate:install
```

You may also publish the config file manually:

```
php artisan vendor:publish --tag="collate-config"
```

Configuration
-------------

[](#configuration)

The published config file (`config/collate.php`) contains three options:

```
return [
    // Path to the qpdf binary (default: 'qpdf')
    'binary_path' => env('COLLATE_BINARY_PATH', 'qpdf'),

    // Default filesystem disk for reading/writing PDFs (default: null, uses your app's default disk)
    'default_disk' => env('COLLATE_DISK'),

    // Directory for temporary files during processing (automatically cleaned up)
    'temp_directory' => env('COLLATE_TEMP_DIR', storage_path('app/collate')),
];
```

Quick Examples
--------------

[](#quick-examples)

```
use Johind\Collate\Facades\Collate;

// Prepare an uploaded document for archival
Collate::open($request->file('document'))
    ->addPages('legal/standard-terms.pdf')
    ->withMetadata(title: 'Client Report 2025')
    ->encrypt('client-password')
    ->toDisk('s3')
    ->save('reports/final.pdf');

// Merge and optimize multiple files for web viewing
Collate::merge('cover.pdf', 'chapter-1.pdf', 'chapter-2.pdf')
    ->overlay('branding/watermark.pdf')
    ->linearize()
    ->save('book.pdf');
```

Capabilities
------------

[](#capabilities)

CategoryFeatures**Getting started**[open](#opening-a-pdf) · [choose a disk](#choosing-a-disk) · [save](#save-to-disk) · [download](#download) · [stream](#stream-inline) · [raw content](#raw-content)**Page operations**[merge](#merging-pdfs) · [split](#splitting-a-pdf) · [add](#adding-pages) · [remove](#removing-pages) · [extract](#extracting-pages) · [rotate](#rotating-pages)**Overlays &amp; watermarks**[overlay &amp; underlay](#overlays--underlays)**Security**[encrypt / decrypt](#encryption--decryption) · [restrict permissions](#encryption--decryption)**Metadata &amp; inspection**[read metadata](#reading-metadata) · [write metadata](#writing-metadata) · [page count](#reading-metadata)**Optimization**[flatten · linearize](#flattening--linearization)**Advanced**[conditional operations](#conditional-operations) · [macros](#extending-with-macros) · [debugging](#debugging-the-qpdf-command) · [error handling](#error-handling)Getting Started
---------------

[](#getting-started)

Use `open()` to manipulate an existing PDF, or `merge()` to combine multiple files. Both return a fluent builder you can chain before saving or returning a response.

### Opening a PDF

[](#opening-a-pdf)

```
use Johind\Collate\Facades\Collate;

$pending = Collate::open('invoices/2024-001.pdf');
```

Files are resolved from your configured filesystem disk. You can also pass `UploadedFile` instances:

```
Collate::open($request->file('document'));
```

### Choosing a Disk

[](#choosing-a-disk)

Switch disks on the fly using `fromDisk()`:

```
Collate::fromDisk('s3')->open('reports/quarterly.pdf')->toDisk('local')->save('quarterly.pdf');
```

### Save to Disk

[](#save-to-disk)

```
Collate::open('input.pdf')->save('output.pdf');
```

### Download

[](#download)

Return a download response from a controller. The filename defaults to `document.pdf` when omitted:

```
return Collate::open('invoice.pdf')
    ->encrypt('client-password')
    ->download('invoice-2024-001.pdf');
```

### Stream Inline

[](#stream-inline)

Display the PDF inline in the browser. The filename defaults to `document.pdf` when omitted:

```
return Collate::merge('cover.pdf', 'report.pdf')
    ->linearize()
    ->stream('quarterly-report.pdf');
```

### Raw Content

[](#raw-content)

Get the raw PDF binary contents as a string. Useful for APIs, email attachments, or custom storage:

```
$content = Collate::open('document.pdf')->content();
```

### Returning from Controllers

[](#returning-from-controllers)

`PendingCollate` implements Laravel's `Responsable` interface, so you can return it directly from a controller. By default, the PDF is displayed in the browser:

```
public function show()
{
    return Collate::open('invoice.pdf');
}
```

Page Operations
---------------

[](#page-operations)

### Merging PDFs

[](#merging-pdfs)

Combine multiple files into a single document:

```
Collate::merge(
    'documents/cover.pdf',
    'documents/chapter-1.pdf',
    'documents/chapter-2.pdf',
)->save('documents/book.pdf');

// Also accepts a single array of files
Collate::merge(['doc1.pdf', 'doc2.pdf'])->save('merged.pdf');
```

For more control, pass a closure to select specific pages:

```
use Johind\Collate\PendingCollate;

Collate::merge(function (PendingCollate $pdf) {
    $pdf->addPage('documents/cover.pdf', 1);
    $pdf->addPages('documents/appendix.pdf', range: '1-3');
})->save('documents/book.pdf');
```

### Adding Pages

[](#adding-pages)

Append entire files or specific pages to an existing document:

```
Collate::open('report.pdf')
    ->addPage('appendix.pdf', pageNumber: 3)       // single page from another file
    ->addPages('terms.pdf', range: '1-5')          // page range
    ->addPages(['exhibit-a.pdf', 'exhibit-b.pdf']) // multiple complete files
    ->save('final-report.pdf');
```

Important

The `range` parameter cannot be used when passing an array of files. Chain multiple `addPages()` calls instead.

### Removing Pages

[](#removing-pages)

Remove specific pages from a document:

```
Collate::open('document.pdf')
    ->removePage(3)
    ->save('without-page-3.pdf');

Collate::open('document.pdf')
    ->removePages([1, 3, 5])
    ->save('trimmed.pdf');

// Remove a range of pages
Collate::open('document.pdf')
    ->removePages('5-10')
    ->save('trimmed.pdf');
```

### Extracting Pages

[](#extracting-pages)

Keep only the pages you need using `onlyPages()`:

```
Collate::open('document.pdf')
    ->onlyPages([1, 2, 3])
    ->save('first-three-pages.pdf');

// Also accepts qpdf range expressions
Collate::open('document.pdf')
    ->onlyPages('1-5,8,11-z')
    ->save('selected-pages.pdf');
```

Warning

`onlyPages()` and `removePages()` are mutually exclusive and neither can be called more than once — calling both, or calling either twice, on the same instance will throw a `BadMethodCallException`.

### Page Range Syntax

[](#page-range-syntax)

Anywhere a page range string is accepted (`onlyPages()`, `addPages()`, `removePages()`, `rotate()`), you can use [qpdf range syntax](https://qpdf.readthedocs.io/en/stable/cli.html#page-ranges):

ExpressionMeaning`1-5`Pages 1 through 5`1,3,5`Pages 1, 3, and 5`1-3,7-9`Pages 1–3 and 7–9`z`Last page`1-z`All pages`1-z:odd`Odd pages only`1-z:even`Even pages only### Splitting a PDF

[](#splitting-a-pdf)

Split every page into its own file. The path supports a `{page}` placeholder for the page number:

```
$paths = Collate::open('multi-page.pdf')
    ->split('pages/page-{page}.pdf');

// $paths → Collection ['pages/page-1.pdf', 'pages/page-2.pdf', ...]
```

Important

Always include `{page}` in your path. Without it, every page will be written to the same destination, with each one overwriting the last.

All operations (page selection, rotation, overlays, etc.) are applied before splitting, so you can chain them freely:

```
Collate::open('scanned.pdf')
    ->rotate(90)
    ->onlyPages('1-5')
    ->split('pages/page-{page}.pdf');
```

### Rotating Pages

[](#rotating-pages)

Rotate pages by 0, 90, 180, or 270 degrees:

```
Collate::open('scanned.pdf')
    ->rotate(90)
    ->save('rotated.pdf');

// Rotate specific pages only
Collate::open('scanned.pdf')
    ->rotate(90, range: '1-3')
    ->rotate(180, range: '5')
    ->save('fixed.pdf');
```

Overlays &amp; Underlays
------------------------

[](#overlays--underlays)

Add watermarks, letterheads, or backgrounds. Both methods accept a disk path or an `UploadedFile` instance:

```
// Overlay (on top — watermarks, stamps)
Collate::open('document.pdf')
    ->overlay('watermark.pdf')
    ->save('watermarked.pdf');

// Underlay (behind — backgrounds, letterheads)
Collate::open('content.pdf')
    ->underlay('letterhead.pdf')
    ->save('branded.pdf');
```

Encryption &amp; Decryption
---------------------------

[](#encryption--decryption)

Encrypt a document with a password:

```
Collate::open('confidential.pdf')
    ->encrypt('secret')
    ->save('protected.pdf');
```

For more control, use separate user and owner passwords and restrict specific permissions. Note that `restrict()` must be called after `encrypt()`:

```
Collate::open('confidential.pdf')
    ->encrypt(
        userPassword: 'secret',
        ownerPassword: 'more-secret',
        bitLength: 256,
    )
    ->restrict('print', 'extract')
    ->save('locked.pdf');
```

The following permissions can be passed to `restrict()`:

PermissionEffect`print`Disallow printing`modify`Disallow modifications`extract`Disallow text and image extraction`annotate`Disallow adding annotations`assemble`Disallow page assembly (inserting, rotating, etc.)`print-highres`Disallow high-resolution printing`form`Disallow filling in form fields`modify-other`Disallow all other modificationsDecrypt a password-protected document:

```
Collate::open('locked.pdf')
    ->decrypt('secret')
    ->save('unlocked.pdf');
```

Re-encrypt with a new password in one step:

```
Collate::open('locked.pdf')
    ->decrypt('old-password')
    ->encrypt('new-password')
    ->save('re-encrypted.pdf');
```

Metadata &amp; Inspection
-------------------------

[](#metadata--inspection)

### Reading Metadata

[](#reading-metadata)

Use `inspect()` (a semantic alias for `open()`) for read-only operations like reading metadata or counting pages:

```
$meta = Collate::inspect('document.pdf')->metadata();

$meta->title;        // 'Quarterly Report'
$meta->author;       // 'Taylor Otwell'
$meta->subject;
$meta->keywords;
$meta->creator;
$meta->producer;
$meta->creationDate;
$meta->modDate;

$count = Collate::inspect('document.pdf')->pageCount();
```

`pageCount()` and `metadata()` are also available on the builder if you need them mid-chain, even after a `merge()`:

```
Collate::merge('doc1.pdf', 'doc2.pdf')
    ->when(fn ($pdf) => $pdf->pageCount() > 10, fn ($pdf) => $pdf->rotate(90))
    ->save('merged.pdf');
```

### Writing Metadata

[](#writing-metadata)

Set metadata on the output document:

```
Collate::open('document.pdf')
    ->withMetadata(
        title: 'Annual Report 2024',
        author: 'Taylor Otwell',
    )
    ->save('branded-report.pdf');

// Also accepts a PdfMetadata instance (named parameters override its values)
$meta = Collate::inspect('source.pdf')->metadata();
Collate::open('target.pdf')
    ->withMetadata($meta, author: 'New Author')
    ->withMetadata(title: 'Updated Title')
    ->save('output.pdf');
```

Note

When you pass a `PdfMetadata` instance, you can override any named fields in the same call except `title`. To change the title, call `withMetadata()` again with `title:` as shown above.

Flattening &amp; Linearization
------------------------------

[](#flattening--linearization)

Flatten form fields and annotations into the page content, or optimize a PDF for fast web viewing:

```
Collate::open('form-filled.pdf')->flatten()->save('flattened.pdf');

Collate::open('large-report.pdf')->linearize()->save('web-optimized.pdf');
```

Advanced
--------

[](#advanced)

### Conditional Operations

[](#conditional-operations)

`PendingCollate` uses the `Conditionable` trait, so you can conditionally apply operations:

```
Collate::open('document.pdf')
    ->when($request->boolean('watermark'), fn ($pdf) => $pdf->overlay('watermark.pdf'))
    ->when($request->boolean('flatten'), fn ($pdf) => $pdf->flatten())
    ->save('output.pdf');
```

### Extending with Macros

[](#extending-with-macros)

Register macros on `PendingCollate` to add chainable operations:

```
use Johind\Collate\PendingCollate;

PendingCollate::macro('stamp', function () {
    return $this->overlay('assets/stamp.pdf');
});

Collate::open('contract.pdf')->stamp()->save('stamped.pdf');
```

Register macros on `Collate` to add new entry points:

```
use Johind\Collate\Collate;

Collate::macro('openInvoice', function (int $invoiceId) {
    return $this->open("invoices/{$invoiceId}.pdf");
});

Collate::openInvoice(2024001)->download();
```

### Debugging the qpdf Command

[](#debugging-the-qpdf-command)

Use `dump()` and `dd()` to inspect the underlying qpdf command that Collate builds, without executing it:

```
Collate::open('document.pdf')
    ->rotate(90)
    ->encrypt('secret')
    ->dump();  // dumps the command and continues the chain

Collate::open('document.pdf')
    ->overlay('watermark.pdf')
    ->dd();    // dumps the command and stops execution
```

Warning

The output may contain sensitive data such as file paths and passwords.

### Error Handling

[](#error-handling)

All exceptions thrown by Collate extend `Johind\Collate\Exceptions\CollateException`, which itself extends PHP's `RuntimeException`.

When a `qpdf` command fails, a `Johind\Collate\Exceptions\ProcessFailedException` is thrown, exposing the `exitCode`and `errorOutput` from the underlying process. Invalid arguments (bad page ranges, unsupported rotation degrees, etc.) throw standard `InvalidArgumentException` or `BadMethodCallException` instances.

```
use Johind\Collate\Exceptions\ProcessFailedException;

try {
    Collate::open('corrupted.pdf')->save('output.pdf');
} catch (ProcessFailedException $e) {
    $e->exitCode;    // qpdf exit code
    $e->errorOutput; // stderr from qpdf
}
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Thank you for your help in keeping Collate stable! I am primarily looking for contributions that focus on fixing bugs, improving error handling or enhancing performance. If you have an idea for a new feature, please open an issue to discuss it with me first, since I want to ensure that the scope of the package remains focused. Please note that I do not provide monetary compensation for contributions.

Security
--------

[](#security)

If you discover a security vulnerability, please send an email rather than opening a GitHub issue.

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

45

—

FairBetter than 92% of packages

Maintenance96

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity57

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~0 days

Total

11

Last Release

52d ago

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

v1.4.3PHP ^8.4 || ^8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/f5f531f5b08f2538e350a08ec17aa3d22e75e4142fa5237b6b4819c5988d9dce?d=identicon)[johind](/maintainers/johind)

---

Top Contributors

[![johind](https://avatars.githubusercontent.com/u/10245695?v=4)](https://github.com/johind "johind (110 commits)")

---

Tags

encryptionextractlaravelmergepackagepdfphpqpdfsplitwatermarklaravelpdfqpdfpdf-manipulationmerge-pdfsplit-pdfai-preparation

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/johind-collate/health.svg)

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

###  Alternatives

[spatie/laravel-pdf

Create PDFs in Laravel apps

9963.4M11](/packages/spatie-laravel-pdf)[vormkracht10/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

24149.7k](/packages/vormkracht10-laravel-mails)[codersfree/laravel-greenter

Laravel package for Greenter

261.4k](/packages/codersfree-laravel-greenter)[samuelterra22/laravel-report-generator

Rapidly Generate Simple Pdf, Excel &amp; CSV Reports on Laravel (Using Barryvdh/DomPdf or Barryvdh/laravel-snappy &amp; maatwebsite/excel)

125.3k](/packages/samuelterra22-laravel-report-generator)

PHPackages © 2026

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