Adobe XMP¶
Last Updated: May 15, 2026
XMP (Extensible Metadata Platform) is an XML-based metadata container developed by Adobe. It can be embedded in common image and video formats (JPEG, HEIC, TIFF, DNG, MP4, MOV, PSD, โฆ) and is also used as a standalone sidecar format โ typically named the same as the original with an .xmp extension โ to carry Dublin Core, IPTC, EXIF, and vendor-specific fields.
How PhotoPrism Reads XMP¶
PhotoPrism has two separate code paths for XMP, and it is important to understand which one handles your files:
Embedded XMP via ExifTool (Primary Path)¶
For XMP packets embedded in a media file, PhotoPrism does not parse the XML itself. Instead, the indexer runs ExifTool once per file and caches its output as a JSON document. ExifTool flattens EXIF, XMP, IPTC, Maker Notes, QuickTime atoms, and vendor tags into a single object; PhotoPrism then reads the values it recognizes from that JSON.
The relevant code:
internal/photoprism/convert_sidecar_json.goโ runsexiftool -n -m -api LargeFileSupport -j <file>(adds-eefor videos) and writes the result to the cache.internal/photoprism/mediafile_meta.goโ callsCreateExifToolJsonwhen the cached JSON is missing and thenReadExifToolJsonto feed the cache into the metadata.internal/meta/json_exiftool.goโ iterates the fields ofmeta.Dataand, for eachmeta:"..."struct tag, assigns the first non-empty value found in the ExifTool JSON.
ExifTool normalizes tag names across groups by default, so an ExifTool JSON key such as Description may originate from XMP-dc:description, IPTC:Caption-Abstract, or EXIF:ImageDescription โ whichever group ExifTool selected for that file. If you need to see the origin explicitly, pass -g to ExifTool (exiftool -g -j <file>) when debugging.
This path also covers the XMP that exiftool extracts from RAW, HEIC, and video containers. If PHOTOPRISM_DISABLE_EXIFTOOL is set, embedded XMP is not indexed.
.xmp Sidecar Files via the Built-In Reader¶
When the indexer encounters a standalone .xmp file (see internal/photoprism/index_mediafile.go, case m.IsXMP()), it does not invoke ExifTool. It parses the XML directly with the built-in reader in:
internal/meta/xmp.goโ entry pointmeta.XMP(fileName); assigns the recognised values tometa.Data, then normalises capture time, local time, and time zone through the shared(*Data).ResolveTimeZoneresolver so the sidecar path produces the same entity state the ExifTool path would for identical metadata.internal/meta/xmp_document.goโ the reader itself: an XPath-based, namespace-aware parser built onantchfx/xmlqueryandantchfx/xpath.
The reader was rewritten in #2260 and is no longer a proof of concept. It now covers the same descriptive, camera/lens/exposure, GPS, identity, and time fields the ExifTool path covers (the remaining gaps are listed under Open Issues), and it reads standalone .xmp sidecar files only โ it does not read XMP embedded in another media file.
Key design points (see internal/meta/README.md for the authoritative description):
- Namespace-aware queries. Every XPath expression is compiled once at package init via
xpath.CompileWithNSagainst a fixed prefixโURI map. The reader matches by XML namespace, not by local name, so a sidecar that uses a non-default prefix for a known namespace still parses, and unrelated elements that merely share a local name do not collide. - Namespace-priority fallback. Each field is backed by a
chainXPathโ an ordered list of expressions evaluated left-to-right; the first non-empty match wins. This is the direct-reader equivalent of the ExifTool path'smeta:"A,B,C"left-to-right alias fallback. For exampleTitlereadsdc:title(language-taggedrdf:Altโ firstrdf:liโ bare text) and then falls back tophotoshop:Headline;Copyrightfalls back fromdc:rightstoxmpRights:WebStatement. - Element-or-attribute matching. RDF/XML allows a scalar property to be written either as a child element or as an attribute on
rdf:Description. TheelemOrAttrhelper builds a union expression that matches both forms โ required because digiKam emitsxmpMM:*/exif:*/tiff:*as attributes while Adobe writes them as child elements. Multiple siblingrdf:Descriptionblocks (each declaring its own namespace) are also walked correctly. - Composition lives in the accessor, not the chain. Sign handling (
GPSLatitudeRef/GPSLongitudeRef), the sub-second join fromexif:SubSecTimeOriginal, the APEX โ seconds conversion forexif:ShutterSpeedValue, and the combined-vs-splitexif:GPSTimeStamp/GPSDateStampreassembly are all implemented in the relevant accessor rather than in the generic chain engine. - Loader security guards.
Loadrejects sidecars larger than 1 MiB (ErrXmpFileTooLarge) and documents nesting deeper than 64 elements (ErrXmpTooDeep). XXE and DTD attacks are mitigated byencoding/xml's default behaviour (no external entity resolution);internal/meta/xmp_security_test.gois the regression guard. A malformed sidecar does not block indexing of the related image โ the indexer logs a warning, records the parse failure in the XMP file row'sfile_error, and continues with the remaining related files. - Source priority. Sidecar values are tagged
SrcXmp(priority 32), which outranksSrcMeta(priority 16) at the entity layer. Re-indexing a photo after a sidecar has been added therefore overwrites the previously embedded-path values without a database wipe โ including GPS coordinates, which the sidecar overrides even when the image carries its own embedded EXIF position.
Fields Extracted from XMP¶
The table below lists the XMP elements PhotoPrism currently consumes, their primary XMP namespace, and whether each path reads them. ExifTool-JSON keys are the default (un-grouped) names as they appear in the output of exiftool -n -j; PhotoPrism looks them up case-sensitively via the meta:"..." struct tags on meta.Data. The ".xmp Sidecar (Direct)" column shows the priority chain the reader evaluates (first non-empty match wins).
PhotoPrism Data Field |
XMP Namespace and Element | ExifTool JSON Key(s) | Embedded (ExifTool) | .xmp Sidecar (Direct) |
|---|---|---|---|---|
Title |
dc:title (Dublin Core); also photoshop:Headline |
Title, Headline |
โ | โ dc:title โ photoshop:Headline |
Caption |
dc:description |
Description, ImageDescription, Caption, Caption-Abstract |
โ | โ dc:description (lang-alt fallback) |
Artist |
dc:creator |
Artist, Creator, By-line, OwnerName, Owner |
โ | โ first dc:creator/rdf:Seq entry |
Copyright |
dc:rights; xmpRights:WebStatement |
Rights, Copyright, CopyrightNotice, WebStatement |
โ | โ dc:rights โ xmpRights:WebStatement |
License |
xmpRights:UsageTerms |
UsageTerms, License |
โ | โ xmpRights:UsageTerms (lang-alt fallback) |
Subject |
dc:subject; lr:hierarchicalSubject; Iptc4xmpExt:PersonInImage |
Subject, HierarchicalSubject, PersonInImage, CatalogSets, ObjectName |
โ | โ |
Keywords |
dc:subject (aggregated) |
Keywords |
โ | โ dc:subject/rdf:Bag โ dc:subject/rdf:Seq |
TakenAt |
photoshop:DateCreated; exif:DateTimeOriginal; xmp:CreateDate |
SubSecDateTimeOriginal, DateTimeOriginal, CreationDate, DateTimeDigitized |
โ | โ photoshop:DateCreated โ exif:DateTimeOriginal โ xmp:CreateDate (+ exif:SubSecTimeOriginal join) |
CreatedAt |
xmp:CreateDate; xmpDM:CreationDate |
SubSecCreateDate, CreateDate, MediaCreateDate, TrackCreateDate |
โ | โ xmp:CreateDate โ xmpDM:CreationDate |
TimeOffset |
exif:OffsetTimeOriginal / OffsetTime / OffsetTimeDigitized |
OffsetTimeOriginal, OffsetTime |
โ | โ OffsetTimeOriginal โ OffsetTime โ OffsetTimeDigitized |
Software |
xmp:CreatorTool |
Software, CreatorTool, HistorySoftwareAgent, ProcessingSoftware |
โ | โ xmp:CreatorTool |
CameraMake |
tiff:Make |
Make, CameraMake |
โ | โ tiff:Make |
CameraModel |
tiff:Model |
Model, CameraModel, UniqueCameraModel |
โ | โ tiff:Model |
CameraSerial |
exifEX:SerialNumber; aux:SerialNumber |
SerialNumber |
โ | โ exifEX:SerialNumber โ aux:SerialNumber |
CameraOwner |
aux:OwnerName |
OwnerName, Owner |
โ | โ aux:OwnerName |
LensMake |
exifEX:LensMake |
LensMake |
โ | โ exifEX:LensMake |
LensModel |
exifEX:LensModel; aux:Lens / aux:LensID |
LensModel, Lens, LensID |
โ | โ exifEX:LensModel โ aux:Lens โ aux:LensID |
FocalLength |
exif:FocalLength; exif:FocalLengthIn35mmFilm |
FocalLength, FocalLengthIn35mmFormat |
โ | โ exif:FocalLength โ exif:FocalLengthIn35mmFilm |
Exposure |
exif:ExposureTime; exif:ShutterSpeedValue |
ExposureTime, ShutterSpeedValue, ShutterSpeed |
โ | โ exif:ExposureTime โ exif:ShutterSpeedValue (APEX) |
Aperture / FNumber |
exif:ApertureValue; exif:FNumber |
ApertureValue, Aperture, FNumber |
โ | โ exif:ApertureValue / exif:FNumber |
Iso |
exifEX:PhotographicSensitivity; exif:ISOSpeedRatings |
ISO |
โ | โ exifEX:PhotographicSensitivity โ exif:ISOSpeedRatings |
Flash |
exif:Flash/Fired |
FlashFired |
โ | โ exif:Flash/Fired (element / attribute / nested) |
Notes |
exif:UserComment |
UserComment |
โ | โ exif:UserComment (lang-alt fallback) |
Rotation |
xmp:Rotation; tiff:Orientation |
Rotation, Orientation |
โ | โ |
Width / Height |
tiff:ImageWidth / ImageLength; exif:PixelXDimension / PixelYDimension |
ImageWidth, ExifImageWidth, PixelXDimension, ImageHeight, ExifImageHeight |
โ | โ |
Lat / Lng |
exif:GPSLatitude / GPSLongitude (+ GPSLatitudeRef / GPSLongitudeRef) |
GPSLatitude, GPSLongitude, GPSPosition |
โ | โ decimal, DMS, and 2-component Adobe form |
Altitude |
exif:GPSAltitude (+ GPSAltitudeRef) |
GlobalAltitude, GPSAltitude |
โ | โ rational, ref-sign applied |
TakenGps |
exif:GPSTimeStamp / GPSDateStamp |
GPSDateTime, GPSDateStamp |
โ | โ combined ISO 8601 โ split GPSDateStamp + GPSTimeStamp |
Projection |
GPano:ProjectionType (Google Photo Sphere) |
ProjectionType |
โ | โ GPano:ProjectionType (auto-adds the panorama keyword) |
ColorProfile |
photoshop:ICCProfile |
ICCProfileName, ProfileDescription |
โ | โ photoshop:ICCProfile |
DocumentID |
xmpMM:OriginalDocumentID / DocumentID; dc:identifier |
ContentIdentifier, OriginalDocumentID, DocumentID, ImageUniqueID, BurstUUID |
โ | โ OriginalDocumentID โ DocumentID โ dc:identifier |
InstanceID |
xmpMM:InstanceID |
InstanceID |
โ | โ xmpMM:InstanceID |
Favorite |
Custom http://www.fstopapp.com/xmp/ favorite attribute (F-Stop app) |
Favorite (when present) |
โ (via tag alias) | โ F-Stop favorite="1" attr |
HasThumbEmbedded |
photoshop:Thumbnail |
ThumbnailImage, PhotoshopThumbnail |
โ | โ |
HasVideoEmbedded |
Google Motion Photo (GCamera:MicroVideo), Samsung MotionPhoto |
EmbeddedVideoFile, MotionPhoto, MotionPhotoVideo, MicroVideo |
โ | โ |
Notes
- The XMP element column lists the primary namespace mapping. Many fields are aliased across several namespaces (for example,
dc:titleโphotoshop:HeadlineโIPTC:Headline) and ExifTool merges them, which is why PhotoPrism usually lists multiple ExifTool keys per field. - The
.xmpsidecar column now shows the full priority chain the reader evaluates, not a single element. Where a lang-alt fallback is noted, the reader prefers thexml:lang="x-default"rdf:li, then the firstrdf:li, then bare element text. Lat/Lng/Altitudeare decimal floats derived in the reader. The reader does not populate the ExifTool-format string fieldsGPSLatitude/GPSLongitudeโ those keep whatever the embedded path set. GPS decimal parsing now accepts the pure decimal form, the 3-component DMS form (51 deg 15' 17.47" N), and the 2-component Adobe XMP form (52,30.4567N) that older readers silently dropped.- The XMP
DocumentIDis adopted as the photo UUID. Real-world document IDs are frequently non-canonical (no dashes,adobe:docid:/xmp.did:prefixes), so it is stored as-is rather than discarded by a strict UUID check.InstanceIDandSoftwareare mirrored onto the primary file row so the values are visible in the UI (which shows per-file identity metadata for the primary JPEG/HEIC).ColorProfileandProjectionare intentionally not mirrored to the primary file โ they describe the physical image container, not user-supplied sidecar metadata. - The authoritative mapping for the ExifTool path lives in the
meta:"..."struct tags onmeta.Dataininternal/meta/data.go. For the direct sidecar reader, the authoritative mapping is the set ofchainXPathdefinitions ininternal/meta/xmp_document.go; the per-fixture provenance lives in the fixture corpus underinternal/meta/testdata/xmp/. - The
xmp:"..."/dc:"..."struct tags onmeta.Dataare read byinternal/meta/report.goto render the developer field report (photoprism show metadata-fields); they document the namespace mapping but are not the mechanism that drives the reader โ thechainXPathdefinitions are.
Sidecar Reader Limitations¶
The built-in .xmp sidecar reader now covers the high-value descriptive, camera, GPS, identity, and time fields, but it is still a focused reader rather than a general-purpose XMP toolkit:
- Only the fields marked as supported in the table above are applied; everything else in the sidecar is ignored, even if it is valid XMP. Notably
xmp:Rating,xmp:Label,Subject,Rotation/Orientation, image dimensions, and embedded-media flags are not read from sidecars (they remain ExifTool-only). - The reader is read-only. It does not write
.xmpsidecars back out; round-tripping or generating sidecars is out of scope. - It is not a generic RDF/XMP processor. Each supported field is wired explicitly through a
chainXPath; a brand-new namespace or property is not picked up until an accessor and chain are added for it. Adding one is intentionally small โ declare achainXPathat package init, document the priority order, add the accessor that callsfirstNonEmpty(scalars) orqueryAll(rdf:Bag/rdf:Seq), and wire the field intoxmp.gowith the existing "set only when non-empty" pattern. encoding/xml's namespace andxml:langhandling remains the long-standing Go limitation (golang/go#14407); the reader works around it with XPath-level namespace binding rather than relying on the standard unmarshaller.
Pull requests that extend the supported field set are welcome.
RAW Conversion¶
PhotoPrism currently supports Darktable and RawTherapee as RAW image converters (as well as Sips on macOS). Darktable fully supports XMP sidecar files; RawTherapee only partially. XMP is a container format, so the fields (namespaces) used to describe how an image should be rendered differ between Lightroom/Photoshop, Darktable, and RawTherapee โ an application that "supports XMP" in general may still be unable to interpret edits written by another vendor.
From our experience, some basic edits done with Adobe tools โ such as cropping โ can survive conversion with Darktable, while advanced edits like lens or color corrections usually do not.
Resources¶
File Samples¶
We would be happy to receive more XMP files for testing. There are two ways to contribute:
- Pull request against
internal/meta/testdataโ see the Pull Requests guide. Use this for files you are clearly licensed to share publicly (files you created yourself, or files from an openly licensed corpus). New fixtures should follow the corpus layout underinternal/meta/testdata/xmp/(adobe/,darktable/,digikam/,synthetic/) and ship the paired*.exiftool.txtreference. - Email to samples@photoprism.app โ for files you cannot or would rather not commit directly. Please include the file format and the related GitHub issue number (or other helpful reference) in the subject line, and let us know whether we have permission to upload your files to dl.photoprism.app/samples so other contributors can use them for regression testing.
A short note about the camera or software that produced the sidecar, which fields are relevant, and what PhotoPrism currently gets wrong about the file helps us triage quickly.
References¶
- XMP Part 1: Data and Serialization Model
- XMP Part 2: Standard Schemas
- XMP Part 3: Storage in Files
- Adobe XMP Programmers Guide
- Adobe XMP Files Plugin SDK
- Adobe BSD 3-Clause License and XMP Toolkit SDK
- ExifTool Tag Names: XMP โ authoritative list of the XMP tags ExifTool exposes.
- XMP code in GIMP โ mostly comments; included here for reference.
- Camera Raw Schema (exiv2 reference)
Implementation & Library Notes¶
The .xmp sidecar reader is built on antchfx/xmlquery (MIT) and antchfx/xpath (MIT), which provide a DOM-style parser and full XPath 1.0 with namespace-aware compilation via xpath.CompileWithNS(expr, nsMap). This is what makes the namespace-priority chains (//dc:title | //photoshop:Headline, compiled once and reused for every sidecar in an indexer run) possible. Both libraries are read-only, so writing sidecars back out would still require encoding/xml or another library.
Alternatives considered during the rewrite, and still relevant if the scope expands to writing sidecars or to a fully RDF-aware model:
evanoberholster/imagemetaโ MIT, actively maintained. Broader image-metadata library with anxmpsub-package; decodes dates and rationals into Go types and handles nestedrdf:Description. Read-only.beevik/etreeโ BSD-2-Clause, actively maintained. DOM-style XML with XPath-like selectors and write support; rational/date coercion still manual.knakk/rdfโ MIT, actively maintained. Turtle / N-Triples / RDF-XML triple parser; cleanest semantic match for XMP-as-RDF but does not write RDF/XML back.barasher/go-exiftoolโ Apache-2.0. Wraps theexiftoolbinary; would unify both XMP paths at the cost of making ExifTool a hard dependency for sidecar reading as well.sibprogrammer/xqโ MIT. CLI XML extractor; not importable, but a compact working example of drivingantchfx/xmlquery+antchfx/xpath, and a handy debugging aid alongsideexiftool -g -j <file>.
No maintained Go binding for libexempi was found; the Go ecosystem has converged on native implementations.
Open Issues¶
- Replace the hand-written struct in
xmp_document.gowith a namespace-aware parser so arbitrary namespace prefixes and nestedrdf:Descriptionblocks parse correctly. (Done in #2260 โ XPath-based reader onantchfx/xmlquery.) - Add a namespace-priority mechanism to the direct sidecar reader, analogous to the ExifTool path's left-to-right fallback. (Done โ
chainXPathpriority lists per field.) - Extend the built-in
.xmpsidecar reader to cover GPS (exif:GPSLatitude/GPSLongitude/GPSAltitude),xmpMM:DocumentID/xmpMM:InstanceID,xmp:CreatorTool, andxmpRights:UsageTerms. (Done โ see the field table above.) - Extend the reader to the remaining ExifTool-only fields:
xmp:Rating,xmp:Label,Subject,Rotation/Orientation, image dimensions, and the embedded-media flags. - Add sidecar write support (or a generic RDF model) so PhotoPrism can persist edits back to
.xmp. The current reader is read-only. - Experiment with Adobe Lightroom to see how it currently uses sidecar files. Recent versions of Lightroom no longer appear to sync metadata to XMP by default, probably because Adobe focuses on cloud storage. Needs further investigation.
- Create a matrix showing which fields are used/supported by which application (Photoshop, Lightroom, Darktable, and others โ see also RAW Image Conversion).