Skip to content

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 โ€” runs exiftool -n -m -api LargeFileSupport -j <file> (adds -ee for videos) and writes the result to the cache.
  • internal/photoprism/mediafile_meta.go โ€” calls CreateExifToolJson when the cached JSON is missing and then ReadExifToolJson to feed the cache into the metadata.
  • internal/meta/json_exiftool.go โ€” iterates the fields of meta.Data and, for each meta:"..." 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 point meta.XMP(fileName); assigns the recognised values to meta.Data, then normalises capture time, local time, and time zone through the shared (*Data).ResolveTimeZone resolver 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 on antchfx/xmlquery and antchfx/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.CompileWithNS against 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's meta:"A,B,C" left-to-right alias fallback. For example Title reads dc:title (language-tagged rdf:Alt โ†’ first rdf:li โ†’ bare text) and then falls back to photoshop:Headline; Copyright falls back from dc:rights to xmpRights: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. The elemOrAttr helper builds a union expression that matches both forms โ€” required because digiKam emits xmpMM:* / exif:* / tiff:* as attributes while Adobe writes them as child elements. Multiple sibling rdf:Description blocks (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 from exif:SubSecTimeOriginal, the APEX โ†’ seconds conversion for exif:ShutterSpeedValue, and the combined-vs-split exif:GPSTimeStamp/GPSDateStamp reassembly are all implemented in the relevant accessor rather than in the generic chain engine.
  • Loader security guards. Load rejects sidecars larger than 1 MiB (ErrXmpFileTooLarge) and documents nesting deeper than 64 elements (ErrXmpTooDeep). XXE and DTD attacks are mitigated by encoding/xml's default behaviour (no external entity resolution); internal/meta/xmp_security_test.go is 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's file_error, and continues with the remaining related files.
  • Source priority. Sidecar values are tagged SrcXmp (priority 32), which outranks SrcMeta (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 .xmp sidecar column now shows the full priority chain the reader evaluates, not a single element. Where a lang-alt fallback is noted, the reader prefers the xml:lang="x-default" rdf:li, then the first rdf:li, then bare element text.
  • Lat/Lng/Altitude are decimal floats derived in the reader. The reader does not populate the ExifTool-format string fields GPSLatitude/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 DocumentID is 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. InstanceID and Software are 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). ColorProfile and Projection are 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 on meta.Data in internal/meta/data.go. For the direct sidecar reader, the authoritative mapping is the set of chainXPath definitions in internal/meta/xmp_document.go; the per-fixture provenance lives in the fixture corpus under internal/meta/testdata/xmp/.
  • The xmp:"..." / dc:"..." struct tags on meta.Data are read by internal/meta/report.go to render the developer field report (photoprism show metadata-fields); they document the namespace mapping but are not the mechanism that drives the reader โ€” the chainXPath definitions 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 .xmp sidecars 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 a chainXPath at package init, document the priority order, add the accessor that calls firstNonEmpty (scalars) or queryAll (rdf:Bag/rdf:Seq), and wire the field into xmp.go with the existing "set only when non-empty" pattern.
  • encoding/xml's namespace and xml:lang handling 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.

Learn more โ€บ

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 under internal/meta/testdata/xmp/ (adobe/, darktable/, digikam/, synthetic/) and ship the paired *.exiftool.txt reference.
  • 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

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 an xmp sub-package; decodes dates and rationals into Go types and handles nested rdf: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 the exiftool binary; 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 driving antchfx/xmlquery + antchfx/xpath, and a handy debugging aid alongside exiftool -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.go with a namespace-aware parser so arbitrary namespace prefixes and nested rdf:Description blocks parse correctly. (Done in #2260 โ€” XPath-based reader on antchfx/xmlquery.)
  • Add a namespace-priority mechanism to the direct sidecar reader, analogous to the ExifTool path's left-to-right fallback. (Done โ€” chainXPath priority lists per field.)
  • Extend the built-in .xmp sidecar reader to cover GPS (exif:GPSLatitude / GPSLongitude / GPSAltitude), xmpMM:DocumentID / xmpMM:InstanceID, xmp:CreatorTool, and xmpRights: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).

Released Features