Pro editionstability: Experimental
C2PA preview-capability status
At a glance
Section titled “At a glance”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.
C2paCapabilityStatusreports 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.
ManifestStoreand theC2paManifestEmbedderinterface 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, andextract()parses a Store back out (returningnullwhen none is present). This seam does no claim synthesis — it never assembles assertions or claims itself; it moves an opaqueManifestStorethe 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.
Why it works this way
Section titled “Why it works this way”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.
Preview-capability status
Section titled “Preview-capability status”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.
Enabling the preview
Section titled “Enabling the preview”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.
Quick start
Section titled “Quick start”// 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.Production-style usage
Section titled “Production-style usage”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');Fail-closed when the flag is off
Section titled “Fail-closed when the flag is off”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.
What this surface is
Section titled “What this surface is”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.
API surface
Section titled “API surface”| Symbol | Role |
|---|---|
C2paCapabilityStatus | Machine-readable status value object. current() reads the env flag live; generallyAvailable / conformanceClaimed are always false. |
ManifestStore | Immutable Manifest Store value object. The stable seam type; carries bytes both ways. No claim-level accessors. Always available. |
C2paManifestEmbedder | Frozen, vendor-neutral SPI: embed() / extract(). No claim synthesis. extract() returns null on miss. Always available. |
ExperimentalC2paEmbedder | Default-off synthesis. buildManifestStore() assembles a draft manifest; constructor throws LogicException unless the env flag is exactly "1". |
Feature::PREVIEW_C2PA_DRAFT | Scoped opt-in flag. ENV_PREVIEW_C2PA_DRAFT = NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT; isEnabled() is strict === '1'. |
Edge cases & gotchas
Section titled “Edge cases & gotchas”- 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()throwsLogicExceptionwhen 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.
C2paCapabilityStatusreports the surface’s preview state as data.
Security notes
Section titled “Security notes”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.
Conformance
Section titled “Conformance”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.
Publication boundary
Section titled “Publication boundary”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.
See also
Section titled “See also”- C2PA preview — Deep Reference — the full API reference for this surface.
- Pro security
- Core signing
- Conformance landing
- Post-quantum cryptography preview-capability status