Skip to content

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 โ€” 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 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 point meta.XMP(fileName); assigns a fixed set of values to meta.Data.
  • internal/meta/xmp_document.go โ€” hard-coded Go struct with encoding/xml tags, plus accessor methods such as Title(), Description(), and Keywords().

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 ".xmp Sidecar (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 has photoshop:Headline but no dc:title, the direct reader leaves Title empty even though the ExifTool path would pick Headline up.
  • The direct reader matches XML elements by local name, not by XML namespace. Its struct tags in xmp_document.go are 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 (like photoshop:Headline for Title) is not.
  • 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 XML struct in internal/meta/xmp_document.go.
  • Standalone .xmp sidecar fields that the XML struct parses into memory but that the current reader does not yet assign to meta.Data (for example GPSLatitude, 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 on meta.Data so that the first non-empty alias wins (for example meta:"Artist,Creator,By-line,OwnerName,Owner" for Data.Artist, or meta:"Rights,Copyright,CopyrightNotice,WebStatement" for Data.Copyright). The direct sidecar reader's accessors in xmp_document.go are hard-wired to a single XML element per field, so a sidecar that only carries photoshop:Headline (or only xmpRights:WebStatement) leaves Title / Copyright empty even though the ExifTool path would populate them. The xmp:"..." struct tags that already exist on meta.Data fields look like such a priority list but are currently only consumed by internal/meta/report.go to render the developer field report.
  • It has no generic XMP/RDF parser. The XmpDocument struct is a hand-written mapping of the elements observed in sample files, so sidecars that nest fields differently (for example, inside additional rdf:Description nodes 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 .xmp sidecars.
  • Go's encoding/xml does not natively unmarshal timestamps or rational numbers, so values like EXIF dates, apertures, and focal lengths would have to be parsed manually. Only photoshop:DateCreated is currently handled, via txt.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 uses encoding/xml directly.

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.

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).
  • 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

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 an xmp sub-package for sidecars and embedded XMP; decodes dates and rationals into Go types and handles nested rdf: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-pick dc:title, then photoshop:Headline via prefix-aware paths) and supports writing. Rational/date coercion still has to be implemented manually.
  • subchen/go-xmldom โ€” Apache-2.0. Same niche as etree with a smaller community; prefer etree unless 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:lang alternatives 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 than knakk/rdf.

ExifTool Wrapper (Alternative Strategy)

  • barasher/go-exiftool โ€” Apache-2.0. Actively maintained (~300 stars, latest commit August 2025). Wraps the Phil Harvey exiftool binary. Sidesteps parsing entirely โ€” ExifTool already resolves namespace priority, xml:lang alternatives, rationals, and dates. PhotoPrism already shells out to exiftool for the embedded-XMP path (see convert_sidecar_json.go), so adopting this wrapper for .xmp sidecars 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.go with a generic RDF-aware parser so arbitrary namespace prefixes and nested rdf:Description blocks parse correctly. See XML/XMP Libraries above โ€” evanoberholster/imagemeta, beevik/etree, and knakk/rdf are the main contenders; barasher/go-exiftool is 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:
    1. extend the hand-written accessors in xmp_document.go so each falls back across equivalent namespaces (e.g. Title() reads dc:title, then photoshop:Headline; Copyright() reads dc:rights, then xmpRights:WebStatement)
    2. make the existing xmp:"..." struct tags on meta.Data load-bearing โ€” drive the reader from them via reflection once the parser can resolve namespace-qualified element names. beevik/etree is a promising foundation for option (a); option (b) fits better with an RDF-aware parser such as knakk/rdf.
  • Extend the built-in .xmp sidecar reader to cover GPS (exif:GPSLatitude / exif:GPSLongitude / exif:GPSAltitude), xmp:Rating, xmp:Label, xmpMM:DocumentID / xmpMM:InstanceID, xmp:CreatorTool, and xmpRights: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).

Released Features