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 Tcpdf Laravel Package

setasign/fpdi-tcpdf

Lightweight bridge between FPDI and TCPDF that lets PHP developers import pages from existing PDF files directly into new TCPDF documents. Handy for merging files, adding stamps or watermarks, reordering pages, and assembling reports without rebuilding layouts from scratch.

Deep Wiki
Context7

Getting Started

What you are actually installing. setasign/fpdi-tcpdf is a thin Composer package: it pins compatible versions of setasign/fpdi and tecnickcom/tcpdf and ships the bridge class setasign\Fpdi\Tcpdf\Fpdi (a TCPDF subclass that pulls in FpdiTrait + FpdfTplTrait). It is archived as of 2020-02-19, so treat it as vendored legacy code, not a dependency you can count on. First action on any project that has it: composer why setasign/fpdi-tcpdf and composer show setasign/fpdi-tcpdf setasign/fpdi tecnickcom/tcpdf to see the versions you are actually locked to.

Where to look first. vendor/setasign/fpdi-tcpdf/src/Tcpdf/Fpdi.php — that one file is the whole bridge and it is MIT-licensed, which matters later. Then the FPDI docs for setSourceFile / importPage / useTemplate.

First use case: stamp dynamic data onto a supplied PDF. Almost every day-to-day use of this package is "customer/template PDF in, personalised PDF out."

use setasign\Fpdi\Tcpdf\Fpdi;

$pdf = new Fpdi('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->setAutoPageBreak(false);

$pageCount = $pdf->setSourceFile(storage_path('app/templates/invoice.pdf'));

for ($i = 1; $i <= $pageCount; $i++) {
    $tpl  = $pdf->importPage($i);
    $size = $pdf->getTemplateSize($tpl);          // in mm, the document unit

    $pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
    $pdf->useTemplate($tpl, 0, 0, $size['width'], $size['height']);

    if ($i === 1) {
        $pdf->SetFont('helvetica', '', 11);
        $pdf->SetXY(150, 40);
        $pdf->Cell(40, 6, 'INV-2024-0042', 0, 0, 'R');
    }
}

return response()->streamDownload(
    fn () => print $pdf->Output('invoice.pdf', 'S'),
    'invoice.pdf',
    ['Content-Type' => 'application/pdf']
);

Implementation Patterns

1. Template stamping as a service. Wrap the bridge in a single injectable class rather than instantiating Fpdi across controllers. This is the seam that lets you swap the library or vendor the bridge class later without touching call sites.

final class PdfStamper
{
    public function __construct(private array $config) {}

    public function stamp(string $sourcePath, array $fields): string
    {
        $pdf = $this->make();
        // ... setSourceFile / importPage / useTemplate / Cell ...
        return $pdf->Output('', 'S');   // raw bytes
    }
}

2. Merge / append. Call setSourceFile() once per file, then loop pages. Do not re-call it per page — it resets parser state and is measurably slower on large sources.

foreach ($paths as $path) {
    $pages = $pdf->setSourceFile($path);
    for ($i = 1; $i <= $pages; $i++) {
        $tpl  = $pdf->importPage($i);
        $size = $pdf->getTemplateSize($tpl);
        $pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
        $pdf->useTemplate($tpl, 0, 0, $size['width'], $size['height']);
    }
}

3. Overlay / watermark. Import the content page as the background template, then draw on top. For repeated overlays (letterhead on every page) build it once as an FPDI template via the FpdfTplTrait and useTemplate it repeatedly instead of re-importing — this keeps the output size flat.

4. N-up imposition. Place several imported pages on one TCPDF page by computing a grid and passing explicit w/h to useTemplate, preserving aspect ratio yourself:

$size = $pdf->getTemplateSize($tpl);
$ratio = $size['height'] / $size['width'];
$w = $cellWidth;
$h = $w * $ratio;
$pdf->useTemplate($tpl, $x, $y, $w, $h);

5. Laravel integration tips. Bind K_PATH_CACHE to storage/app/tcpdf (created and writable) before instantiating — TCPDF writes temp files there and fails opaquely otherwise. For anything over a few dozen pages, push the work to a queue job, return the bytes from Output($name, 'S'), and Storage::put() them; stream from the controller afterwards.

6. The exit pattern (do this early). MIT licensing makes extraction trivial:

composer remove setasign/fpdi-tcpdf
composer require setasign/fpdi:^2.6 tecnickcom/tcpdf:^6.7

Then copy vendor/setasign/fpdi-tcpdf/src/Tcpdf/Fpdi.php into app/Support/Pdf/TcpdfFpdi.php, change the namespace to App\Support\Pdf, and rewrite the use setasign\Fpdi\Tcpdf\Fpdi; imports. The class is self-contained (it extends \TCPDF and only uses FPDI traits), so this decouples you from the abandoned package while keeping behaviour byte-identical. It also lets you unpin TCPDF, which is the real win — TCPDF itself is still maintained and your pinned copy almost certainly is not.

Gotchas and Tips

  • The pin is the product, and the pin is the problem. The package's value was version alignment; its cost is that it blocks TCPDF upgrades, PHP 8.x compatibility fixes and security patches. Any composer update that needs a newer tecnickcom/tcpdf will conflict with it. This alone justifies the extraction pattern above on day one.
  • Disable TCPDF's furniture. setPrintHeader(false), setPrintFooter(false), setAutoPageBreak(false) before adding imported pages, or TCPDF will draw headers/footers and inject page breaks into your imported content. Also set setMargins(0,0,0) when positioning with absolute coordinates.
  • Units mismatch. TCPDF defaults to mm; the source PDF is in points. getTemplateSize() returns values in your document unit, so use those for both AddPage() and useTemplate() — never mix a hard-coded point size with an mm page.
  • Always pass explicit width and height to useTemplate(). The auto-scaling behaviour when only one dimension is supplied has bitten enough people that explicit values are the safer convention; compute aspect ratio yourself.
  • Order of operations is fixed: AddPage() → useTemplate(). Calling useTemplate() before a page exists throws; calling importPage() after switching source files invalidates prior template IDs.
  • Encrypted PDFs. Owner-password-only files generally work; user-password files need the password. If setSourceFile() throws PdfParserException / CrossReferenceException on a file that opens fine in a viewer, encryption is the first thing to check. Wrap parsing in try/catch and surface a domain error rather than a 500.
  • Form fields are flattened. Imported AcroForm fields render visually but are not interactive and are not fillable by TCPDF. If the requirement is "fillable PDF," this package is the wrong tool — use a form-aware library. Link annotations, on the other hand, are preserved.
  • File size balloons on repetition. Importing the same heavy page N times embeds the content stream N times. Reuse an FPDI template (or the same template ID) when the source page does not change.
  • Memory and temp files. Large merges are RAM-hungry; raise memory_limit in the job, and make sure K_PATH_CACHE exists and is writable. A K_PATH_CACHE pointing at a non-existent directory produces confusing TCPDF errors rather than a clear "permission denied."
  • Debugging. Set $pdf->SetCreator()/SetTitle() for sanity in output, and catch FPDI exceptions separately from TCPDF's Error() abort path. $pdf->Output($path, 'F') vs 'S' is the difference between a side effect and a return value — in Laravel always prefer 'S' and let the framework handle delivery.
  • Extension point. If you need to change page-box handling or setPageFormat behaviour, subclass the bridge (or your vendored copy) rather than patching vendor/; the overridden methods worth knowing are _beginpage, _endpage, and setPageFormat, which is exactly where the FPDI/TCPDF impedance mismatch lives.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin