Full working example available on GitHub: edit-xmp-in-psd-and-ai-files-using-groupdocs-metadata-dotnet
The Production Challenge: Metadata That Lives Where Nobody Looks
A brand studio delivers a campaign: layered PSD masters, AI source files, hundreds of assets pushed into the client’s DAM. Three weeks later, licensing asks who owns the hero image. The answer exists, but it lives in an email thread, because the file itself carries an empty dc:rights field. XMP editing is a GroupDocs.Metadata capability for .NET that fixes this class of problem at the pipeline level, reading and writing the xmp metadata packet inside PSD and AI files without any Adobe software in the loop.
The scale problem arrives quietly. One designer can fill metadata panels by hand, carefully, for a while. A production team moving thousands of assets per quarter cannot, and every handoff between agency, studio, and client multiplies the files whose psd metadata nobody verified. Search stops finding assets that exist. Rights questions become archaeology, and archaeology has no service-level agreement.
What these teams actually need reads like an API spec: snapshot everything a file carries at ingestion, check specific fields at licensing gates, and write ownership and keywords at export, with identical code for both Adobe formats.
Reality check: an asset with an empty dc:rights field is not unlicensed, but nobody downstream can prove otherwise without finding a human who remembers.
Why the Usual Fixes Fall Short
Teams typically try three approaches before automating properly:
- Manual panel editing in Photoshop or Bridge: works per file, cannot be audited, and requires an Adobe seat for what is fundamentally a data-entry task.
- Sidecar spreadsheets: the metadata exists but detaches from the asset the moment a file is copied, renamed, or re-delivered.
- In-house parsing: PSD resource blocks and AI containers are nontrivial formats, and a homegrown parser becomes a maintenance liability the first time Adobe revises anything.
GroupDocs.Metadata covers the gap with one API: cast the root package to IXmp and the packet is readable and writable for both formats, alongside the 170+ others the documentation lists.
The Solution: XMP Operations Inside the Pipeline
GroupDocs.Metadata for .NET slots into the asset pipeline at three points. At ingestion it snapshots the full packet into a dictionary your database indexes. At the licensing gate it reads Dublin Core, the scheme where dc:rights and dc:creator live. At export it writes ownership and dc:subject keywords, creating missing schemes on files that arrive without XMP at all. I have sat in one too many launch retros where the root cause was an asset shipped without rights data; the gate exists because retros are more expensive than reads.
To follow the implementation, you’ll need:
- .NET SDK 8.0 or later
- GroupDocs.Metadata 26.6.0 (get a temporary license)
- A PSD or AI file to experiment with
dotnet add package GroupDocs.Metadata --version 26.6.0
The companion repository seeds one sample of each format and asserts every step below.
Implementing the Workflow Step by Step
Step 1 — Snapshot Everything at Ingestion
One pass captures the packet, the named schemes, and anything vendor tools tucked away. Store the dictionary beside the asset record and later questions become database lookups.
// Full XMP snapshot: packet, schemes, then a deep sweep
var result = new Dictionary<string, string>();
using (var metadata = new Metadata(adobeFilePath))
{
var root = metadata.GetRootPackage() as IXmp;
if (root?.XmpPackage == null) return result;
foreach (var property in root.XmpPackage)
{
result[property.Name] = property.InterpretedValue?.ToString()
?? property.Value?.ToString() ?? string.Empty;
}
// CollectScheme(...) repeats this loop for DublinCore, XmpBasic,
// Photoshop, CameraRaw, PagedText, XmpDynamicMedia, XmpMediaManagement
foreach (var p in metadata.FindProperties(p => p.Name != null))
{
if (!result.ContainsKey(p.Name))
{
result[p.Name] = p.InterpretedValue?.ToString()
?? p.Value?.ToString() ?? string.Empty;
}
}
}
return result;
InterpretedValue comes first throughout, so dates and enumerations land human-readable. The trailing FindProperties sweep is the completeness guarantee for adobe illustrator metadata written by plugins the named schemes never heard of.
Two operational notes from running this at ingestion scale. Store the snapshot keyed by asset ID and stamp it with the capture date, because the file will change and the snapshot is your before picture. And treat an empty dictionary as a signal, not an error; it routes the asset straight to the stamping step rather than failing the intake.
Step 2 — Check Dublin Core at the Licensing Gate
Nine dc:* fields answer the questions legal and licensing actually ask. Reading just that scheme keeps the gate fast.
// dc:* fields only - Title, Creator, Rights, Subject and friends
var result = new Dictionary<string, string>();
using (var metadata = new Metadata(adobeFilePath))
{
var root = metadata.GetRootPackage() as IXmp;
var dc = root?.XmpPackage?.Schemes?.DublinCore;
if (dc == null) return result;
foreach (var property in dc)
{
result[property.Name] = property.InterpretedValue?.ToString()
?? property.Value?.ToString() ?? string.Empty;
}
}
return result;
Why these settings matter for production teams:
- Null-conditional chain: files without XMP are routine in fresh exports; an empty dictionary means “stamp me”, not “crash”.
- Scheme scope: gates run on every asset movement, so reading nine fields instead of the whole tree keeps them cheap.
Step 3 — Stamp Ownership at Export
The write touches three layers so every reader, XMP-aware or not, sees the same identity. Guards create missing packet and scheme objects first.
// Guard-create the packet and scheme, then write rights and creator
using (var metadata = new Metadata(inputPath))
{
var root = metadata.GetRootPackage() as IXmp;
if (root == null) return;
if (root.XmpPackage == null)
root.XmpPackage = new XmpPacketWrapper();
if (root.XmpPackage.Schemes.DublinCore == null)
root.XmpPackage.Schemes.DublinCore = new XmpDublinCorePackage();
var dc = root.XmpPackage.Schemes.DublinCore;
dc.SetRights(copyright);
dc.Set("dc:creator", XmpArray.From(new[] { creator }, XmpArrayType.Ordered));
// Mirror the identity for XmpBasic readers and tag-classified fields
if (root.XmpPackage.Schemes.XmpBasic == null)
root.XmpPackage.Schemes.XmpBasic = new XmpBasicPackage();
root.XmpPackage.Schemes.XmpBasic.CreatorTool = creator;
metadata.SetProperties(p => p.Tags.Contains(Tags.Person.Creator),
new PropertyValue(creator));
metadata.Save(outputPath);
}
The SetProperties call with Tags.Person.Creator is the detail worth stealing: it updates every property the library classifies as a creator field, wherever the format stores it, so tools that never read XMP still show the right name.
Step 4 — Write Keywords for Search
dc:subject is the vocabulary DAM search indexes. Without it, assets exist but never match a query.
// Replace the dc:subject bag with the pipeline's keyword list
root.XmpPackage.Schemes.DublinCore.Set(
"dc:subject",
XmpArray.From(keywords, XmpArrayType.Unordered));
metadata.Save(outputPath);
The write replaces the existing bag, so additive tagging means read, merge in C#, write. The repository writes three sample keywords and asserts the first survives in the saved bytes. Teams that version their taxonomy usually store the canonical keyword set per campaign and let the pipeline reconcile files against it on every export, which turns keyword drift into a diff instead of a debate.
Do we need Photoshop licenses just to fix metadata?
No, and that is usually the point of automating this. GroupDocs.Metadata reads and writes the packet directly in .NET, so a server-side job can stamp rights or fix keywords across an archive without opening a single Adobe application. Designers keep their tools for design work, while the pipeline owns metadata hygiene at scale.
What This Changes for the Business
The workflow above turns three recurring incidents into non-events. Rights questions stop requiring human memory, because dc:rights is checked at a gate and stamped when missing. Unsearchable assets stop accumulating, because keywords are written by the pipeline rather than by whoever remembered. And metadata work stops consuming Adobe seats, because none of the four steps opens a designer tool.
There is also an audit story here that manual editing can never offer. Every gate decision and every stamp is a logged code path, so when a client asks how an asset got its rights line, the answer is a timestamped pipeline record. The whole surface is five small methods, each asserted in the companion repository against both a PSD and an AI sample, which is exactly the kind of footprint a platform team can own without a dedicated maintainer.
Conclusion
XMP metadata in PSD and AI files stops being a manual chore once the pipeline owns it: snapshot at ingestion, check Dublin Core at gates, stamp ownership and keywords at export. One IXmp cast serves both formats, guards make fresh exports safe input, and every operation shown here runs asserted in the sample repository.
Ready to wire it into your pipeline?
- Clone the sample repository and run
dotnet run - Follow the technical deep-dive use case guide
- Read the Working with XMP metadata reference