Adobe XMP¶
Last Updated: April 21, 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 recognises 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 normalises 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 โ so all XMP support beyond a handful of sidecar fields depends on ExifTool being enabled. If PHOTOPRISM_DISABLE_EXIFTOOL is set, embedded XMP is not indexed.
.xmp Sidecar Files via the Built-In Reader (Proof of Concept)¶
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 a fixed set of values tometa.Data.internal/meta/xmp_document.goโ hard-coded Go struct withencoding/xmltags, plus accessor methods such asTitle(),Description(), andKeywords().
This reader is an initial, proof-of-concept implementation that only recognises a small subset of the fields the ExifTool-backed path covers. It supports only actual .xmp sidecar files and does not read XMP that is embedded in another media file.
Unlike the ExifTool-driven path โ where each Data field lists an ordered set of acceptable keys in its meta:"..." struct tag and the first non-empty value wins โ the direct sidecar reader has no namespace-priority mechanism. Each accessor in xmp_document.go looks up exactly one element (for example, dc:title for Title, tiff:Model for CameraModel, dc:rights for Copyright). The only fallbacks that currently exist are intra-element โ specifically, choosing between the XMP rdf:Alt/rdf:li language-tagged value and a plain chardata value for dc:title and dc:description. The xmp:"..." struct tags on meta.Data fields look like a namespace-priority list but are currently only consumed by internal/meta/report.go for the developer field report โ not by the XMP reader itself.
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.
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 only |
Caption |
dc:description |
Description, ImageDescription, Caption, Caption-Abstract |
โ | โ dc:description only |
Artist |
dc:creator; also photoshop:AuthorsPosition, photoshop:OwnerName |
Artist, Creator, By-line, OwnerName, Owner |
โ | โ dc:creator only |
Copyright |
dc:rights; xmpRights:WebStatement |
Rights, Copyright, CopyrightNotice, WebStatement |
โ | โ dc:rights only |
License |
xmpRights:UsageTerms |
UsageTerms, License |
โ | โ |
Subject |
dc:subject; lr:hierarchicalSubject (Lightroom); Iptc4xmpExt:PersonInImage |
Subject, HierarchicalSubject, PersonInImage, CatalogSets, ObjectName |
โ | โ |
Keywords |
dc:subject (aggregated); lr:hierarchicalSubject |
Keywords |
โ | โ dc:subject only |
TakenAt |
xmp:CreateDate; photoshop:DateCreated; exif:DateTimeOriginal |
SubSecDateTimeOriginal, DateTimeOriginal, CreationDate, DateTimeDigitized |
โ | โ photoshop:DateCreated only |
CreatedAt |
xmp:CreateDate; QuickTime CreateDate |
SubSecCreateDate, CreateDate, MediaCreateDate, TrackCreateDate |
โ | โ |
Software |
xmp:CreatorTool; xmpMM:History/softwareAgent |
Software, CreatorTool, HistorySoftwareAgent, ProcessingSoftware |
โ | โ |
CameraMake |
tiff:Make |
Make, CameraMake |
โ | โ tiff:Make only |
CameraModel |
tiff:Model; aux:UniqueCameraModel |
Model, CameraModel, UniqueCameraModel |
โ | โ tiff:Model only |
CameraSerial |
aux:SerialNumber |
SerialNumber |
โ | โ |
LensMake |
aux:LensMake / exifEX:LensMake |
LensMake |
โ | โ |
LensModel |
aux:Lens / exifEX:LensModel |
LensModel, Lens, LensID |
โ | โ <LensModel> element (local name; not aux:Lens) |
FocalLength |
exif:FocalLength, exifEX:FocalLengthIn35mmFilm |
FocalLength, FocalLengthIn35mmFormat |
โ | โ |
Exposure |
exif:ExposureTime, exif:ShutterSpeedValue |
ExposureTime, ShutterSpeedValue, ShutterSpeed |
โ | โ |
Aperture / FNumber |
exif:ApertureValue, exif:FNumber |
ApertureValue, Aperture, FNumber |
โ | โ |
Iso |
exif:ISOSpeedRatings |
ISO |
โ | โ |
Flash |
exif:Flash/Fired |
FlashFired |
โ | โ |
Rotation |
xmp:Rotation; tiff:Orientation |
Rotation, Orientation |
โ | โ |
Width / Height |
tiff:ImageWidth / ImageLength; exif:PixelXDimension / PixelYDimension |
ImageWidth, ExifImageWidth, PixelXDimension, ImageHeight, ExifImageHeight |
โ | โ |
GPSLatitude |
exif:GPSLatitude |
GPSLatitude, GPSPosition |
โ | โ |
GPSLongitude |
exif:GPSLongitude |
GPSLongitude, GPSPosition |
โ | โ |
Altitude |
exif:GPSAltitude |
GlobalAltitude, GPSAltitude |
โ | โ |
TakenGps |
exif:GPSTimeStamp / GPSDateStamp |
GPSDateTime, GPSDateStamp |
โ | โ |
Projection |
GPano:ProjectionType (Google Photo Sphere) |
ProjectionType |
โ | โ |
ColorProfile |
photoshop:ICCProfile |
ICCProfileName, ProfileDescription |
โ | โ |
DocumentID |
xmpMM:OriginalDocumentID / DocumentID; MWG ImageUniqueID |
ContentIdentifier, OriginalDocumentID, DocumentID, ImageUniqueID, BurstUUID |
โ | โ |
InstanceID |
xmpMM:InstanceID |
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 โ in the "
.xmpSidecar (Direct)" column identifies the single XMP element the reader actually consults โ it does not imply that the other aliases in the middle column are tried as fallbacks. For example, if a sidecar hasphotoshop:Headlinebut nodc:title, the direct reader leavesTitleempty even though the ExifTool path would pickHeadlineup. - The direct reader matches XML elements by local name, not by XML namespace. Its struct tags in
xmp_document.goare plain local names (xml:"title",xml:"Make", etc.), so the namespaces shown above are the conventional ones from the XMP spec โ a sidecar that uses a different prefix for the same element is still read, but an entirely different element (likephotoshop:HeadlineforTitle) is not. - 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 XML struct ininternal/meta/xmp_document.go. - Standalone
.xmpsidecar fields that the XML struct parses into memory but that the current reader does not yet assign tometa.Data(for exampleGPSLatitude,GPSLongitude,Rating,Rotation, and the full EXIF block) are intentionally left asโabove. Wiring these up is a natural next step โ see "Open Issues" below.
Sidecar Reader Limitations¶
The built-in .xmp sidecar reader is a proof-of-concept and has several known shortcomings:
- 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.
- No namespace-priority fallback. The ExifTool path uses
meta:"A,B,C"struct tags onmeta.Dataso that the first non-empty alias wins (for examplemeta:"Artist,Creator,By-line,OwnerName,Owner"forData.Artist, ormeta:"Rights,Copyright,CopyrightNotice,WebStatement"forData.Copyright). The direct sidecar reader's accessors inxmp_document.goare hard-wired to a single XML element per field, so a sidecar that only carriesphotoshop:Headline(or onlyxmpRights:WebStatement) leavesTitle/Copyrightempty even though the ExifTool path would populate them. Thexmp:"..."struct tags that already exist onmeta.Datafields look like such a priority list but are currently only consumed byinternal/meta/report.goto render the developer field report. - It has no generic XMP/RDF parser. The
XmpDocumentstruct is a hand-written mapping of the elements observed in sample files, so sidecars that nest fields differently (for example, inside additionalrdf:Descriptionnodes or with non-default namespace prefixes) may not parse as expected. - GPS coordinates are stored in XMP as strings like
52,27.5814N. The struct fields exist but are not converted to decimal latitude/longitude โ the indexer therefore never sees GPS data from.xmpsidecars. - Go's
encoding/xmldoes not natively unmarshal timestamps or rational numbers, so values like EXIF dates, apertures, and focal lengths would have to be parsed manually. Onlyphotoshop:DateCreatedis currently handled, viatxt.ParseTime. - The reader was originally prototyped on
trimmer-io/go-xmp, but that library did not produce the values we needed for our sample files, so the current implementation usesencoding/xmldirectly.
Pull requests that extend the supported field set (or replace the reader with a generic RDF-aware parser) 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 colour 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). - 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)
XML/XMP Libraries¶
The following Go libraries are worth considering as replacements or additions to the existing reader. Their documented maintenance state is as of April 2026.
XMP-Specific
evanoberholster/imagemetaโ MIT. Actively maintained (commits through April 2026). Broader image-metadata library with anxmpsub-package for sidecars and embedded XMP; decodes dates and rationals into Go types and handles nestedrdf:Description. Read-only, so only a partial replacement if we also want to write sidecars.
Generic XML / DOM (for a hand-rolled XMP Parser)
beevik/etreeโ BSD-2-Clause. Actively maintained (latest commit August 2025, ~1.7k stars). DOM-style XML with XPath-like selectors; strong fit for namespace-priority fallback (walk once, cherry-pickdc:title, thenphotoshop:Headlinevia prefix-aware paths) and supports writing. Rational/date coercion still has to be implemented manually.subchen/go-xmldomโ Apache-2.0. Same niche asetreewith a smaller community; preferetreeunless its XPath dialect is specifically needed.
RDF Parsers (XMP Is RDF/XML Under the Hood)
knakk/rdfโ MIT. Actively maintained (commits through March 2026). Turtle / N-Triples / RDF-XML parser that produces triples. Cleanest semantic match for XMP in theory (namespace-agnostic property lookup,xml:langalternatives via language-tagged literals), but we would reconstruct XMP-specific structures ourselves and it does not write RDF/XML back.deiu/rdf2goโ MIT. Graph API with Turtle + JSON-LD I/O; RDF/XML parsing is weaker thanknakk/rdf.
ExifTool Wrapper (Alternative Strategy)
barasher/go-exiftoolโ Apache-2.0. Actively maintained (~300 stars, latest commit August 2025). Wraps the Phil Harveyexiftoolbinary. Sidesteps parsing entirely โ ExifTool already resolves namespace priority,xml:langalternatives, rationals, and dates. PhotoPrism already shells out toexiftoolfor the embedded-XMP path (seeconvert_sidecar_json.go), so adopting this wrapper for.xmpsidecars too would unify both paths โ at the cost of making ExifTool (and its Perl runtime) a hard dependency for sidecar reading as well.
Notes on encoding/xml Itself
Go 1.23 added well-formedness enforcement (with an AllowIllFormed escape hatch), but namespace handling and xml:lang semantics are unchanged from what PhotoPrism originally hit โ see golang/go#14407, still open. A materially better encoding/xml is unlikely to land in the standard library; any real fix will almost certainly come via a third-party package.
No maintained Go binding for libexempi (the GNOME XMP Toolkit port) was found; the Go ecosystem has converged on native implementations.
Open Issues¶
- Replace the hand-written struct in
xmp_document.gowith a generic RDF-aware parser so arbitrary namespace prefixes and nestedrdf:Descriptionblocks parse correctly. See XML/XMP Libraries above โevanoberholster/imagemeta,beevik/etree, andknakk/rdfare the main contenders;barasher/go-exiftoolis an alternative if we're willing to make ExifTool a hard dependency for sidecars too. - Add a namespace-priority mechanism to the direct sidecar reader, analogous to the ExifTool path's
meta:"A,B,C"left-to-right fallback. Two natural options:- extend the hand-written accessors in
xmp_document.goso each falls back across equivalent namespaces (e.g.Title()readsdc:title, thenphotoshop:Headline;Copyright()readsdc:rights, thenxmpRights:WebStatement) - make the existing
xmp:"..."struct tags onmeta.Dataload-bearing โ drive the reader from them via reflection once the parser can resolve namespace-qualified element names.beevik/etreeis a promising foundation for option (a); option (b) fits better with an RDF-aware parser such asknakk/rdf.
- extend the hand-written accessors in
- Extend the built-in
.xmpsidecar reader to cover GPS (exif:GPSLatitude/exif:GPSLongitude/exif:GPSAltitude),xmp:Rating,xmp:Label,xmpMM:DocumentID/xmpMM:InstanceID,xmp:CreatorTool, andxmpRights:UsageTerms. - 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).