Skip to content

Migrate from TCPDF 6.x to NextPDF

Migrate in a clear order. Move to the NextPDF engine first with the smallest possible change. Prove what already works. Audit what does not. Fix each call site. Then remove the adapter. The compatibility layer supports steps two through four; it is not the destination.

This page gives you the strategy. For the exact behavior of any individual method, use /integrations/tcpdf-compat/method-coverage/ alongside the authoritative in-repo matrix docs/TCPDF_COVERAGE.md.

TCPDF 6.x codebase

Swap dependency: install compat-legacy

Run existing suite unchanged

Strict-mode audit: enumerate behavioral gaps

Fix call sites: drop ignored params or move to modern API

Re-baseline byte-level test assertions

Remove the TCPDF dependency

Incrementally retire the adapter onto Document

Diagram

Every stage keeps the application shippable. You never need one all-at-once cutover.

Install nextpdf/compat-legacy (see /integrations/tcpdf-compat/install/). Do not remove tecnickcom/tcpdf yet; keeping both lets you compare results.

Choose how legacy call sites resolve the class:

  • Preferred: change each file’s use/require to use NextPDF\Compat\Tcpdf\TCPDF;. This is explicit and easy to search.
  • When you cannot touch call sites yet: enable opt-in global aliases once at boot with LegacyBootstrap::enableAliases() (see /integrations/tcpdf-compat/boot-and-discovery/). This resolves \TCPDF and the four helper classes to the adapter.

The two strategies are mutually exclusive in practice. If the real TCPDF library remains autoloadable and you enable global aliases, the alias is skipped when a \TCPDF class already exists. You may then keep using legacy TCPDF without noticing. During Stage 1, prefer per-file imports so you know exactly which class each call site uses. See /integrations/tcpdf-compat/troubleshooting/.

Stage 2 — Run the existing suite unchanged

Section titled “Stage 2 — Run the existing suite unchanged”

Run your full test suite against the adapter without changing anything else. Most delegated methods (94 of the ~120 surveyed) behave compatibly. Expect two predictable failure classes:

  1. Byte-level assertions. Tests that compare exact Portable Document Format (PDF) bytes will fail because the engine is an independent implementation. This is expected, not a defect. Defer these to Stage 4.
  2. Return-value branches. A few methods return compatibility placeholders rather than computed values. Most notably, MultiCell() returns 1, and Write() returns 0. Code that branches on those return values needs adjustment.

Catalog every failure. Classify each as byte-baseline, return-value, or true behavioral gap.

This stage makes the migration safe. Run the suite, or a representative production path, with strict mode enabled:

examples/migration-audit.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\Exception\TcpdfNotImplementedException;
use NextPDF\Compat\Tcpdf\TCPDF;
function renderInvoice(TCPDF $pdf): void
{
// ... your existing rendering code, unchanged ...
}
$pdf = new TCPDF('P', 'mm', 'A4');
$pdf->setStrictMode(true);
try {
renderInvoice($pdf);
$pdf->Output(__DIR__ . '/audit.pdf', 'F');
} catch (TcpdfNotImplementedException $e) {
// Each message names the method, the ignored parameters, and a hint.
fwrite(STDERR, 'MIGRATION GAP: ' . $e->getMessage() . "\n");
}

Treat every TcpdfNotImplementedException as one work item. The message gives you the method, the exact ignored parameter list, and a migration hint. The set of throwing methods is enumerated and test-asserted in tests/Unit/Compat/Tcpdf/TcpdfStrictModeTest.php. Each rationale is in docs/TCPDF_COVERAGE.md.

Run strict mode as a dedicated continuous integration (CI) job, not in production. The point is to surface gaps, not to make production throw.

For each gap, pick the cheapest correct fix:

Gap patternFix
Ignored parameter does not matter (e.g. a TCPDF $align you never relied on)Drop the parameter. The call becomes exactly compatible.
Ignored parameter mattered (e.g. a clickable Image() link)Re-express it with the modern API. Draw the image, then add Document::link() over the rectangle.
Method is unimplemented (setSignature(), endPage())endPage() / Open(): remove the call. Signing: see /integrations/tcpdf-compat/security-and-operations/; it requires a commercial edition.
Not-applicable method (setPDFVersion(), setUserRights())Remove the call. Output is always PDF 2.0; user-rights is deprecated in PDF 2.0.
Return-value branchCompute the value yourself, or move that logic to the modern API.

Use the escape hatch when the TCPDF surface cannot express what you need:

examples/migration-escape-hatch.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF();
$pdf->AddPage();
// Legacy path stays as-is for the parts that work:
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 10, 'Header line', 0, 1);
// Modern path for what the TCPDF surface cannot express here:
$document = $pdf->getDocument();
$document->image('logo.png', 10, 30, 40, 0);
$document->link(10, 30, 40, 20, 'https://example.com');

Replace exact-byte assertions with assertions about what matters:

  • The output begins with %PDF and parses (smoke level).
  • Rendered text content is present (extract text and assert on it).
  • Structural properties (page count, page size, and presence of an outline) match.

This one-time cost gives you tests that survive future engine upgrades.

After the strict-mode audit passes, strict mode is off in production, and the suite is green on re-baselined assertions, remove tecnickcom/tcpdf:

Terminal window
composer remove tecnickcom/tcpdf

Run the suite again. If anything still resolves to the real TCPDF class, Stage 1’s alias caveat applied; fix the remaining call sites to import the adapter explicitly.

The adapter is a migration aid, not a permanent layer. After TCPDF is gone and the engine is proven, retire the adapter incrementally:

  1. In each module, replace new TCPDF(...) with the modern NextPDF\Core\Document construction.
  2. Replace TCPDF method calls with their modern equivalents (the getDocument() calls you already added in Stage 4 are the template).
  3. When a module no longer references the adapter, delete its compatibility imports.
  4. When no module references the adapter, remove nextpdf/compat-legacy from composer.json.

At that point, you are on the modern PDF 2.0 API with no compatibility layer.

  • nextpdf/compat-legacy installed; engine link verified.
  • Call sites import the adapter explicitly (or aliases enabled with the real TCPDF removed from the autoload path).
  • Full suite run against the adapter; failures classified.
  • Strict-mode CI job added; every gap cataloged.
  • Each gap fixed (drop parameter / modern API / remove call).
  • Byte-level assertions re-baselined to content and structure.
  • tecnickcom/tcpdf removed; suite green.
  • Adapter retired module by module; dependency removed.
  • /integrations/tcpdf-compat/method-coverage/ — per-method behavior and replacement guidance
  • docs/TCPDF_COVERAGE.md — authoritative, test-verified matrix
  • /integrations/tcpdf-compat/configuration/ — moving configuration off global constants
  • /integrations/tcpdf-compat/security-and-operations/ — encryption and signing during migration
  • /integrations/tcpdf-compat/troubleshooting/ — the alias/real-TCPDF conflict and other traps