Skip to content
getnextpdf.com

Pro editionstability: Experimental

C2PA preview-capability status

Preview capability (experimental). The C2PA surface provides a stable seam that extracts and carries a Manifest Store, plus a default-off flag that gates draft manifest synthesis. C2paCapabilityStatus reports its preview state as data.

NextPDF Pro exposes a Content Credentials (C2PA) preview surface through C2paCapabilityStatus. It has two layers: a stable seam that is always available, and draft manifest synthesis behind an opt-in flag.

Two layers: stable seam vs. flagged synthesis

Section titled “Two layers: stable seam vs. flagged synthesis”

The surface has two clearly separated layers, and the distinction is the whole point of the boundary:

  • Always available — the stable seam. ManifestStore and the C2paManifestEmbedder interface are a frozen, vendor-neutral seam. They can carry a Manifest Store both ways: embed() writes a caller-supplied Store’s bytes into a PDF, and extract() parses a Store back out (returning null when none is present). This seam does no claim synthesis — it never assembles assertions or claims itself; it moves an opaque ManifestStore the caller already holds.
  • Behind the opt-in flag — claim synthesis. Actually building a Manifest Store (assembling the claim and the ingredient-hash assertion) lives only in ExperimentalC2paEmbedder, gated behind a default-off env flag. This is the draft-shaped, breaking-without-notice part.

So the precise statement is: extraction and byte-carry are always reachable; synthesis of the manifest contents is reachable only when the preview flag is explicitly enabled.

The load-bearing decision splits a stable transport seam from the volatile synthesis path, not one embedder doing everything. The C2PA-PDF profile is still owned by an external working group and is not frozen. Its Claim Signature box (c2cs, a COSE_Sign1 structure) is the part most likely to drift. So synthesis omits that box and lives behind a default-off flag, while extract-and-carry depends on no unfrozen field and stays reachable for everyone. The status object exposes maturity as data, with generallyAvailable and conformanceClaimed hard-coded false, so callers cannot mistake producer intent for a validated result. That separation lets teams adopt the durable seam now and add a signed adapter later.

Design background: Compliance you can hand to an auditor.

C2paCapabilityStatus reports the surface’s status explicitly as data, not prose. Its generallyAvailable and conformanceClaimed properties are hard-coded false and stay false regardless of the flag; enabling the preview flips only previewEnabled. The maturity token is the label preview-draft, and specPin records the pinned draft commit.

use NextPDF\Pro\Compliance\C2pa\C2paCapabilityStatus;
// Reads the env flag live; performs no I/O.
$status = C2paCapabilityStatus::current();
$status->previewEnabled; // bool — true only if the opt-in env flag is set
$status->generallyAvailable; // false (always)
$status->conformanceClaimed; // false (always)
$status->maturity; // 'preview-draft'
$status->specPin; // '4e2afed8' (pinned draft commit, short)
$status->envGate; // 'NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT'
echo $status->summary();
// "C2PA preview-draft surface is present but switched off;
// the C2PA-PDF profile is not yet final."

The C2PA specification is maintained by an external working group, and the profile NextPDF would target is not frozen. When the working-group profile stabilizes, the surface’s status is what would change.

The opt-in is a single process-environment variable, NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT. It is read live and compared strictly against the string "1" — any other value (including 0, true, yes, the empty string, or absence) is treated as OFF. A Pro licence alone does not enable it; the operator must consciously opt in.

// Opt in for the current process BEFORE constructing the embedder.
putenv('NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT=1');
use NextPDF\Pro\Compliance\C2pa\Experimental\ExperimentalC2paEmbedder;
$embedder = new ExperimentalC2paEmbedder();
// Synthesise a draft-shaped Manifest Store binding the host PDF by SHA-256.
$pdfBytes = file_get_contents('input.pdf');
$store = $embedder->buildManifestStore($pdfBytes, 'my-app/1.0.0');
// The result is an opaque ManifestStore; $store->toBytes() is its serialisation.

In a real deployment, set the flag at boot (not via putenv() at runtime), gate it on an explicit human opt-in, and check the capability status before touching the experimental embedder so the draft path is reached only on purpose.

