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

Tcpdf Laravel Laravel Package

elibyy/tcpdf-laravel

elibyy/tcpdf-laravel is a Laravel wrapper for TCPDF that adds a service provider, facade, and publishable configuration. It simplifies generating, customizing, and outputting PDFs from Laravel apps, so you can stream, download, or save documents with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire-up

composer require elibyy/tcpdf-laravel
php artisan vendor:publish --provider="Elibyy\TCPDF\ServiceProvider" --tag=config

On Laravel 5.5+ the provider and TCPDF alias are auto-discovered. On older skeletons (or if you disable discovery), register manually:

// config/app.php
'providers' => [ /* ... */ Elibyy\TCPDF\ServiceProvider::class ],
'aliases'   => [ /* ... */ 'TCPDF' => Elibyy\TCPDF\Facades\TCPDF::class ],

Where to look first

  1. config/tcpdf.php — the published config controls the constructor arguments (page_format, page_orientation, unit, unicode, encoding, diskcache, pdfa) plus default fonts and paths. This is the first file to read when output looks wrong.
  2. Elibyy\TCPDF\Facades\TCPDF — it is a thin proxy. Every method call is forwarded to an Elibyy\TCPDF\TCPDF instance, so the entire TCPDF 6.x API is available: Cell, MultiCell, writeHTML, Image, Output, etc.
  3. vendor/elibyy/tcpdf-laravel/src/TCPDF.php — shows exactly how config keys map to the parent constructor and to TCPDF constants. Read it before filing a "my config is ignored" issue.

First use case — download an invoice

use Elibyy\TCPDF\Facades\TCPDF;

Route::get('/invoices/{id}/pdf', function ($id) {
    TCPDF::SetTitle("Invoice #{$id}");
    TCPDF::SetAuthor(config('app.name'));
    TCPDF::AddPage();
    TCPDF::SetFont('helvetica', '', 12);
    TCPDF::Write(0, 'Hello from TCPDF');

    return response(TCPDF::Output('invoice.pdf', 'S'))
        ->header('Content-Type', 'application/pdf');
});

Note the 'S' output mode: it returns the PDF as a string, which you hand to a normal Laravel response. 'I'/'D' echo bytes and send headers directly and are usually the wrong choice inside a framework (see Gotchas).

Implementation Patterns

1. Choose the right Output() mode

Mode Behaviour Use when
S Returns string Small/medium PDFs returned via response()
F Writes file server-side Large PDFs, queued generation
D Forces download Never inside controllers — use a response
I Inline to browser Only in a bare script with no middleware
// Stream a large document without buffering the whole string
TCPDF::Output(storage_path("app/invoices/{$id}.pdf"), 'F');
return response()->download(storage_path("app/invoices/{$id}.pdf"))
                 ->deleteFileAfterSend();

2. Blade → HTML → PDF (the most common real-world pattern)

TCPDF has no Blade engine, so render the view yourself and hand the HTML to writeHTML:

$html = view('pdf.invoice', ['invoice' => $invoice])->render();

TCPDF::AddPage();
TCPDF::SetFont('dejavusans', '', 10);
TCPDF::writeHTML($html, true, false, true, false, '');
return response(TCPDF::Output("invoice-{$invoice->id}.pdf", 'S'))
    ->header('Content-Type', 'application/pdf');

Keep the Blade view to tables and inline styles — TCPDF's CSS support is roughly CSS 2.1 with tables only. No flexbox, no grid, no external stylesheets.

3. Custom header/footer — subclass, then bind

Override Header()/Footer() on a subclass and put it in the container under the key the facade resolves (tcpdf) in a service provider:

namespace App\Pdf;

class BrandedPdf extends \Elibyy\TCPDF\TCPDF
{
    public function Header(): void
    {
        $this->SetY(8);
        $this->SetFont('helvetica', 'B', 9);
        $this->Cell(0, 8, config('app.name'), 0, false, 'R');
    }

    public function Footer(): void
    {
        $this->SetY(-12);
        $this->SetFont('helvetica', '', 8);
        $this->Cell(0, 8, $this->getAliasNumPage().' / '.$this->getAliasNbPages(), 0, false, 'C');
    }
}

// AppServiceProvider::register()
$this->app->bind('tcpdf', fn () => new \App\Pdf\BrandedPdf());

Call setHeaderFont()/setFooterFont() and set margins before the first AddPage(); after that they no longer apply.

4. One request, several PDFs

The facade resolves a single shared instance. For multi-document endpoints (e.g. batch statements), skip the facade and instantiate directly:

