Getting image metadata (EXIF) using Node.js

; Date: Wed Nov 17 2021

Tags: Node.JS »»»» Image Processing

We can store extra data inside images, such as geolocation coordinates, text strings, and more. While there are fancy GUI applications for dealing with this data, we sometimes need to process it in our Node.js programs. For example, a static website generator platform, like AkashaCMS, might use metadata stored in images such as to automatically provide captions, or tooltip text. What we'll do in this tutorial is explore reading metadata in Node.js.

Our goal in this article is learning about accessing image metadata in a Node.js program.

What, precisely, is image metadata? It is the extra data carried in the image file beyond the pixels. The pixels are the visual part of the image that we see. But, there is other non-visual data items that can be included in an image. Much of it is data about the camera, the exposure, and even the geographical location, but it is possible to attach arbitrary information.

A possible use is sorting images by the location where they were shot. Or, we could store descriptions, captions, or other text items, that are meant to be displayed by a content management system. As we'll see there is a long list of possible data to store in images.

The rationale for accessing this data, is we need to use it, to help drive an application, to show some data to the user, or anything else. It's up to you how you'll use this data, but the first step is knowing how to retrieve this data. Since there are several packages for this purpose, we'll show small example scripts for each.

There are several metadata formats available:

Format Image Types Description
EXIF JPEG, TIFF Developed by Japan Electronic Industries Development Association (JEIDA), and containing fields for date and time, camera settings, a thumbnail, descriptions, and copyright.
IPTC Information Interchange Model (IIM) JPEG/Exif, TIFF, JPEG2000 or Portable Network Graphics (PNG) Developed by the International Press Telecommunications Council (IPTC), to support the exchange of news between international news organizations.
Extensible Metadata Platform (XMP) TIFF, JPEG, JPEG2000, PNG, GIF, MP3, MP4, MOV, PDF, WebP An ISO standard originally developed by Adobe, and supersedes the IPTC model. The structure is what's known as Resource Description Framework, and is commonly serialized as a subset of XML. This means it can store any data, however the most commonly used tags are from the Dublin Core Metadata Initiative.

That's good, and to learn in practice what that means let's open up a photograph in Gimp.

This picture was shot in July 2013, during the second BC-BC endurance run for electric vehicles. The BC-BC event featured a sort of race from British Columbia to Baja California, showcasing the ability of electric vehicles to travel long distances even in 2013. This may seem like a commonplace thing now that several electric vehicle makers are offering long-range EV's, but in 2013 that idea was poppycock that any electric car could make such a long trip. A dozen or more cars were involved, and nowadays this kind of trip is commonplace thanks to an increase in fast charging infrastructure. The picture was taken at a car dealership in Redwood City, which served as one of the many waypoints for the event. But I've gotten distracted.

The point here is that - in Gimp v2.10 - you open an image file, then in the Image menu you find a Metadata sub-menu, which has one choice to Edit the Metadata, and another choice to View it. This image was shot using an Olympus E-PL2, a micro-four-thirds mirrorless camera I owned at the time. I've scrolled down to the exposure area, and I see that the image is slightly overexposed because the exposure bias is at 0 EV. I know now that it's usually better to set that to -2/3 EV, but I've gotten distracted again. The real point is that there are Olympus-specific EXIF values, and generic EXIF.Photo values. This also has tabs for XMP and IPTC data, both of which are empty in this case.

There are close to a zillion GUI applications for editing EXIF, XMP, and IPTC data in images. Some of them offer batch editing, meaning to set values for a group of pictures all at once. With others, like Gimp, you edit one image file at a time.

There are some open source command-line tools, like exif, exiv2, exempi, and exiftool, that can both read and write not just EXIF values, but much more. I use macOS, and use MacPorts, and the four tools are installed using this command:

$ sudo port install exif exiv2 exempi exiftool
$ exif /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG
$ exiv2 pr /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG
$ exempi -x /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG
$ exiftool -l /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG

For further usage, see the corresponding help information for each command.

