Extension authoring: the public SPI overview
At a glance
Section titled “At a glance”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.
Install
Section titled “Install”composer require nextpdf/core:^3Conceptual overview
Section titled “Conceptual overview”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;
FontRegistryInterfaceandImageRegistryInterfaceare the main examples. Register the assets, and the engine reads them. - Strategy contracts. Single-job hooks the engine calls during a render.
TextPreprocessorInterfacehandles layout-time text interception, andHtmlSecurityPolicyInterfacegates Hypertext Markup Language (HTML) features. You provide the behavior, and the engine invokes it. - Signing contracts. Cryptographic back ends.
SignerInterface,HsmSignerInterface, andDeferredSignerInterfacelet 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.
What is extensible
Section titled “What is extensible”| Capability | Public contract | Stability |
|---|---|---|
| Font registration and lookup | NextPDF\Contracts\FontRegistryInterface | stable (since 1.7.0) |
| Image caching and decode | NextPDF\Contracts\ImageRegistryInterface | stable (since 2.0.0) |
| Layout-time text interception | NextPDF\Contracts\TextPreprocessorInterface | stable (since 1.9.0) |
| HTML feature gating | NextPDF\Contracts\HtmlSecurityPolicyInterface | stable (since 3.1.0) |
| Document factory wiring | NextPDF\Contracts\DocumentFactoryInterface | stable (since 1.7.0) |
| Synchronous signing | NextPDF\Contracts\SignerInterface | stable (since 1.0.0) |
| Hardware-backed signing | NextPDF\Contracts\HsmSignerInterface | stable (since 1.0.0) |
| Deferred and batch signing | NextPDF\Contracts\DeferredSignerInterface | experimental (since 3.0.0) |
| RFC 3161 timestamping | NextPDF\Contracts\TimestampProviderInterface | experimental (since 3.0.0) |
| Lifecycle observation | NextPDF\Event\* (PSR-14 compatible) | stable dispatcher; experimental payloads |
What is NOT public
Section titled “What is NOT public”The following types are internal. Do not import, subclass, or depend on them:
- Any class outside the
NextPDF\ContractsandNextPDF\Eventnamespaces, unless its PHPDoc carries an@stabilitytag. - 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.
API surface
Section titled “API surface”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.
Code sample — Quick start
Section titled “Code sample — Quick start”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);Code sample — Production
Section titled “Code sample — Production”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); }}Edge cases & gotchas
Section titled “Edge cases & gotchas”- Registry lock. After
FontRegistryInterface::lock(), change methods throwLogicException. Lock only after warmup finishes. - Stability mismatch. An
experimentalcontract can change in a minor release. Check the stated stability before you depend on the contract in production. - Namespace discipline. A type outside
NextPDF\ContractsorNextPDF\Eventwith no@stabilitytag is internal, even if it is technicallypublic.
Performance
Section titled “Performance”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.
Security notes
Section titled “Security notes”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.
Conformance
Section titled “Conformance”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.
Commercial context
Section titled “Commercial context”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.
See also
Section titled “See also”- SPI stability rules
- Custom fonts
- Custom layout and text interception
- Action triggers and event listeners
- KMS provider contract
Related contracts and modules
Section titled “Related contracts and modules”- Contracts module reference — the full generated contracts map.
- Signing contracts reference —
SignerInterface,HsmSignerInterface, andDeferredSignerInterface. - Security-policy contract reference —
HtmlSecurityPolicyInterfacedetail. - Event module reference — the lifecycle observation surface.
- SPI stability rules — the change policy behind every
@stabilitytag. - KMS provider contract — the signing back-end contract and threat model.
The glossary defines SPI, extension point, stability tag, and backward-compatibility promise; see the published glossary for each canonical definition.