foreach ($customers as $customer) {
    $pdf = new \Elibyy\TCPDF\TCPDF();
    $pdf->AddPage();
    // ...
    $pdf->Output(storage_path("app/statements/{$customer->id}.pdf"), 'F');
}

If you insist on the facade, reset between documents:

\Illuminate\Support\Facades\Facade::clearResolvedInstance('tcpdf');

5. Per-document config overrides

Because the instance is built from config the first time it is resolved, mutate config before the first facade call:

config(['tcpdf.page_orientation' => 'L', 'tcpdf.page_format' => 'A4']);
TCPDF::AddPage();

Or pass parameters straight to TCPDF methods (SetMargins, setPageFormat, SetFont) — usually clearer than fighting the config layer.

6. Queue heavy generation

PDF rendering is CPU- and memory-bound. Generate 'F' to storage in a job, attach the path to a mailable, and keep the HTTP request thin. This also sidesteps the facade-state problem if you instantiate fresh instances per job.

7. Testing

Output('', 'S') is easy to assert against:

$pdf = TCPDF::Output('', 'S');
$this->assertStringStartsWith('%PDF-', $pdf);

Gotchas and Tips

The facade is stateful. Laravel's Facade caches the resolved instance for the lifetime of the process. In a single request, every TCPDF:: call mutates the same document — fine for one PDF, disastrous for two, and genuinely dangerous in queue workers where the process is reused across jobs. Fix: Facade::clearResolvedInstance('tcpdf') before reuse, or instantiate new \Elibyy\TCPDF\TCPDF() directly.

Never use Output(..., 'I') from a controller. It echoes bytes and emits raw headers, bypassing Laravel's response pipeline. You will get corrupted output with Debugbar, gzip middleware, Content-Length assumptions, or PHPUnit's output buffering. Always return 'S' in a response or 'F' + response()->download().

Absolute paths only. TCPDF resolves image and font paths relative to the current working directory, which differs between CLI (project root) and FPM (often public/). Always use public_path(), storage_path(), or an absolute path:

TCPDF::Image(public_path('img/logo.png'), 15, 10, 30, 0, '', '', '', true, 300);

K_TCPDF_EXTERNAL_CONFIG is set by the wrapper. TCPDF's own tcpdf_config.php is disabled so the Laravel config wins. When you see "undefined constant" or "font not found" errors, it is almost always the wrapper's config-to-constant mapping — check the published config and the vendor TCPDF.php before assuming a TCPDF bug.

Unicode needs a Unicode font. With 'unicode' => true and a UTF-8 document, helvetica will mangle anything outside Latin-1. Set SetFont('dejavusans', '', 10) (or freeserif, or add your own font) for accented characters, Cyrillic, CJK, and the € sign.

HTML is not HTML5. writeHTML supports tables, inline styles, <b>/<i>/<u>, <br>, limited <a>. No flexbox, no float-based layout tricks that depend on modern CSS, no <script>, and page breaks only via page-break-* on block elements (patchily). Prototype the HTML in a browser, but verify pagination in the PDF.

Font subsetting is the usual performance culprit. Large documents with many writeHTML calls get slow and memory-hungry. Mitigations: $pdf->setFontSubsetting(false) when you don't need it, enable 'diskcache' => true for big documents, raise memory_limit, and move generation to a queue.

Config cache freezes config/tcpdf.php. After php artisan config:cache, editing the file does nothing until you re-cache. Similarly, config([...]) overrides applied after the facade has resolved are silently ignored.

Header/Footer callbacks must never throw. An exception inside Header() or Footer() surfaces as a broken or blank page with little context. Wrap custom logic in try/catch and log.

PDF/A is a mode switch, not a checkbox. Setting 'pdfa' => true changes constructor behaviour and restricts output/encryption. Validate the result with an external PDF/A checker before shipping; do not assume the flag alone produces compliant files.

TCPDF 6.x is legacy. The wrapper pins the TCPDF line, which predates modern PHP. Expect deprecation notices on PHP 8.1+, and pin the wrapper version in composer.json — treat a minor bump as a regression-test event, since Output() signatures and font handling have shifted historically between TCPDF generations.

Extension points to remember: bind your own subclass under the tcpdf container key; override config at runtime before first resolution; use setPrintHeader(false) / setPrintFooter(false) for full-bleed cover pages; and reach for the underlying TCPDF object via TCPDF::getInstance()-style access only when the facade genuinely can't express what you need.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource