Skip to content

Extension authoring: the public SPI overview

NextPDF exposes a small, deliberate set of public contracts in the NextPDF\Contracts and NextPDF\Event namespaces. Implement these contracts to add fonts, intercept text, observe the document lifecycle, or provide your own signing back end without forking the engine.

Terminal window
composer require nextpdf/core:^3

NextPDF separates its public service provider interface (SPI) from its internal code. The SPI is the set of types you can implement or observe. Everything else is private and may change without notice.

The public SPI has three forms:

  • Registry contracts. Process-lifetime services that you populate before creating documents; FontRegistryInterface and ImageRegistryInterface are the main examples. Register the assets, and the engine reads them.
  • Strategy contracts. Single-job hooks the engine calls during a render. TextPreprocessorInterface handles layout-time text interception, and HtmlSecurityPolicyInterface gates Hypertext Markup Language (HTML) features. You provide the behavior, and the engine invokes it.
  • Signing contracts. Cryptographic back ends. SignerInterface, HsmSignerInterface, and DeferredSignerInterface let you provide key custody and signature production. The engine builds the Cryptographic Message Syntax (CMS) structure, and your code holds the key.

A separate event system in NextPDF\Event, compatible with PHP Standard Recommendation 14 (PSR-14), handles observation. Lifecycle events let you react to document creation, new pages, font loading, signing, and write-out. They do not change engine behavior.

Each contract carries an @stability tag in its source PHPDoc: stable, experimental, or deprecated. The tag and the per-contract backward-compatibility promise tell you how much change to expect. See SPI stability rules for the full policy.

CapabilityPublic contractStability
Font registration and lookupNextPDF\Contracts\FontRegistryInterfacestable (since 1.7.0)
Image caching and decodeNextPDF\Contracts\ImageRegistryInterfacestable (since 2.0.0)
Layout-time text interceptionNextPDF\Contracts\TextPreprocessorInterfacestable (since 1.9.0)
HTML feature gatingNextPDF\Contracts\HtmlSecurityPolicyInterfacestable (since 3.1.0)
Document factory wiringNextPDF\Contracts\DocumentFactoryInterfacestable (since 1.7.0)
Synchronous signingNextPDF\Contracts\SignerInterfacestable (since 1.0.0)
Hardware-backed signingNextPDF\Contracts\HsmSignerInterfacestable (since 1.0.0)
Deferred and batch signingNextPDF\Contracts\DeferredSignerInterfaceexperimental (since 3.0.0)
RFC 3161 timestampingNextPDF\Contracts\TimestampProviderInterfaceexperimental (since 3.0.0)
Lifecycle observationNextPDF\Event\* (PSR-14 compatible)stable dispatcher; experimental payloads

The following types are internal. Do not import, subclass, or depend on them:

  • Any class outside the NextPDF\Contracts and NextPDF\Event namespaces, unless its PHPDoc carries an @stability tag.
  • Concrete engine code, including the HTML parser, the writer, the layout pipeline, and the font subsetter.
  • The NextPDF Pro and NextPDF Enterprise packages. Their internal classes are not part of the open-source surface. When a paid edition ships an SPI implementation, consume the public contract, not its internal type.

The generated contracts map is authoritative and is rebuilt from source for every release. Treat the @stability PHPDoc tag in each interface file as the single source of truth. Use the table above as a reading aid.

Register a font, then watch for it to load. Both steps use only public types.

<?php
declare(strict_types=1);
use NextPDF\Contracts\FontRegistryInterface;
use NextPDF\Event\Content\FontLoadedEvent;
use NextPDF\Event\EventDispatcher;
use NextPDF\Event\ListenerProvider;
/** @var FontRegistryInterface $fonts */
$fonts->register('/srv/fonts/Inter-Regular.ttf', 'Inter');
$listeners = new ListenerProvider();
$listeners->addListener(
FontLoadedEvent::class,
static function (FontLoadedEvent $event): void {
\error_log("Font loaded: {$event->family} {$event->style}");
},
);
$dispatcher = new EventDispatcher($listeners);

In a long-running worker, build the registries once at boot, lock them, and inject a shared dispatcher through the document factory.

<?php
declare(strict_types=1);
use NextPDF\Contracts\DocumentFactoryInterface;
use NextPDF\Contracts\FontRegistryInterface;
use NextPDF\Event\EventDispatcher;
use NextPDF\Event\ListenerProvider;
use Psr\Log\LoggerInterface;
final class DocumentBootstrap
{
public function __construct(
private readonly FontRegistryInterface $fonts,
private readonly DocumentFactoryInterface $factory,
private readonly LoggerInterface $logger,
) {}
public function warmup(): EventDispatcher
{
$this->fonts->warmup([
'/srv/fonts/Inter-Regular.ttf',
'/srv/fonts/Inter-Bold.ttf',
]);
$this->fonts->lock();
$listeners = new ListenerProvider();
$listeners->addListener(
\NextPDF\Event\Security\SignatureAppliedEvent::class,
fn (object $event): mixed => $this->logger->info('Signature applied'),
);
return new EventDispatcher($listeners);
}
}
  • Registry lock. After FontRegistryInterface::lock(), change methods throw LogicException. Lock only after warmup finishes.
  • Stability mismatch. An experimental contract can change in a minor release. Check the stated stability before you depend on the contract in production.
  • Namespace discipline. A type outside NextPDF\Contracts or NextPDF\Event with no @stability tag is internal, even if it is technically public.

The SPI has zero cost when unused. If no listener is bound for an event class, the event dispatcher returns immediately after a single hasListeners() check. Registries hold pure PHP data, and they support boot-time warmup to spread out first-request latency.

The signing contracts are the security-sensitive surface. HsmSignerInterface requires that the private key never leaves the hardware boundary. Your implementation must honor that requirement. See the key management system (KMS) provider contract for the third-party signing back-end contract and its threat model.

No normative claims are made on this overview page. Per-contract conformance, including PDF Advanced Electronic Signatures (PAdES) and key management, is documented on the relevant SPI pages.

NextPDF Pro and NextPDF Enterprise provide production implementations of several signing and validation contracts, including key-management-system-backed signing. You depend on the public contract while the edition supplies the implementation, so your code stays portable across editions.

The glossary defines SPI, extension point, stability tag, and backward-compatibility promise; see the published glossary for each canonical definition.