use NextPDF\Pro\Compliance\C2pa\C2paCapabilityStatus;
use NextPDF\Pro\Compliance\C2pa\Experimental\ExperimentalC2paEmbedder;
// Set NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT=1 in the environment at process boot.
$status = C2paCapabilityStatus::current();
if (!$status->previewEnabled) {
// Flag off: do NOT instantiate the experimental embedder — its
// constructor would throw. Fall back to your non-preview path.
return;
}
// Surface the draft pin to the operator. The bytes are transient:
// re-embed once a stable adapter ships. Run `composer c2pa:draft-status`
// in CI to fail the build if the pinned snapshot goes stale.
$pinnedDraft = ExperimentalC2paEmbedder::SPEC_PIN_SHA; // 4e2afed8…
$pinnedDate = ExperimentalC2paEmbedder::SPEC_PIN_DATE; // 2026-04-26
$embedder = new ExperimentalC2paEmbedder();
$store = $embedder->buildManifestStore($pdfBytes, 'my-app/1.0.0');

There is no silent fallback. If ExperimentalC2paEmbedder is constructed while NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT is not exactly "1", the constructor throws a LogicException whose message names the flag, the env var, and the pinned draft SHA/date. A caller cannot accidentally serialise draft-shaped bytes into a production PDF by merely holding a Pro licence.

The stable extract path is fail-closed too, but differently: extract() returns null when no Manifest Store is present (the common case, kept cheap and exception-free), and throws a C2paException subclass — JumbfBombException, JumbfCycleDetectedException, JumbfDepthExceededException, or MalformedJumbfException — when a Store is present but breaches the parser’s adversarial-hardening invariants (depth, cycle, size, count). These are never silently swallowed.

The surface is a stable seam that can parse (extract) and carry (embed) a C2PA Manifest Store, plus a default-off experimental embedder that synthesises draft-shaped manifest contents, and a status object that reports its own preview state. The experimental embedder deliberately omits the Claim Signature (c2cs) box — the COSE_Sign1 part of the spec most likely to drift — so even with the flag on, the output is unsigned, draft-shaped bytes. The working-group profile is unfrozen.

The wire format the experimental embedder emits is pinned to a draft commit of c2pa-org/specifications (SPEC_PIN_SHA = 4e2afed8…, dated 2026-04-26), exposed as a public constant so callers can pin the SHA they expect.

SymbolRole
C2paCapabilityStatusMachine-readable status value object. current() reads the env flag live; generallyAvailable / conformanceClaimed are always false.
ManifestStoreImmutable Manifest Store value object. The stable seam type; carries bytes both ways. No claim-level accessors. Always available.
C2paManifestEmbedderFrozen, vendor-neutral SPI: embed() / extract(). No claim synthesis. extract() returns null on miss. Always available.
ExperimentalC2paEmbedderDefault-off synthesis. buildManifestStore() assembles a draft manifest; constructor throws LogicException unless the env flag is exactly "1".
Feature::PREVIEW_C2PA_DRAFTScoped opt-in flag. ENV_PREVIEW_C2PA_DRAFT = NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT; isEnabled() is strict === '1'.
  • Default-off, strict opt-in. Synthesis is behind NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT, read strictly as === '1'. Anything else is OFF; a Pro licence alone does not enable it.
  • Construct-time fail-closed. new ExperimentalC2paEmbedder() throws LogicException when the flag is off — never a silent no-op.
  • Output is unsigned and transient. Even with the flag on, the Claim Signature box is omitted; treat the bytes as draft-shaped and re-embed once a stable adapter ships.
  • Pin staleness check. Run composer c2pa:draft-status (exit 0 fresh / 1 soft-warn / 2 hard-fail) in CI to detect when the pinned draft snapshot ages out.
  • Reports preview state. C2paCapabilityStatus reports the surface’s preview state as data.

A preview surface is not a security control. Carrying or synthesising a draft Manifest Store is not the same as producing or verifying an attestation — and the experimental embedder omits the Claim Signature box entirely, so its output is unsigned by construction. Do not rely on this preview for provenance assurance in production; it does not assert that a credential is valid or that a file is signed.

The C2PA-PDF profile is maintained by an external working group and is not yet final. For this surface NextPDF reports a preview-capability status, pinned to a draft commit of the specification.

This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.