Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pdf Merger Laravel Package

tomsgu/pdf-merger

A Laravel/PHP package for merging multiple PDF files into one document. It offers a simple API to combine PDFs, control page order, and add PDF merging to your workflows with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it as a normal Composer dependency — it is framework-agnostic and ships no Laravel service provider:

composer require tomsgu/pdf-merger

It pulls in setasign/fpdi and setasign/fpdf transitively, so you get PDF parsing (FPDI) and PDF writing (FPDF) without wiring anything else up. Requirements are modest: PHP >= 7.2 plus the usual FPDF/FPDI extensions (zlib, mbstring, and GD if you later want images in the output).

Where to look first:

  1. README.md — the entire public surface is there in ~20 lines.
  2. src/PDFMerger.php — the whole class. Expect essentially two meaningful methods: addPDF($filePath, $pages = 'all') and merge($outputMode = 'browser', $outputPath = 'output/default.pdf').
  3. src/PDFMergerInterface.php (if present in your tagged version) — use it for type-hinting in your own wrappers.

First use case — combine a contract and its appendix into a single file inside a Laravel app:

use Tomsgu\PDFMerger\PDFMerger;

$pdf = new PDFMerger();
$pdf->addPDF(storage_path('app/contracts/base.pdf'), 'all');
$pdf->addPDF(storage_path('app/contracts/appendix.pdf'), '1-3');

$pdf->merge('file', storage_path('app/contracts/merged.pdf'));

Page selection accepts 'all', a single page ('2'), a list ('1, 3, 4'), or ranges ('1-3'). Page numbers are 1-based, matching what readers show, not what the PDF's internal /Pages array shows.

For an HTTP download, prefer the string mode over the browser mode so you stay inside Laravel's response lifecycle:

$binary = $pdf->merge('string');

return response($binary)
    ->header('Content-Type', 'application/pdf')
    ->header('Content-Disposition', 'attachment; filename="merged.pdf"');

Implementation Patterns

Bind it, don't new it everywhere. Even though there's no provider, register a small factory or a scoped binding in AppServiceProvider so you can swap/test it:

$this->app->bind(PDFMergerFactory::class, fn () => new class {
    public function make(): PDFMerger { return new PDFMerger(); }
});

Because PDFMerger accumulates state (addPDF calls queue up files), never reuse an instance across requests — always construct fresh per merge.

Wrap the merge in a domain service. A ContractMerger or DocumentAssembler class keeps controller code clean and gives you a single place to normalise paths:

class DocumentAssembler
{
    public function merge(array $paths, string $disk = 'local'): string
    {
        $pdf = new PDFMerger();
        foreach ($paths as $path) {
            $pdf->addPDF(Storage::disk($disk)->path($path), 'all');
        }
        $pdf->merge('file', $out = Storage::disk($disk)->path('tmp/'.uniqid().'.pdf'));
        return $out;
    }
}

merge('file', ...) writes to disk; merge('string') returns the raw bytes in memory. Use file for anything over a few MB, then stream the result with response()->file() or Storage::download().

Queue large merges. Merging is CPU- and memory-bound inside the PHP process (FPDI parses each source document fully). Move multi-hundred-page or batch merges into a ShouldQueue job, write the result to storage/app/private/, and notify the user via a broadcast or database notification. This also keeps request timeouts out of the picture.

Compose with a page-level builder. A common pattern is a fluent wrapper that records (path, pages) tuples, then flushes them to PDFMerger in one shot — useful for template-driven documents (cover page → body → terms → signature page).

Pair with barryvdh/laravel-dompdf or spatie/laravel-pdf for the "generate then combine" workflow: render HTML sub-documents to temp PDFs, then feed them to PDFMerger. Keep temp files on the local disk and clean them in a finally block.

Gotchas and Tips

  • FPDI restrictions are your restrictions. Encrypted PDFs, PDFs with certain compression schemes, and (depending on the FPDI version resolved) files above PDF 1.7 will throw on addPDF(). User-uploaded PDFs from scanners and office suites are the usual offenders — catch the exception and surface a friendly error rather than a 500.
  • Output directory must exist and be writable. merge('file', ...) will not create parent directories. Call File::ensureDirectoryExists() first; a missing path is a silent-looking failure mode rather than an obvious one.
  • merge('browser') sends headers. It calls header() directly. In Laravel this bypasses the response layer, so it breaks under test (headers already sent) and under middleware that decorates responses. Use 'string' or 'file' in application code; reserve browser/download for throwaway scripts.
  • Merging mutates nothing on disk, but FPDI keeps parsed pages in memory. For a 100-page, image-heavy PDF expect tens of MB of RAM; batch merges of 20+ documents should be chunked or queued with a raised memory_limit.
  • Path resolution is your job. The package takes a filesystem path, not a Storage instance or a URL. Build it with Storage::disk('local')->path($relative) — storage_path('app/'.$relative) works too but hardcodes the app directory, which bites when tests use a different disk root.
  • No metadata helpers. Output metadata (title, author) falls back to FPDF defaults. If you need it, extend PDFMerger, override the merge routine, or set the properties on the underlying FPDF instance after construction — but the public API is thin, so an extension point here means subclassing, not configuration.
  • Low release cadence and a small star count mean you should pin the version in composer.json and add a smoke test that runs an actual merge against a fixture PDF in CI. This catches upstream FPDI/FPDF breakage after composer update.
  • Testing: merge('string') returns the binary, so assert with str_starts_with($binary, '%PDF-') rather than snapshotting bytes. Keep one tiny (2-page) fixture in tests/Fixtures/ — FPDI cannot parse some synthetic PDFs produced by naive generators, so generate your fixture with a real tool like LibreOffice or Dompdf.
  • License is MIT, so vendoring a patched fork or extending the class inside your app is unproblematic if you hit an FPDI edge case you need to work around.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log