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

Fpdi Fpdf Laravel Package

setasign/fpdi-fpdf

Setasign FPDI-FPDF combines FPDI with FPDF so PHP applications can import pages from existing PDFs and place them into new FPDF-generated documents. Useful for merging, watermarking, stamping, and templating PDFs without external tools.

Deep Wiki
Context7

Getting Started

setasign/fpdi-fpdf is not a library — it is an abandoned Composer metapackage whose only job was to pin compatible versions of setasign/fpdi and setasign/fpdf. Installing it gives you no classes of its own. Because it was archived in February 2020, the correct "first step" is to not install it and instead require the concrete packages directly:

composer remove setasign/fpdi-fpdf   # if already present
composer require setasign/fpdi setasign/fpdf

Those two packages give you the actual runtime you care about:

  • setasign/fpdf — the classic FPDF generator (global FPDF class).
  • setasign/fpdi — setasign\Fpdi\Fpdi, which extends FPDF and adds PDF importing (setSourceFile, importPage, useTemplate).

Look first at the FPDI source in vendor/setasign/fpdi/src/Fpdi.php and the FpdiTrait; that is where 90% of the day-to-day API lives. There is no Laravel service provider, no config file, and nothing to publish.

Canonical first use case: you have an existing PDF (invoice, contract, certificate) and need to stamp text or a signature onto every page without regenerating the document.

use setasign\Fpdi\Fpdi;

$pdf = new Fpdi();
$pageCount = $pdf->setSourceFile(storage_path('app/invoices/1024.pdf'));

for ($i = 1; $i <= $pageCount; $i++) {
    $tpl  = $pdf->importPage($i);
    $size = $pdf->getTemplateSize($tpl);           // ['width'=>..,'height'=>..,'orientation'=>'P']
    $pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
    $pdf->useTemplate($tpl);

    $pdf->SetFont('Helvetica', 'B', 12);
    $pdf->SetTextColor(200, 0, 0);
    $pdf->SetXY(10, $size['height'] - 20);
    $pdf->Cell(0, 10, 'PAID', 0, 0, 'C');
}

file_put_contents(storage_path('app/invoices/1024-paid.pdf'), $pdf->Output('S'));

Output('S') returns the PDF as a string instead of writing to the browser — this is the form you want in a framework context.

Implementation Patterns

Wrap it in a service, not in controllers. FPDI/FPDF are mutable objects with global-ish state; keeping construction and output in one place prevents "why is my font wrong on page 3" bugs.

namespace App\Services;

use setasign\Fpdi\Fpdi;

class PdfStamper
{
    public function stamp(string $sourcePath, string $text): string
    {
        $pdf = new Fpdi();
        $pdf->setSourceFile($sourcePath);
        // ... loop as above ...

        return $pdf->Output('S');
    }
}

Bind it in a service provider if you want to swap it in tests, and inject it into controllers or jobs.

Return it as a download without touching the filesystem.

public function download(Order $order, PdfStamper $stamper)
{
    $bytes = $stamper->stamp(
        \Storage::path($order->invoice_path),
        'PAID ' . now()->toDateString()
    );

    return response($bytes, 200, [
        'Content-Type'        => 'application/pdf',
        'Content-Disposition' => 'attachment; filename="invoice-'.$order->id.'.pdf"',
    ]);
}

Use response()->streamDownload() if the output can be large, and Storage::path() / Storage::disk()->path() rather than concatenating storage_path() by hand.

Push batch work into a queued job. Generating 500 stamped invoices in a request is a memory and timeout problem, not an FPDI problem. set_time_limit(0), raise memory_limit, write results to Storage, and let the job update a status column.

Merging multiple PDFs is the other common pattern. Loop the sources, call setSourceFile() per source, import each page, and append. There is no "append file" shortcut — every page is imported individually.

Testing: keep a couple of small fixture PDFs in tests/Fixtures and assert on %PDF- header plus page count (e.g. with a quick preg_match_all('/\/Type\s*\/Page[^s]/', $bytes)). Do not try to assert on rendered pixels.

Config quirks: none, by design. Units default to millimeters, page format to A4, and compression to on. Set these explicitly at the top of your service ($pdf->SetCompression(true)), because they are object state, not app config.

Gotchas and Tips

  • It is abandoned. composer install will flag it; composer audit and Dependabot will nag. The fix is to require setasign/fpdi and setasign/fpdf directly and drop the metapackage. Your lockfile will otherwise carry a dead transitive node indefinitely.
  • FPDF is very old. setasign/fpdf (the 1.8.x line) predates PHP 8 and can emit deprecation/notice chatter on 8.1+ (dynamic properties, implicit float-to-int). If it breaks under APP_DEBUG=false-style strict error handling, either raise it to the newest 1.8.x or silence notices around the call — do not patch vendor/.
  • No UTF-8 out of the box. Core fonts (Helvetica, Times, Courier) are Latin-1 only. Emoji, CJK, or accented names will render as mojibake. You must add a TTF via AddFont() and use iconv/mb_convert_encoding for the text.
  • Parser limits on modern PDFs. PDFs using compressed cross-reference streams (1.5+) or PDF 2.0 can fail with "Cannot find xref table" / "Unable to find xref". FPDI ships a free parser with version limits; setasign sells a commercial parser for the rest. Always test with real customer PDFs, not ones you generated yourself.
  • setSourceFile() is stateful and returns the page count. Call it once per source document; calling it again mid-import resets the template registry and your previously imported page IDs become invalid.
  • Imported page IDs are per-source. After switching source files, an old $tpl must not be reused. Re-import inside the loop for multi-document merges.
  • Rotation and CropBox. Some scanned PDFs are rotated 90°/270° in the page dictionary. useTemplate() usually respects this, but if content appears clipped, pass explicit offsets ($pdf->useTemplate($tpl, 0, 0, $w, $h, true)) and inspect getTemplateSize() rather than assuming portrait.
  • Origin is top-left, units are mm by default. Coordinates from design tools (often points, bottom-left origin) need conversion. 72pt = 25.4mm.
  • Output() modes differ. 'S' returns a string, 'D' sends headers, 'F' writes a file. Mixing 'I' with a Laravel Response produces a corrupted download because FPDF has already flushed headers.
  • Memory. FPDI holds the whole source document and the whole output in memory. Multi-hundred-page PDFs in a worker will hit memory_limit; cap file size and page count at validation.
  • Global FPDF class name. FPDF is not namespaced; FPDI is (setasign\Fpdi\Fpdi). If you already have another library defining FPDF (e.g. an old TCPDF shim), you get a fatal "cannot redeclare class". Check composer why setasign/fpdf before adding a second PDF stack.
  • You can stamp, not edit. Imported pages are templates; you can place text/images/lines over them but cannot modify existing content, delete a table row, or extract text. If requirements drift toward "fill this form field", FPDI is the wrong tool — use a form-filling library (setasign/fpdi + FPDI_Protection is commercial; pdftk/qpdf via shell are alternatives).
  • Version pinning conflict. The metapackage pinned specific FPDI/FPDF versions. If you have an unrelated package requiring tecnickcom/tcpdf, the fpdi-tfpdf variant and this one cannot coexist cleanly. Resolve with composer why-not and pick one PDF engine.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core