Skip to content

Migrate a TCPDF 6.x codebase to NextPDF

The nextpdf/compat-legacy package exposes the TCPDF 6.x public method names, parameter order, and defaults on top of the NextPDF core engine through the NextPDF\Compat\Tcpdf\TCPDF adapter. Migrate in this order: move to the engine with the smallest change, prove what already works, turn on strict mode to find what does not, fix the call sites one at a time, then retire the adapter and use the modern API. The adapter supports the migration; it is not the destination.

Prerequisites, up front:

  • NextPDF core and nextpdf/compat-legacy are installed.
  • You have an existing TCPDF 6.x codebase with a test suite. The suite is the safety net for each stage below.

This is a how-to. For the per-method behavior of one TCPDF call, read the method-coverage page. For the full file-by-file strategy with code, read the upstream migration page. Both links are under See also.

Install the adapter alongside core. Do not remove the real TCPDF library yet — keeping both lets you compare output as you migrate.

Terminal window
composer require nextpdf/compat-legacy

Before you change any code, confirm that the engine link resolves (nextpdf/core ^3.0) and the suite still runs.

The adapter is a compatibility layer, not a fork of TCPDF and not a byte-identical clone. Of roughly 120 surveyed TCPDF 6.x public methods, about 94 map directly to a NextPDF\Core\Document operation and behave compatibly for the documented parameters. A defined minority either accept legacy parameters that the engine does not honor (silent-ignore), or produce no output (unimplemented or not-applicable). The authoritative, test-verified coverage matrix is in the package repository at docs/TCPDF_COVERAGE.md. When this guide and that matrix disagree, the matrix wins.

Two facts shape the whole migration:

  • Output bytes differ. The engine is an independent PDF 2.0 implementation, so the rendered bytes differ from TCPDF output even when the visible result looks the same. Tests that assert on exact PDF bytes need re-baselining onto rendered content or structural properties.
  • Strict mode is your audit tool. With strict mode off (the default), methods that cannot reproduce TCPDF behavior degrade silently. With strict mode on, those calls throw TcpdfNotImplementedException, naming the exact ignored parameters and a migration hint. Run strict mode in a dedicated audit pass, never in production.

The adapter also exposes the wrapped engine document through getDocument(), which returns the NextPDF\Core\Document. Use that as the exit path: migrate call sites to the modern API one at a time until you can remove the adapter.

ConcernSurface
Constructnew NextPDF\Compat\Tcpdf\TCPDF('P', 'mm', 'A4')
Opt-in global aliasesNextPDF\Compat\Tcpdf\LegacyBootstrap::enableAliases()
Enable the auditTCPDF::setStrictMode(true)
Audit exceptionNextPDF\Compat\Tcpdf\Exception\TcpdfNotImplementedException
Escape hatch to modern APITCPDF::getDocument(): NextPDF\Core\Document
OutputTCPDF::Output(string $name, string $dest)S, F, E, I, D

LegacyBootstrap::enableAliases() is idempotent. It registers \TCPDF, \TCPDF_STATIC, \TCPDF_FONTS, \TCPDF_COLORS, and \TCPDF_IMAGES only when those classes do not already exist. The method-coverage and quickstart pages linked under See also cover the full per-method behavior and output destinations.

Change the import, keep the TCPDF-style calls, and produce a PDF.

quickstart-first.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF('P', 'mm', 'A4');
$pdf->SetCreator('Quickstart');
$pdf->SetTitle('First Document');
$pdf->SetFont('helvetica', '', 12);
$pdf->AddPage();
$pdf->Cell(0, 10, 'Hello from the NextPDF engine', 1, 1, 'C');
$pdf->Output(__DIR__ . '/quickstart.pdf', 'F');

Output($name, 'F') writes the file and returns an empty string. Unlike legacy TCPDF, the adapter’s Output() does not echo into the active output buffer, so you can safely call it inside a queue worker or an HTTP handler that controls its own response.

When you cannot change call sites that construct new \TCPDF(...) against the global namespace, enable the opt-in aliases once at boot.

