Intents enable validation, add required actions that are required by the C2PA specification, and help prevent invalid operations when using a Builder. Intents are about the operation (create, edit, update) executed on the source asset.
Without intents, you have to manually construct the correct manifest structure: adding the required actions (c2pa.created or c2pa.opened as the first action per the specification), setting digital source types, managing ingredients, and linking actions to ingredients. Getting any of this wrong produces a non-compliant manifest.
With intents, the caller declares what is being done and Builder handles the rest.
For example, without intents you have to manually wire up actions and make sure ingredients are properly linked to actions. This is especially important for parentOf ingredient relationships with the c2pa.opened action.
#include "c2pa.hpp"
c2pa::Context context;
c2pa::Builder builder(context, R"({
"assertions": [
{
"label": "c2pa.actions",
"data": {
"actions": [
{
"action": "c2pa.created",
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"
}
]
}
}
]
})");
builder.sign(source_path, output_path, signer);But with intents, Builder generates the actions automatically; for example:
#include "c2pa.hpp"
c2pa::Context context;
c2pa::Builder builder(context, R"({})");
builder.set_intent(Create, TrainedAlgorithmicMedia);
builder.sign(source_path, output_path, signer);Both of these code snippets produce the same signed manifest. But with intents, Builder validates the setup and fills in the required structure.
You can set the intent on a Builder instance by:
Don't set intent using the deprecated load_settings() function. For existing code, see Context and Settings - Migrating from deprecated APIs.
Pass the intent through a Context object when creating a Builder. This keeps intent configuration alongside other builder settings such as claim_generator_info and thumbnail.
c2pa::Context context(R"({
"version": 1,
"builder": {
"intent": {"Create": "digitalCapture"},
"claim_generator_info": {"name": "My App", "version": "0.1.0"}
}
})");
c2pa::Builder builder(context, R"({})");
builder.sign(source_path, output_path, signer);You can reuse the same Context across multiple Builder instances, ensuring consistent configuration:
c2pa::Context context(R"({
"version": 1,
"builder": {
"intent": "edit",
"claim_generator_info": {"name": "Batch Editor"}
}
})");
for (const auto& path : image_paths) {
c2pa::Builder builder(context, R"({})");
builder.sign(path, output_path(path), signer);
}Call set_intent directly on a Builder instance for one-off operations or when the intent is determined at runtime. For example:
c2pa::Context context;
c2pa::Builder builder(context, R"({})");
builder.set_intent(Create, TrainedAlgorithmicMedia);
builder.sign(source_path, output_path, signer);When an intent is configured in multiple places, the most specific setting takes precedence.
If set_intent is called on a Builder instance, it takes precedence over all other sources.
flowchart TD
Check{Was set_intent called
on the Builder?}
Check --> |Yes| UseSetIntent["Use set_intent value"]
Check --> |No| CheckCtx{Was a Context with
builder.intent provided?}
CheckCtx --> |Yes| UseCtx["Use Context intent"]
CheckCtx --> |No| CheckGlobal{Was load_settings called
with builder.intent?}
CheckGlobal --> |Yes| UseGlobal["Use global intent
(deprecated)"]
CheckGlobal --> |No| NoIntent["No intent set.
Caller must define actions
manually in manifest JSON."]
The intent operates on the source passed to sign(), not on any ingredient added via add_ingredient.
The following diagram shows what happens at sign time for each intent:
flowchart LR
subgraph CREATE
S1[source stream] --> B1[Builder]
B1 --> O1[signed output]
B1 -. adds .-> A1["c2pa.created action
+ digital source type"]
end
flowchart LR
subgraph EDIT
S2[source stream] --> B2[Builder]
B2 --> O2[signed output]
S2 -. auto-created as .-> P2[parentOf ingredient]
P2 --> B2
B2 -. adds .-> A2["c2pa.opened action
linked to parent"]
end
flowchart LR
subgraph UPDATE
S3[source stream] --> B3[Builder]
B3 --> O3[signed output]
S3 -. auto-created as .-> P3[parentOf ingredient]
P3 --> B3
B3 -. adds .-> A3["c2pa.opened action
linked to parent"]
B3 -. restricts .-> R3[content must not change]
end
For Edit and Update intents, Builder looks at the source stream, and if no parentOf ingredient has been added manually, it automatically creates one from that stream (and adds the needed action). The source stream becomes the parent ingredient. If a parentOf ingredient has already been added manually (via add_ingredient), Builder uses that one instead and does not automatically create one from the source.
The Builder intent controls what the Builder does with the source stream (source asset) at sign time. The add_ingredient method adds other ingredients explicitly. These are separate concerns.
flowchart TD
Intent["Intent
(via Context, set_intent,
or load_settings)"] --> Q{Intent type?}
Q --> |CREATE| CreateFlow["No parent allowed
Source stream is new content"]
Q --> |EDIT or UPDATE| EditFlow{Was a parentOf ingredient
added via add_ingredient?}
EditFlow --> |No| Auto["Builder auto-creates
parentOf from source stream"]
EditFlow --> |Yes| Manual["Builder uses the
manually-added parent"]
Auto --> Opened["Builder adds c2pa.opened
action linked to parent"]
Manual --> Opened
CreateFlow --> Created["Builder adds c2pa.created
action + digital source type"]
AddIngredient["add_ingredient()"] --> IngType{relationship?}
IngType --> |parentOf| ParentIng["Overrides auto-parent
for EDIT/UPDATE"]
IngType --> |componentOf| CompIng["Additional ingredient
not affected by intent"]
ParentIng --> EditFlow
The C2paBuilderIntent and C2paDigitalSourceType enums are available through the c2pa.hpp header file:
#include "c2pa.hpp"Enum values are in the global namespace:
C2paBuilderIntent: Create, Edit, or UpdateC2paDigitalSourceType: Empty, DigitalCapture, TrainedAlgorithmicMedia, and so on...
Use the Builder set_intent method to specify the intent:
void Builder::set_intent(
C2paBuilderIntent intent,
C2paDigitalSourceType digital_source_type = Empty
);Where:
intentis one of the intent types.digital_source_typeis one of theC2paDigitalSourceTypevalues values that describes how the asset was made. Required for theCreateintent. Defaults toEmpty.
Intent types can be any C2paBuilderIntent values:
| Intent | Operation | Parent ingredient | Auto-generated action |
|---|---|---|---|
Create |
Brand-new content | Must NOT have one. | c2pa.created |
Edit |
Modifying existing content | Automatically created from the source stream if not provided | c2pa.opened (linked to parent) |
Update |
Metadata-only changes | Automatically created from the source stream if not provided. | c2pa.opened (linked to parent) |
| Enum value | Description |
|---|---|
Empty |
No source type specified. The default value. |
DigitalCapture |
Captured from a real-world source using a digital device |
TrainedAlgorithmicMedia |
Created by a trained algorithm (e.g., generative AI) |
DigitalCreation |
Created digitally (e.g., drawing software) |
CompositeWithTrainedAlgorithmicMedia |
Composite that includes trained algorithmic media |
AlgorithmicallyEnhanced |
Enhanced by an algorithm |
ScreenCapture |
Captured from a screen |
VirtualRecording |
Recorded from a virtual environment |
Composite |
Composed from multiple sources |
CompositeCapture |
Composite of captured sources |
CompositeSynthetic |
Composite of synthetic sources |
DataDrivenMedia |
Generated from data |
AlgorithmicMedia |
Created by an algorithm |
HumanEdits |
Human-edited content |
ComputationalCapture |
Captured with computational processing |
NegativeFilm |
Scanned from negative film |
PositiveFilm |
Scanned from positive film |
Print |
Scanned from a print |
TrainedAlgorithmicData |
Data created by a trained algorithm |
flowchart TD
Start([Start]) --> HasParent{Does the asset have
prior history?}
HasParent --> |No| IsNew[Brand-new content]
IsNew --> CREATE["Use Create
+ C2paDigitalSourceType"]
HasParent --> |Yes| ContentChanged{Will the content
itself change?}
ContentChanged --> |Yes| EDIT[Use Edit]
ContentChanged --> |No, metadata only| UPDATE[Use Update]
ContentChanged --> |Need full manual control| MANUAL["Skip intents.
Define actions and ingredients
directly in manifest JSON."]
Use the Create intent when the asset has no prior history. A C2paDigitalSourceType is required to describe how the asset was produced. Builder will:
- Add a
c2pa.createdaction with the specified digital source type. - Reject the operation if a
parentOfingredient exists.
Using Context:
c2pa::Context context(R"({
"version": 1,
"builder": {"intent": {"Create": "digitalCreation"}}
})");
c2pa::Builder builder(context, R"({})");
builder.sign(source_path, output_path, signer);Using set_intent:
c2pa::Context context;
c2pa::Builder builder(context, R"({})");
builder.set_intent(Create, DigitalCreation);
builder.sign(source_path, output_path, signer);c2pa::Context context(R"({
"version": 1,
"builder": {"intent": {"Create": "trainedAlgorithmicMedia"}}
})");
c2pa::Builder builder(context, R"({})");
builder.sign(source_path, output_path, signer);A Context and a manifest definition can be combined. The Context handles the intent; the manifest definition provides additional metadata and assertions:
c2pa::Context context(R"({
"version": 1,
"builder": {
"intent": {"Create": "digitalCapture"},
"claim_generator_info": {"name": "an_app", "version": "0.1.0"}
}
})");
std::string manifest_def = R"({
"title": "My New Image",
"assertions": [
{
"label": "cawg.training-mining",
"data": {
"entries": {
"cawg.ai_inference": {"use": "notAllowed"},
"cawg.ai_generative_training": {"use": "notAllowed"}
}
}
}
]
})";
c2pa::Builder builder(context, manifest_def);
builder.sign(source_path, output_path, signer);Use the Edit intent when an existing asset is modified. With this intent, Builder:
- Checks if a
parentOfingredient has already been added. If not, it automatically creates one from the source stream passed tosign(). - Adds a
c2pa.openedaction linked to the parent ingredient.
No digital_source_type parameter is needed.
Using Context:
c2pa::Context context(R"({
"version": 1,
"builder": {"intent": "edit"}
})");
c2pa::Builder builder(context, R"({})");
// The Builder reads "original.jpg" as the parent ingredient,
// then writes the new manifest into "edited.jpg"
builder.sign("original.jpg", "edited.jpg", signer);Using set_intent:
c2pa::Context context;
c2pa::Builder builder(context, R"({})");
builder.set_intent(Edit);
builder.sign("original.jpg", "edited.jpg", signer);The resulting manifest contains one ingredient with relationship: "parentOf" pointing to original.jpg and a c2pa.opened action referencing that ingredient. If the source file already has a C2PA manifest, the ingredient preserves the full provenance chain.
To control the parent ingredient's metadata (for example, to set a title or use a different source), add it explicitly:
c2pa::Context context(R"({
"version": 1,
"builder": {"intent": "edit"}
})");
c2pa::Builder builder(context, R"({})");
std::ifstream original("original.jpg", std::ios::binary);
builder.add_ingredient(
R"({"title": "Original Photo", "relationship": "parentOf"})",
"image/jpeg",
original
);
builder.sign("canvas.jpg", "edited.jpg", signer);A parent ingredient can be combined with component or input ingredients. The intent creates the c2pa.opened action for the parent; additional actions can reference components (componentOf) or inputs (inputTo):
c2pa::Context context(R"({
"version": 1,
"builder": {"intent": "edit"}
})");
std::string manifest_def = R"({
"assertions": [
{
"label": "c2pa.actions.v2",
"data": {
"actions": [
{
"action": "c2pa.placed",
"parameters": {"ingredientIds": ["overlay_label"]}
}
]
}
}
]
})";
c2pa::Builder builder(context, manifest_def);
// The Builder auto-creates a parent from the source stream
// and generates a c2pa.opened action for it.
// Add a component ingredient manually.
std::ifstream overlay("overlay.png", std::ios::binary);
builder.add_ingredient(
R"({
"title": "overlay.png",
"relationship": "componentOf",
"label": "overlay_label"
})",
"image/png",
overlay
);
builder.sign("original.jpg", "composite.jpg", signer);Use the Update intent for metadata-only changes where the asset content itself is not modified. This is a restricted form of the Edit intent that:
- Allows exactly one ingredient (the parent).
- Does not allow changes to the parent's hashed content.
- Produces a more compact manifest than
Edit.
As with Edit intent, Builder automatically creates a parent ingredient from the source stream if one is not provided.
Using Context:
c2pa::Context context(R"({
"version": 1,
"builder": {"intent": "update"}
})");
c2pa::Builder builder(context, R"({})");
builder.sign("signed_asset.jpg", "updated_asset.jpg", signer);Using set_intent:
c2pa::Context context;
c2pa::Builder builder(context, R"({})");
builder.set_intent(Update);
builder.sign("signed_asset.jpg", "updated_asset.jpg", signer);