A related tool is the ImageMagick suite. As the name implies, this is a group of tools for performing magic with images. One of the tools, identify, is useful for inspecting image data.

$ identify -verbose /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG

These different tools are presenting the same data in different ways. It's hard to point to a specific tool that's better than the others. Exiftool offers a dizzyingly long list of options, but at the cost of confusing and long documentation. Fortunately, the documentation for Exiv2 is much easier to understand.

But, enough introduction. Our point for this article is, how do we access image metadata in a Node.js program?

Our plan of attack is to search the npm registry for packages that read image metadata, and can possibly change that data, and put them through their paces.

We'll be creating several small scripts, so let's start by making a project directory and initializing package.json:

$ mkdir image-metadata
$ cd image-metadata
$ npm init -y

This is of course what we do for every Node.js project.

Using exif-parser to read EXIF tags from an image

For the first script, let's install the exif-parser package:

$ npm install exif-parser --save

+ exif-parser@0.1.12
added 1 package from 1 contributor and audited 1 package in 1.915s
found 0 vulnerabilities

This is a relatively popular package, and its primary purpose is reading EXIF data from an image.

According to the ( exif-parser documentation, we supply it with a Buffer, call the parse method, then we're given an object named tags. That's simple enough.

We'll be writing simple scripts, using ES6 modules, and using top-level-async, as described here: Node.js Script writers: Top-level async/await now available

Create a file named exif-parser.mjs containing this:

import { promises as fs } from 'fs';
import ExifParser from 'exif-parser';

const imgbuffer = await fs.readFile(process.argv[2]);
const parser = ExifParser.create(imgbuffer);

const img = parser.parse();


We read the file named on the command line. The fs.readFile method, if we do not supply an encoding, will return a Buffer containing the data from the file. That's exactly what we need for the ExifParser.create function. The next thing we do is ensure a few options are set, then we call parse, and finally print the tags:

$ node exif-parser.mjs /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG 
  ImageDescription: 'OLYMPUS DIGITAL CAMERA         ',
  Model: 'E-PL2           ',
  XResolution: 314,
  YResolution: 314,
  ResolutionUnit: 2,

There we go, simple and easy-to-use method to read EXIF tags. The only issue I see is that this misses a lot of data that Gimp showed on this file. Namely, Gimp showed a large number of Olympus-specific tags, none of which are shown here.

Using exifreader to read image metadata

Let's move on to the next package, exifreader. This one supports a wide range of image types, and it also supports Exif, IPTC, XMP, ICC, and MPF metadata. This could be a very good option.


$ npm install exifreader --save

+ exifreader@4.0.0
added 2 packages from 1 contributor and audited 3 packages in 4.157s
found 0 vulnerabilities

The ( documentation page says we can generate a customized module if we want a limited version of the package to support limited needs. Installing this way supports every file type and metadata type.

Now create a file named exifreader.mjs containing:

import { promises as fs } from 'fs';
import ExifReader from 'exifreader';

const imgbuffer = await fs.readFile(process.argv[2]);
//////// You can do this instead
// const tags = await ExifReader.load(process.argv[2]);
const tags = ExifReader.load(imgbuffer, {
    expanded: true,
    includeUnknown: true


There is an options object, and in this case we're setting every documented option.

$ node exifreader.mjs /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG 
  file: {
    'Bits Per Sample': { value: 8, description: '8' },
    'Image Height': { value: 3024, description: '3024px' },
    'Image Width': { value: 4032, description: '4032px' },
    'Color Components': { value: 3, description: '3' },
    Subsampling: { value: [Array], description: 'YCbCr4:2:2 (2 1)' }
  Thumbnail: {
    Compression: { id: 259, value: 6, description: 6 },
    XResolution: { id: 282, value: [Array], description: '72' },
    YResolution: { id: 283, value: [Array], description: '72' },
    ResolutionUnit: { id: 296, value: 2, description: 'inches' },
    JPEGInterchangeFormat: { id: 513, value: 11476, description: 11476 },
    JPEGInterchangeFormatLength: { id: 514, value: 6929, description: 6929 },
    type: 'image/jpeg',
    image: ArrayBuffer { ... },
    base64: [Getter]
  exif: {
    ImageDescription: {
      id: 270,
      value: [Array],
      description: 'OLYMPUS DIGITAL CAMERA         '
    Make: { id: 271, value: [Array], description: 'OLYMPUS IMAGING CORP.  ' },
    Model: { id: 272, value: [Array], description: 'E-PL2           ' },
    XResolution: { id: 282, value: [Array], description: '314' },
    YResolution: { id: 283, value: [Array], description: '314' },
    ResolutionUnit: { id: 296, value: 2, description: 'inches' },
 iptc: {
    'Model Version': { id: 256, value: [Array], description: '4' },
    'Coded Character Set': { id: 346, value: [Array], description: 'UTF-8' },
    'Record Version': { id: 512, value: [Array], description: '4' },
    'By-line': { id: 592, value: [Array], description: 'Picasa' }
  xmp: {
    about: { value: '', attributes: {}, description: '' },
    ModifyDate: {
      value: '2013-07-08T15:40:38-07:00',
      attributes: {},
      description: '2013-07-08T15:40:38-07:00'
    creator: { value: [Array], attributes: {}, description: 'Picasa' }

The object that's returned has five fields: file, describing the file, Thumbnail, containing the thumbnail image, exif, containing the EXIF data, iptc, containing the IPTC data, and xmp, containing the XMP data. It's presented as a nice data structure which is easy to understand.

Using exiftool-vendored to read image metadata

There are two Node.js packages that wrap around the exiftool program, exiftool and exiftool2, that look like they are not good choices. Instead, exiftool-vendored looks like a much better wrapper around exiftool.

What's going on is that exiftool is a Perl script, that is available as a regular command-line tool. These wrapper packages execute this script in the background rather than implement metadata parsing in Node.js. What makes exiftool-vendored interesting is the good quality API.

$ npm install exiftool-vendored --save

+ exiftool-vendored@15.6.0
added 6 packages from 6 contributors and audited 10 packages in 13.673s
found 0 vulnerabilities

The ( documentation site gives a sense of what this package can do. Namely, it is able to read a huge long list of metadata items from image files, where the supported tags are derived from a large library of sample images. In several cases the documentation refers you to the exiftool documentation as well.

An important page to consult is ( the list of supported Tags.

Now, create a file named exiftool-vendored.mjs containing:

import { exiftool /* , ExifTool */ } from 'exiftool-vendored';

// The `exiftool` import is a prebaked instance of the ExifTool class with
// sensible defaults.  If you want different defaults, change the 
// import statement and run this instead:

// const exiftool = new ExifTool({ taskTimeoutMillis: 5000 })

const tags = await[2]);



As said in the comments, you can create a custom exiftool instance by calling the ExifTool constructor yourself.

Calling asynchronously reads the named image file, and returns a tags object. We can also use this for writing tags, and more.

Calling exiftool.end is required so that the script exits.

$ node exiftool-vendored.mjs /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG 
  SourceFile: '/Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG',
  errors: [],
  ExifToolVersion: 12.34,
  FileName: 'P7050342.JPG',
  Directory: '/Volumes/david/media/2013-07-02-BC-BC',
  FileSize: '4.3 MiB',
  FileModifyDate: ExifDateTime {
    year: 2013,
    month: 7,
    day: 5,
    hour: 10,
    minute: 52,
    second: 4,
    millisecond: 0,
    tzoffsetMinutes: -420,
    rawValue: '2013:07:05 10:52:04-07:00',
    zoneName: 'UTC-7'

This prints out the returned object. An important thing to notice is this object does not list IPTC or XMP fields, etc. What's happening is the image I used here does not contain tags of those sorts.

The underlying tool, exiftool, does support those other types of metadata. And consulting the Tags documentation page verifies that it will read all the tags supported by exiftool.

Using imagemagick to read image metadata

The ImageMagick package is a comprehensive set of tools for not only reading image metadata, but performing all kinds of slicing and dicing operations. See ( for details on that package.

The ( imagemagick package for Node.js is a wrapper around the command-line tools. To use this package you must first install ImageMagick. Fortunately it is readily available, for example through MacPorts or HomeBrew on macOS, or in the standard package managers for Linux distros.

$ npm install imagemagick --save

+ imagemagick@0.1.3
added 1 package from 1 contributor and audited 11 packages in 8.568s
found 0 vulnerabilities

Then create a file named imagemagick.mjs containing:

import im from 'imagemagick';

const metadata = await new Promise((resolve, reject) => {
    im.readMetadata(process.argv[2], function(err, metadata) {
        if (err) reject(err);
        else resolve(metadata);


const identified = await new Promise((resolve, reject) => {
    im.identify(process.argv[2], function(err, metadata) {
        if (err) reject(err);
        else resolve(metadata);


First, this package is a throwback to the old days of Node.js where everything was done with callback functions. A major issue with this package is that it has not been updated for nine years, and therefore knows nothing about Promise's, and therefore we cannot use await with this package. That means we must surround it with a Promise wrapper as shown here.

We're demonstrating two methods to read the metadata.

The readMetadata method calls the identify CLI tool. It parses the output, and returns an object containing data.

Optionally, we can use the identify method, which also calls the identify CLI tool, parses the output, and returns an object containing data. The two objects are similar, but different. You may prefer one over the other.

Using sharp to read image metadata

Sharp is a modern package for image manipulation, that is native to JavaScript. Instead of wrapping around a command-line tool, you have a nice JavaScript API to deal with. Among its many methods is the ability to read image metadata, and even to change the metadata.

Full documentation: (

Package home page: (

$ npm install sharp --save

+ sharp@0.29.3
added 67 packages from 197 contributors and audited 78 packages in 44.277s

found 0 vulnerabilities

Because this relies on a native-code library, libvips, it will either download a precompiled library, or compile the library, as part of the npm install phase.

Create a file named sharp.mjs containing:

import sharp from 'sharp';

const data = await sharp(process.argv[2]).metadata();

Sharp is a modern package which uses an expressive API. This means the sharp function returns an object with API methods that support chaining operations together. All we're interested in at the moment is the metadata, which we get using this method.

This looks simple enough, but...

$ node sharp.mjs /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG 
  format: 'jpeg',
  width: 4032,
  height: 3024,
  space: 'srgb',
  channels: 3,
  depth: 'uchar',
  density: 314,
  chromaSubsampling: '4:2:0',
  isProgressive: false,
  hasProfile: false,
  hasAlpha: false,
  exif: <Buffer 45 78 69 66 00 00 49 49 2a 00 08 00 00 00 0d 00 0e 01 02 00 20 00 00 00 aa 00 00 00 0f 01 02 00 18 00 00 00 ca 00 00 00 10 01 02 00 11 00 00 00 e2 00 ... 18362 more bytes>,
  iptc: <Buffer 50 68 6f 74 6f 73 68 6f 70 20 33 2e 30 00 38 42 49 4d 04 04 00 00 00 00 00 21 1c 01 00 00 02 00 04 1c 01 5a 00 03 1b 25 47 1c 02 00 00 02 00 04 1c 02 ... 38 more bytes>,
  xmp: <Buffer 3c 3f 78 70 61 63 6b 65 74 20 62 65 67 69 6e 3d 22 ef bb bf 22 20 69 64 3d 22 57 35 4d 30 4d 70 43 65 68 69 48 7a 72 65 53 7a 4e 54 63 7a 6b 63 39 64 ... 553 more bytes>

Turns out the exif, iptc, and xmp fields contain the raw data, rather than parsed data. Hurm. It's an opaque object from which we cannot access the fields. This is not the hoped-for result, nor does it fit with other features in Sharp.

In other words, Sharp also includes a method for setting metadata fields when creating a new image. Using the examples in the Sharp documentation let's write another script, sharp-add-copyright.mjs:

import sharp from 'sharp';

const data = await sharp(process.argv[2])
        exif: {
            IFD0: {
                Copyright: process.argv[3]

This example inputs an image to a Sharp processing stream, adds Copyright data to the EXIF, and outputs the result to a new file. By the way, this is the typical pattern of Sharp usage, that you construct a processing pipeline from an input to an output.

We can run this example as so:

$ node sharp-add-copyright.mjs /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG \
        'David Herron' \

I shot this picture, so therefore I deserve the copyright. Following that run the exif command on both the input file, and the output file, and you'll see the output file now has this listed:

Copyright           |David Herron (Photographer) - [None] (Editor)

It's excellent that we can modify EXIF settings using Sharp. But, wouldn't it make sense for Sharp to be capable of showing the structure of EXIF, IPTC and XMP tags? Why can't it do this?

Setting EXIF/IPTC/XMP metadata values

This leads us to a common task, which is not just to read the metadata values, but to modify them. We've demonstrated several methods of reading metadata values in Node.js applications, which we could use to build an index of images based on metadata. But, to make a useful index, we need to set metadata values. And before publishing an image on the Internet shouldn't we attach our copyright notice in the metadata?

Let's start with the command-line tools. In the ImageMagick suite it seems there must be an incantation using the convert or mogrify command for setting EXIF values. But I wasn't able to work this out. Instead, I found the exiftool command to be more straightforward.

$ exiftool -copyright='David Herron' -artist='David Herron' \
$ exif /Volumes/david/media/2013-07-02-BC-BC/P7050342.JPG
Artist              |David Herron
Copyright           |David Herron (Photographer) - [None] (Editor)

The most effective way to drive this from a Node.js script would be using the shelljs package. Shelljs helps you write what would have normally been written as shell scripts, but the scripts are more portable letting them run unchanged on Windows (for example).

After some experimenting, I ended up with the following script:

import { default as shell } from 'shelljs';

const artist = process.argv[2];
const fn = process.argv[3];

shell.exec(`exiftool -copyright -artist ${fn}`);

shell.exec(`exiftool -copyright='${artist}' -artist='${artist}' ${fn}`);

shell.exec(`exiftool -copyright -artist ${fn}`);

This script demonstrates the change, and is therefore more verbose than one would typically do.

$ node shelljs-add-exif.mjs 'David Herron' \
Copyright                       : 
Artist                          : 
    1 image files updated
Copyright                       : David Herron
Artist                          : David Herron

A look over the ( shelljs documentation should give you all kinds of ideas of things to do.

Notice that the script is printing the output from each command. This can be changed by customizing this:

shell.exec(`exiftool -copyright='${artist}' -artist='${artist}' ${fn}`, 
    (code, stdout, stderr) => {
        // code is the process exit code
        // stdout is the text from the standard output
        // stderr is the text from the standard error output

For example, this script might be improved by using the shell.cp command to copy the file before modifying it. Another idea is using a GUI application for initial screening, setting EXIF tags to mark the disposition of each image, then a script query which files have certain EXIF/XMP/etc tags set, and perform a given action.

A similar tool is zx ( (

There is a wealth of details about image metadata at ( .. While the exiftool command is very powerful, it leaves a lot to be desired in clarity and ease-of-use. The project page has this to say:

If you feel the need to use "find" or "awk" in conjunction with ExifTool, then you probably haven't discovered the full power of ExifTool.

I see this differently. That people turn to other tools in conjunction with ExifTool expresses a desire for a different user interface. Must "insanely great tools" always have "a long learning curve"? I believe it is important to make user interfaces or API's that are understandable by mere mortals.

Another tool, Exiv2 ( (, is roughly in the same ballpark as ExifTool. It supports the same metadata types as ExifTool, and its website also has a wealth of information about image metadata. Because it is implemented in C++, it is easier to incorporate into other applications. Its command-line tool is easier to understand. For example:

$ exiv2 -M"reg myprefix" \
        -M"add Xmp.myprefix.Whom Mr. Smith" \
        -M"set Exif.Image.Artist Mr. Smith" \

This demonstrates setting multiple metadata values, using a rather easily understandable syntax. The reg command is required so that the XMP support knows how to record Xmp.myprefix.

$ exiv2 -p a  /Volumes/david/media/2013-07-02-BC-BC/P7050341.JPG 

This prints all metadata.

$ exiv2 -g Whom -g Artist  /Volumes/david/media/2013-07-02-BC-BC/P7050341.JPG
Exif.Image.Artist                            Ascii      13  David Herron
Xmp.myprefix.Whom                            XmpText    12  David Herron

The -g option is how we search the metadata for specific values.

There is a Node.js module for Exiv2, which we briefly discuss in the honorable mentions section.

Stripping image metadata

Another common task is to strip metadata to minimize any privacy issues. For example, your cell phone images typically have geolocation data. Post to Facebook a funny sign you find in the grocery store, and you're instantly informing anyone who checks the image the latitude/longitude where the picture was taken. That sort of information can then be used against you.

Using ImageMagick, we can do this:

$ convert /Volumes/david/media/2013-07-02-BC-BC/P7050340.JPG \
        -strip \
$ exif img-stripped.jpg 
Corrupt data
The data provided does not follow the specification.
ExifLoader: The data supplied does not seem to contain EXIF data.

The result is a perfectly fine JPEG, but with no EXIF/XMP/etc data.

Using Exiv2, the -d option is equivalent to the -strip command. Or, you can use -M del to target specific tags to delete.

A simple modification to the sharp-add-copyright.mjs script shown earlier could strip out specific metadata tags. For example, the tags showing the camera type or exposure do not constitute a privacy concern, whereas the geolocation tags do. Modifying that script to set those tags to empty values would do the trick.

Honorable mentions

Along the way of researching this article, I came across some promising tools which weren't quite useful enough.

The ( Exiv2 package for Node.js interfaces with the Exiv2 C++ library. The documentation shows a very nice API. But, it is only compatible with Node.js v0.8.x, and is incompatible with the current Node.js.

The ( @11ways/exiv2 package may be a fork which updates it for modern Node.js. In fact, it installs with Node.js 16.x on macOS, but prints a zillion or so warnings while doing so. The following program is successful at reading image metadata:

import { default as ex } from '@11ways/exiv2';

const tags = await new Promise((resolve, reject) => {
    ex.getImageTags(process.argv[2], function(err, tags) {
        if (err) reject(err);
        else resolve(tags);


Because the original API is 7 years old, it does not support Promises, etc. Presumably the @11ways team is working on this package, and they have a bunch of warnings to resolve if nothing else.

The ( Exifer package for Node.js looks promising. To read EXIF, IPTC or XMP tags requires using add-on packages. But in testing, it did not seem to function correctly.

The ( piexif package for Node.js looks promising. While the documentation suggests it can read metadata, and also insert metadata, it's not entirely clear what to do.


We've learned about a range of tools for reading and manipulating image metadata.

The state of Node.js support for this area is weak. While there are several packages with good support for reading image metadata, as soon as you want to manipulate (add, delete or change) the metadata you're left using other tools. But if our goal is delivering a Node.js application, or service, we're left wanting.

Our best choice from Node.js code is integrating with the Exiv2 command-line interface. An additional step to explore is updating its Node.js package to support modern Node.js versions.

Another step to explore is getting the Sharp package to deliver parsed EXIF/IPTC/XMP data. Sharp has the advantage of being a general purpose image manipulation library, and it can be used as-is for setting metadata values.

About the Author(s)

( David Herron : David Herron is a writer and software engineer focusing on the wise use of technology. He is especially interested in clean energy technologies like solar power, wind power, and electric cars. David worked for nearly 30 years in Silicon Valley on software ranging from electronic mail systems, to video streaming, to the Java programming language, and has published several books on Node.js programming and electric vehicles.

Books by David Herron