quickstart-alias.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\LegacyBootstrap;
LegacyBootstrap::enableAliases();
// Legacy code now resolves \TCPDF to the adapter:
$pdf = new \TCPDF('P', 'mm', 'A4');
$pdf->AddPage();
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 10, 'Legacy call site, modern engine');
$pdf->Output(__DIR__ . '/aliased.pdf', 'F');

Do not enable aliases while the real TCPDF library is still autoloadable. The alias is skipped when a \TCPDF class already exists, so you may keep using legacy TCPDF without realizing it. During the migration, prefer per-file imports.

The migration-safe step is the strict-mode audit. Run a representative production path, or the suite, with strict mode on, and collect every TcpdfNotImplementedException. Each exception is a work item: it names the method, the ignored parameters, and a hint.

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 $exception) {
// Each message names the method, the ignored parameters, and a hint.
fwrite(STDERR, 'MIGRATION GAP: ' . $exception->getMessage() . "\n");
}

For each gap, pick the cheapest correct fix: drop a parameter you never relied on, or express the intent through the modern API via getDocument(). The escape hatch handles anything the TCPDF surface cannot express.

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 for the parts that already work:
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 10, 'Header line', 0, 1);
// Modern path for what the TCPDF surface cannot express here —
// for example a clickable image (the legacy Image() link parameter
// is one of the silently ignored parameters):
$document = $pdf->getDocument();
$document->image('logo.png', 10, 30, 40, 0);
$document->link(10, 30, 40, 20, 'https://example.com');

Run strict mode as a dedicated continuous integration (CI) job, then turn it off and deploy the audited code path. Keep a periodic strict-mode CI job to catch regressions as you refactor.

  • MultiCell() returns 1, Write() returns 0. These are compatibility placeholders, not computed values. Adjust any code that branches on those return values.
  • Error() throws instead of calling die(). The adapter raises RuntimeException. Code that relies on process termination must catch the exception.
  • Silent-ignore parameters. Methods such as Image(), writeHTML(), SetProtection(), and Bookmark() accept legacy parameters that are ignored. Use strict mode to find them. For a clickable image, draw the image, then add Document::link() over the same rectangle.
  • Unimplemented methods. setSignature(), addEmptySignatureAppearance(), and endPage() are no-ops that throw in strict mode; Open() is a safe no-op that never throws. Remove endPage() and Open(). Signing requires a commercial NextPDF edition through the modern signature API.
  • PDF version is fixed. setPDFVersion() cannot down-target an older PDF version; output is always PDF 2.0. setUserRights() is deprecated in PDF 2.0 and is ignored with a notice.
  • Alias conflict. If anything still resolves to the real TCPDF class after you remove tecnickcom/tcpdf, the alias caveat applies — import the adapter explicitly at those call sites.

The adapter delegates to the engine; document build cost scales with content, not with the adapter layer. Because the adapter’s Output() does not write to the output buffer, it is safe inside a queue worker — move heavy TCPDF-style generation off the request thread the same way you would move any NextPDF generation. Re-baselining byte-level tests onto rendered content is a one-time cost, and it gives you tests that survive future engine upgrades.

  • Encryption. SetProtection() ignores the legacy mode and pubkeys parameters; the engine uses AES-256 for the standard handler. For certificate-based encryption, use the modern public-key encryption entry point exposed on the adapter, not the legacy parameters.
  • Signing is gated. Baseline signature support is a commercial-edition capability reached through the modern signature API with a certificate value object; the legacy setSignature() is a no-op. This guide does not claim about long-term-validation or timestamped signature profiles for any edition.
  • Fail explicitly during the audit. Strict mode makes silent parameter loss visible, so you know when the adapter did not honor the caller’s intent. Treat collected exceptions as the migration work list, not as production behavior.
  • Never write an empty catch block. The audit example catches TcpdfNotImplementedException and writes a defined work-item line.

The full encryption and signature posture during migration is in the compat-legacy security-and-operations page.

This guide makes no normative standards claim of its own. The adapter writes PDF 2.0 (ISO 32000-2) output and cannot down-target an older version. That behavior and its clause are pinned on the upstream method-coverage page, which also records the OWASP fail-explicitly principle behind strict mode and the ISO/IEC 25023 functional-completeness framing of the coverage audit. This cookbook page restates the usage and defers those citations to the upstream page.