Use a Reader when the goal is only to inspect or extract data without creating a new manifest.
- Validating whether an asset has C2PA credentials
- Displaying provenance information to a user
- Extracting thumbnails for display
- Checking trust status and validation results
- Inspecting ingredient chains
c2pa::Reader reader(context, "image.jpg");
auto json = reader.json(); // inspect the manifest
reader.get_resource(thumb_id, stream); // extract a thumbnailThe Reader is read-only. It never modifies the source asset.
Use a Builder when creating a manifest from scratch on an asset that has no existing C2PA data, or when intentionally starting with a clean slate.
- Signing a brand-new asset for the first time
- Adding C2PA credentials to an unsigned asset
- Creating a manifest with all content defined from scratch
c2pa::Builder builder(context, manifest_json);
builder.add_ingredient(ingredient_json, source_path); // add source material
builder.sign(source_path, output_path, signer);Every call to the Builder constructor or Builder::from_archive() creates a new Builder. There is no way to modify an existing signed manifest directly.
Use both when filtering content from an existing manifest into a new one. The Reader extracts data, application code filters it, and a new Builder receives only the selected parts.
- Filtering specific ingredients from a manifest
- Dropping specific assertions while keeping others
- Filtering actions (keeping some, removing others)
- Merging ingredients from multiple signed assets or archives
- Extracting content from an ingredients catalog
- Re-signing with different settings while keeping some original content
// Read existing (does not modify the asset)
c2pa::Reader reader(context, "signed.jpg");
auto parsed = json::parse(reader.json());
// Filter what to keep
auto kept = filter(parsed); // application-specific filtering logic
// Create a new Builder with only the filtered content
c2pa::Builder builder(context, kept.dump());
// ... transfer resources ...
builder.sign(source, output, signer);flowchart TD
Q1{Need to read an existing manifest?}
Q1 -->|No| USE_B["Use Builder alone (new manifest from scratch)"]
Q1 -->|Yes| Q2{Need to create a new/modified manifest?}
Q2 -->|No| USE_R["Use Reader alone (inspect/extract only)"]
Q2 -->|Yes| USE_BR[Use both Reader + Builder]
USE_BR --> Q3{What to keep from the existing manifest?}
Q3 -->|Everything| P1["add_ingredient() with original asset or archive path"]
Q3 -->|Some parts| P2["1. Read: reader.json() + get_resource() 2. Filter: pick ingredients & actions to keep 3. Build: new Builder with filtered JSON 4. Transfer: .add_resource for kept binaries 5. Sign: builder.sign()"]
Q3 -->|Nothing| P3["New Builder alone (fresh manifest, no prior provenance)"]
There are two ways: using add_ingredient() and injecting ingredient JSON via with_definition(). The table below summarizes these options.
| Approach | What it does | When to use |
|---|---|---|
add_ingredient(json, path) |
Reads the source (a signed asset, an unsigned file, or a .c2pa archive), extracts its manifest store automatically, generates a thumbnail |
Adding an ingredient where the library should handle extraction. Works with ingredient catalog archives too: pass the archive path and the library extracts the manifest data |
Inject via with_definition() + add_resource() |
Accepts the ingredient JSON and all binary resources provided manually | Reconstructing from a reader or merging from multiple readers, where the data has already been extracted |
There are two distinct archive concepts:
-
Builder archives (working store archives) (
to_archive/from_archive/with_archive) serialize the fullBuilderstate (manifest definition, resources, ingredients) so it can be resumed or signed later, possibly on a different machine or in a different process. The archive is not yet signed. Use builder archives when:- Signing must happen on a different machine (e.g., an HSM server)
- Checkpointing work-in-progress before signing
- Transmitting a
Builderstate across a network boundary
-
Ingredient archives contain the manifest store data (
.c2pabinary) from ingredients that were added to aBuilder. When a signed asset is added as an ingredient viaadd_ingredient(), the library extracts and stores its manifest store asmanifest_datawithin the ingredient record. When theBuilderis then serialized viato_archive(), these ingredient manifest stores are included. Use ingredient archives when:- Building an ingredients catalog for pick-and-choose workflows
- Preserving provenance history from source assets
- Transferring ingredient data between
ReaderandBuilder
See also Working stores.
Key consideration for builder archives: from_archive() creates a new Builder with default context settings. If specific settings are needed (e.g., thumbnails disabled), use with_archive() on a Builder that already has the desired context:
// Preserves the caller's context settings
c2pa::Builder builder(my_context);
builder.with_archive(archive_stream);
builder.sign(source, output, signer);No. C2PA manifests are cryptographically signed. Any modification invalidates the signature. The only way to "modify" a manifest is to create a new Builder with the desired changes and sign it. This is by design: it ensures the integrity of the provenance chain.
When creating a new manifest, the chain is preserved once the original asset is added as an ingredient. The ingredient carries the original asset's manifest data, so validators can trace the full history. If the original is not added as an ingredient, the provenance chain is broken: the new manifest has no link to the original. This might be intentional (starting fresh) or a mistake (losing provenance).