Skip to content

NextPDF Laravel boot and auto-discovery

Laravel auto-discovers NextPdfServiceProvider from the package composer.json. The provider registers deferred container bindings and, in console context, publishes the configuration file. This page explains how discovery works and how long each binding lives.

Terminal window
composer require nextpdf/laravel
php artisan vendor:publish --tag=nextpdf-config

The package declares its provider and facade alias in the extra.laravel block of its own composer.json:

resource: composer.json (extra.laravel)
{
"extra": {
"laravel": {
"providers": [
"NextPDF\\Laravel\\NextPdfServiceProvider"
],
"aliases": {
"Pdf": "NextPDF\\Laravel\\Facades\\Pdf"
}
}
}
}

When you run composer require, Laravel reads this block, then registers the provider and alias. You do not need to edit config/app.php or bootstrap/providers.php manually. The extra.laravel.providers array auto-registers service providers, and extra.laravel.aliases auto-registers facade aliases (Laravel 12 package development guide, https://laravel.com/docs/12.x/packages, retrieved 2026-05-18).

NextPdfServiceProvider implements DeferrableProvider and the standard register() / boot() lifecycle.

  1. register() merges the package config under the nextpdf key. It then binds the container entries: font registry, image registry, document factory, PHP Standards Recommendation 18 (PSR-18) Hypertext Transfer Protocol (HTTP) client, timestamp client, signer, document, and e-invoice contracts. Every binding is a closure, so nothing heavy is constructed here.
  2. boot() checks that the mbstring and zlib PHP extensions are loaded. It registers the publishable config under the nextpdf-config tag only when runningInConsole() is true.

Because the provider is deferred, register() runs only when you resolve one of the entries returned by provides(). Resolving an unrelated container key does not boot NextPDF.

PHP Standards Recommendation 11 (PSR-11) permits two successive get() calls with the same identifier to return different values depending on the binding strategy (PSR-11 §1.1.2). The provider relies on this behavior by design:

Binding keyLifetimeNotes
FontRegistryInterface (+ FontRegistry alias)singleton, locked after warmupWarmed from preload_fonts; locked so no request can mutate it
ImageRegistrysingletonBounded least recently used (LRU) cache sized by image_cache_mb; not locked
DocumentFactoryInterface (+ DocumentFactory alias)singletonStateless; shares the two registries
Psr\Http\Client\ClientInterfacesingletonRequest-forgery-aware client wrapping a curl client; built from tsa.*
TsaClientscopednull when tsa.url is empty
SignerInterfacefactorynull when signing disabled or certificate empty
PdfDocumentInterface (+ nextpdf alias)factoryFresh NextPDF\Core\Document per resolve, with default metadata applied
EmbedderInterface, ValidatorInterface, ProfileInterface, SchematronRunnerInterfacefactoryResolve to Premium concretes; error on first resolve without nextpdf/premium

The document binding applies defaults.creator, defaults.language, and, when non-empty, defaults.author to each fresh document. When pdfa is non-null, it enables PDF/A (Premium). When the artisan section is present and a Chrome browser-factory class exists, it applies the Chrome renderer config.

has() on the container takes a single string identifier (PSR-11 §1.1.2). The e-invoice contracts are bound, so has() returns true for them even when Premium is absent. The missing concrete only errors at construction.

Add the package to the application’s dont-discover array, then register the provider manually:

resource: application composer.json
{
"extra": {
"laravel": {
"dont-discover": ["nextpdf/laravel"]
}
}
}
resource: bootstrap/providers.php
<?php
declare(strict_types=1);
return [
App\Providers\AppServiceProvider::class,
NextPDF\Laravel\NextPdfServiceProvider::class,
];

Each key resolves in this order: environment variable → published config/nextpdf.php value → package default merged at register(). Most keys accept either a NEXTPDF_* name or a legacy TCPDF_* environment name. Prefer NEXTPDF_*.

Terminal window
php artisan package:discover --ansi

A line that lists nextpdf/laravel confirms discovery. Because the provider is deferred, the bindings themselves do not appear until the first resolve. The discovery line is the correct success signal.

  • The config publish registers only in console context, so a web-only request never triggers it. Run vendor:publish from the command-line interface (CLI).
  • Alongside the registry, factory, HTTP client, signer, timestamp, and document keys, provides() includes the four e-invoice contract keys.
  • A fresh install can look inert until the first relevant resolve. This is the deferred-provider design, not a fault.

register() is O(1) because it creates closures only. The font registry warmup is O(f) in preloaded fonts and runs once per worker process. Deferring the provider keeps NextPDF construction cost off the framework boot path until a binding is actually used.

The deferred design narrows the attack surface at boot. The locked font registry prevents one request from changing font state for another in long-lived workers. For full threat coverage, see /integrations/laravel/security-and-operations/.

ClaimSourceClausereference_id
Successive resolutions may differ by binding strategyPSR-11 Container§1.1.2
has() takes one string identifierPSR-11 Container§1.1.2

The official Laravel 12 package documentation corroborates the Laravel discovery key names (https://laravel.com/docs/12.x/packages, retrieved 2026-05-18).

Premium concretes resolve through the same deferred binding keys. This optional Enterprise capability needs no code change in the Core package documented here. See https://nextpdf.dev/get-license/?intent=laravel-signing.

  • /integrations/laravel/install/ — install and publish
  • /integrations/laravel/overview/ — package architecture
  • /integrations/laravel/integration/ — end-to-end wiring how-to
  • /integrations/laravel/configuration/ — every config key