Migrate from TCPDF 6.x to NextPDF
At a glance
Section titled “At a glance”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.
Migration model
Section titled “Migration model”Every stage keeps the application shippable. You never need one all-at-once cutover.
Stage 1 — Swap the dependency
Section titled “Stage 1 — Swap the dependency”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/requiretouse 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\TCPDFand 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
\TCPDFclass 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:
- 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.
- Return-value branches. A few methods return compatibility
placeholders rather than computed values. Most notably,
MultiCell()returns1, andWrite()returns0. Code that branches on those return values needs adjustment.
Catalog every failure. Classify each as byte-baseline, return-value, or true behavioral gap.
Stage 3 — Strict-mode audit
Section titled “Stage 3 — Strict-mode audit”This stage makes the migration safe. Run the suite, or a representative production path, with strict mode enabled:
<?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.
Stage 4 — Fix call sites
Section titled “Stage 4 — Fix call sites”For each gap, pick the cheapest correct fix:
| Gap pattern | Fix |
|---|---|
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 branch | Compute the value yourself, or move that logic to the modern API. |
Use the escape hatch when the TCPDF surface cannot express what you need:
<?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');Re-baseline byte-level tests
Section titled “Re-baseline byte-level tests”Replace exact-byte assertions with assertions about what matters:
- The output begins with
%PDFand 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.
Stage 5 — Remove the TCPDF dependency
Section titled “Stage 5 — Remove the TCPDF dependency”After the strict-mode audit passes, strict mode is off in production,
and the suite is green on re-baselined assertions, remove
tecnickcom/tcpdf:
composer remove tecnickcom/tcpdfRun 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.
Stage 6 — Retire the adapter
Section titled “Stage 6 — Retire the adapter”The adapter is a migration aid, not a permanent layer. After TCPDF is gone and the engine is proven, retire the adapter incrementally:
- In each module, replace
new TCPDF(...)with the modernNextPDF\Core\Documentconstruction. - Replace TCPDF method calls with their modern equivalents (the
getDocument()calls you already added in Stage 4 are the template). - When a module no longer references the adapter, delete its compatibility imports.
- When no module references the adapter, remove
nextpdf/compat-legacyfromcomposer.json.
At that point, you are on the modern PDF 2.0 API with no compatibility layer.
Migration checklist
Section titled “Migration checklist”-
nextpdf/compat-legacyinstalled; 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/tcpdfremoved; suite green. - Adapter retired module by module; dependency removed.
See also
Section titled “See also”- /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