# exifreader

> Library that parses Exif metadata in images.

Latest version **4.45.0** (published 2026-09-10) · MPL-2.0 license · 0 weekly downloads

## Install

```sh
npm install exifreader
pnpm add exifreader
yarn add exifreader
bun add exifreader
```

Provides the command `exifreader`.

## Health

**Score 75/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 4.45.0 |
| Published | 2026-09-10 |
| First published | 2016-12-28 |
| Weekly downloads | 0 |
| License | MPL-2.0 |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 1.1 MB |
| Known vulnerabilities | 0 |
| Install scripts | yes |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 997 |
| Author | Mattias Wallander |
| Maintainers | mattiasw |

## Links

- npm: https://www.npmjs.com/package/exifreader
- Repository: https://github.com/mattiasw/ExifReader
- Homepage: https://github.com/mattiasw/ExifReader#readme
- Issues: https://github.com/mattiasw/ExifReader/issues
- npm.io page: https://npm.io/package/exifreader

## Recent versions

- 4.45.0 (latest) — 2026-09-10
- 4.44.1 — 2026-09-05
- 4.44.0 — 2026-08-21
- 4.43.0 — 2026-08-16
- 4.42.0 — 2026-08-05
- 4.41.4 — 2026-08-05
- 4.41.3 — 2026-07-18
- 4.41.2 — 2026-07-18
- 4.41.1 — 2026-07-18
- 4.41.0 — 2026-06-08
- 4.40.5 — 2026-06-03
- 4.40.4 — 2026-06-03
- 4.40.3 — 2026-05-31
- 4.40.2 — 2026-05-30
- 4.40.1 — 2026-05-30
- … 121 more at https://npm.io/package/exifreader/versions

## README

ExifReader
==========

ExifReader is a JavaScript library that parses image files and extracts the
metadata. It can also extract an embedded thumbnail. It can be used either in a
browser or from Node. Supports JPEG, JPEG XL, TIFF, PNG, HEIC, AVIF, WebP, and
GIF files with Exif, IPTC, XMP, ICC, MPF, and more metadata (depending on file
type).

If you use the `length: 'auto'` option, ExifReader reads only the bytes
that contain metadata, **well under 1 ms per file** for typical phone JPEGs
and **over 95% less bandwidth**.

ExifReader is highly and easily configurable and the resulting bundle can be as
small as **~9 KiB** (Brotli-compressed) if you're only interested in a few tags
(e.g. date and/or GPS values).

ExifReader supports module formats ESM, AMD, CommonJS, and globals and can
therefore easily be used from Webpack, RequireJS, Browserify, Node etc.

You can try it out on the
[examples site](https://mattiasw.github.io/ExifReader/).

**Support table**

| File type              | Exif    | IPTC    | XMP     | ICC     | MPF     | Photoshop     | MakerNote          | Thumbnail | Image details |
| -----------------------|---------|---------|---------|---------|---------|---------------|--------------------|-----------|---------------|
| JPEG                   | **yes** | **yes** | **yes** | **yes** | **yes** | **some**&ast; | **some**&ast;&ast; | **yes**   | **yes**       |
| JPEG XL&ast;&ast;&ast; | **yes** | no      | **yes** | no      | no      | no            | **some**&ast;&ast; | ???       | **yes**       |
| TIFF                   | **yes** | **yes** | **yes** | **yes** | ???     | **some**&ast; | **some**&ast;&ast; | **yes**   | N/A           |
| PNG                    | **yes** | **yes** | **yes** | **yes** | ???     | ???           | **some**&ast;&ast; | no        | **yes**       |
| HEIC/HEIF              | **yes** | no      | **yes** | **yes** | ???     | ???           | **some**&ast;&ast; | **yes**   | no            |
| AVIF                   | **yes** | no      | **yes** | **yes** | ???     | ???           | **some**&ast;&ast; | **yes**   | no            |
| WebP                   | **yes** | no      | **yes** | **yes** | ???     | ???           | **some**&ast;&ast; | **yes**   | **yes**       |
| GIF                    | N/A     | N/A     | N/A     | N/A     | N/A     | N/A           | N/A                | N/A       | **yes**       |

- `MakerNote` = Manufacturers' proprietary MakerNote tags.
- `Image details` = image width, height, etc. read from image header.
- `N/A` = The feature is not applicable to this file type.
- `???` = may be supported but has not been tested.
- `*` = A draft implementation of Photoshop tags have been added with
    `ClippingPathName` and `PathInformation` currently supported. Photoshop tags
    are very different from other tags and need a lot of extra code so they have
    deliberately not been fully implemented. File an issue if there is something
    you think should really be supported.
- `**` = Some of the Canon-specific and Pentax-specific tags have been added.
    File an issue if you think something more should be supported.
- `***` = Metadata in JPEG XL are often (but not always) encoded with Brotli
    compression. This is supported in some environments when using
    `async: true`. See the [Asynchronous tags](#asynchronous-tags) and
    [Custom decompression](#custom-decompression) sections for details.

If you're missing something that you think should be supported, file an issue
with an attached example image and I'll see what I can do.

**Notes for exif-js users**

If you come here from the popular but now dead exif-js package, please let me
know if you're missing anything from it and I will try to help you. Some notes:

-   ExifReader has a different API, hopefully better. :-)
-   XMP support in exif-js does not seem perfect. ExifReader should be a bit
    better on that part.
-   ExifReader works with strict mode.
-   I've been maintaining this package since 2012 and I have no plans to stop
    doing that anytime soon.

Table of Contents
-----------------

1. [Support](#support)
2. [Installation](#installation)
3. [Usage](#usage)
4. [Configure a Custom Build](#configure-a-custom-build)
5. [Notes](#notes)
6. [Client/Browser Support](#clientbrowser-support)
7. [Examples](#examples)
8. [Tips](#tips)
9. [Known Limitations](#known-limitations)
10. [Contributing](#contributing)
11. [Code of Conduct](#code-of-conduct)
12. [License](#license)
13. [Changelog](#changelog)

Support
-------

Monetary support is not necessary for me to continue working on this, but in
case you like this library and want to support its development you are very
welcome to click the button below. You can also use GitHub's sponsor feature on
the right-hand side on the repository's main page.

<a href="https://www.buymeacoffee.com/mattiasw" target="_blank">
    <img src="https://cdn.buymeacoffee.com/buttons/v2/default-violet.png" alt="Buy me a coffee" width="181" height="50">
</a>

Installation
------------

Easiest is through npm or Bower:

```bash
npm install exifreader --save
```

```bash
bower install exifreader --save
```

If you want to clone the git repository instead:

```bash
git clone git@github.com:mattiasw/ExifReader.git
cd ExifReader
npm install
```

After that, the transpiled, concatenated and minified ES5 file will be in the
`dist` folder together with a sourcemap file.

### Type definitions

Type definitions for TypeScript are included in the package.

Usage
-----

### Importing

*NOTE: See React Native instructions below.*

ES module syntax:

```javascript
import ExifReader from 'exifreader';
```

**NOTE:** TypeScript/Angular seems to sometimes have problems when using the
default export. If you're seeing issues, use this syntax instead:

```javascript
import * as ExifReader from 'exifreader';
```

CommonJS/Node modules:

```javascript
const ExifReader = require('exifreader');
```

AMD modules:

```javascript
requirejs(['/path/to/exif-reader.js'], function (ExifReader) {
    ...
});
```

`script` tag:

```html
<script src="/path/to/exif-reader.js"></script>
```

### Loading tags

There are two ways to load the tags. Either have ExifReader do the loading of
the image file, or load the file yourself first and pass in the file buffer. The
main difference is that the first one is asynchronous and the second one is
synchronous unless specified.

#### Let ExifReader load the file (asynchronous API)

```javascript
const tags = await ExifReader.load(file);
const imageDate = tags['DateTimeOriginal'].description;
const unprocessedTagValue = tags['DateTimeOriginal'].value;
```

Where `file` is one of

*  File object, the result of a form file upload (browser)
*  File path on a local file system (Node.js)
*  URL (browser or Node.js; remember that in a browser context the remote server
   has to set CORS headers that allow for remote loading of the file)

**Security:** When the argument is a string, ExifReader treats it as a URL or a
local file path and will make a network request or read from the file system.
Never pass an untrusted or user-controlled string to `load()`. Doing so lets an
attacker make the process fetch arbitrary URLs (server-side request forgery) or
open arbitrary local files. For untrusted image data, always pass the bytes
themselves (an `ArrayBuffer`, a `Buffer`, or a browser `File`), never a string.

**Tip:** To read metadata from an `HTMLImageElement`, wait for its `load` event
and call `ExifReader.load(image.src)`. The browser should reuse the cached file.

#### Load the file yourself (synchronous API)

```javascript
const tags = ExifReader.load(fileBuffer);
```

Where `fileBuffer` is one of

*  `ArrayBuffer` or `SharedArrayBuffer` (browser, Node.js)
*  `Buffer` (Node.js)

See the [examples site](https://mattiasw.github.io/ExifReader/) for more
directions on how to use the library.

**Security:** Treat all returned metadata as untrusted input. Tag values,
descriptions, and the raw XMP packet (`xmp._raw` when using `expanded: true`)
come straight from the image and can contain arbitrary text, including HTML-like
markup. Never insert them into a page as HTML (for example through `innerHTML`)
without escaping or sanitizing them first, otherwise a crafted image could lead
to cross-site scripting. Use `textContent`, your framework's escaping, or a
sanitizer such as DOMPurify when displaying metadata. The property names inside
a tag value come from the image too, and a tag whose name is `__proto__` is kept
under that name as a regular property, so it does not replace the prototype of
the object holding it. A value parsed from an XMP `rdf:value` element that holds
child elements other than a list is returned as an object without a prototype.
Such an object has no inherited methods, and copying it into an object of your
own with `Object.assign` or a deep merge helper without filtering the keys could
let a crafted image replace that object's prototype.

#### Grouping

By default, Exif, IPTC and XMP tags are grouped together. This means that if
e.g. `Orientation` exists in both Exif and XMP, the first value (Exif) will be
overwritten by the second (XMP). If you need to separate between these values,
pass in an options object with the property `expanded` set to `true`:

```javascript
const tags = ExifReader.load(fileBuffer, {expanded: true});
```

#### Filtering tags (includeTags / excludeTags)

You can filter which tags are returned by using `includeTags` and/or
`excludeTags`.

- If `includeTags` is provided, it uses an **include-pattern**: only the groups
  you specify will be included in the output.
- `excludeTags` can be used alone to remove tags/groups while keeping everything
  else.
- If a group is specified in both `includeTags` and `excludeTags`, `excludeTags`
  for that group will be ignored.
- An empty selector array (e.g. `includeTags: { xmp: [] }`) is treated as
  excluding that group. The group will not be returned and ExifReader will skip
  parsing it.

**Examples**

Exclude a few tags in the Exif group:

```javascript
const tags = ExifReader.load(fileBuffer, {
    excludeTags: {
        exif: ['MakerNote', 0x9286],
    }
});
```

Only return XMP tags (and exclude everything else):

```javascript
const tags = ExifReader.load(fileBuffer, {
    includeTags: {
        xmp: true,
    }
});
```

**Filtering groups**

The group keys follow the same concept as the output when `expanded: true` is
used. (The `thumbnail` group controls the top-level `Thumbnail` value.)

For PNG, filtering is done with the `png` group only. (The `pngFile` and
`pngText` groups exist in `expanded` output today, but `png` is the filtering
key and those groups will be deprecated later.)

| Group key     | Description                                      | Supports IDs |
| ------------- | ------------------------------------------------ | ------------ |
| `exif`        | Exif tags (including GPS IFD, interoperability). | yes          |
| `iptc`        | IPTC tags.                                       | yes          |
| `xmp`         | XMP tags.                                        | no           |
| `icc`         | ICC profile tags.                                | no           |
| `photoshop`   | Photoshop resource tags.                         | yes          |
| `makerNotes`  | MakerNote tags (e.g. Canon/Pentax).              | yes          |
| `mpf`         | MPF tags.                                        | yes          |
| `file`        | JPEG file details and `FileType`.                | no           |
| `jfif`        | JFIF tags.                                       | no           |
| `png`         | PNG header, chunk, and text tags.                | no           |
| `riff`        | WebP (RIFF) tags.                                | no           |
| `gif`         | GIF tags.                                        | no           |
| `gps`         | Computed GPS group (only when `expanded: true`). | no           |
| `composite`   | Composite tags (e.g. FieldOfView).               | no           |
| `thumbnail`   | Top-level `Thumbnail` output.                    | no           |

**Important: dependency tags**

Some tags act as pointers or containers to other metadata. Excluding them can
cause other tags or entire groups to disappear. Some important examples:

- `Exif IFD Pointer`, `GPS Info IFD Pointer`, `Interoperability IFD Pointer`
- `IPTC-NAA` (TIFF embedded IPTC), `ApplicationNotes` (TIFF embedded XMP),
  `ICC_Profile` (TIFF embedded ICC)
- `MakerNote` and `Make` (maker note parsing)
- `ImageSourceData` and `PhotoshopSettings` (Photoshop parsing)
- `JPEGInterchangeFormat` and `JPEGInterchangeFormatLength` (thumbnails)

**NOTE:** When using `includeTags`, you **do not** have to list these dependency
tags. ExifReader will automatically include the required pointer/container tags
needed to find and parse the groups you requested. This section mainly matters
when using `excludeTags`.

Note that XMP and ICC tags are parsed from full metadata blocks, so ExifReader
cannot skip parsing individual XMP/ICC tags based on selectors. The returned
output is still filtered.

#### Unknown tags

Tags that are unknown, either because they have been excluded by making a custom
build or they are yet to be added into ExifReader, are by default not included
in the output. If you need to see them there is an option that can be passed in:

```javascript
const tags = ExifReader.load(fileBuffer, {includeUnknown: true});
```

If you discover an unknown tag that should be handled by ExifReader, please
reach out by filing an issue.

#### Computed tag values (opt-in)

ExifReader exposes three different values per Exif tag:

- **`value`**: A stable, raw-ish value that mirrors how the tag is stored in the
  file.
- **`description`**: A human-friendly value meant for display.
- **`computed`**: An opt-in, type-aware value. This will often be the same as
  `value` except for RATIONAL/SRATIONAL and ASCII tags.

Enable the `computed` value by passing `computed: true` in the options:

```javascript
const tags = ExifReader.load(fileBuffer, {computed: true});

const make = tags['Make'].computed; // e.g. "Apple"
const xResolution = tags['XResolution'].computed; // e.g. 72
```

`computed` is only added for tags parsed from TIFF IFD structures (Exif/GPS/etc,
including maker notes, MPF, and the Thumbnail IFD). It is not currently added to
XMP/IPTC/ICC tags or PNG text tags.

The `computed` conversion rules are based on the TIFF tag type:

- **ASCII**: If `value` contains a single string, `computed` is that string.
  Otherwise `computed` is a string array.
- **RATIONAL / SRATIONAL**: `computed` is a number (or an array of numbers).
  Division by zero yields `null`.
- **All other types**: `computed` equals `value`.

Note that `computed` may evolve in minor versions as the feature matures. If you
need a value with the strongest stability guarantees, prefer `value`.

#### Read only part of file

If you only want to read part of the image file you can use the `length` option:

```javascript
const tags = await ExifReader.load(filename, {length: 128 * 1024});
```

This will load only the first 128 KiB of the file. This could be useful if you
know the metadata is located at the beginning of the file. Just be aware that
it's common for the metadata to be spread out over a larger area so please try
it out on your set of files to know if it's suitable for your situation.

One thing to look out for is ICC profiles, which are often large. If the profile
does not fit inside the part you read, no ICC tags are returned for it. Use
`length: 'auto'` (see below) if you need the ICC tags without picking a size
yourself.

Note that this option only works when ExifReader handles the loading of the
file. If e.g. an already loaded ArrayBuffer or Buffer is passed into ExifReader,
the whole file will already have been loaded into memory and it's too late.
More specifically the length option will work for 1. local files when running
through Node.js, 2. remote files when passing a URL, and 3. browser File objects
(e.g., from form file fields). For remote files accessed through a web browser,
make sure the remote server is either on the same origin (domain) as your script
or that the server is passing correct CORS headers, specifically allowing the
`Range` header.

#### Read only the metadata bytes (`length: 'auto'`)

When you want to download or store just enough of an image to extract its
metadata, without guessing a fixed `length`, pass `length: 'auto'`:

```javascript
const tags = await ExifReader.load(url, {
    length: 'auto',
    expanded: true,
    includeOffsets: true,
    excludeTags: {mpf: true},  // skip embedded preview; see note below
});

tags.metadataRange.end       // exact byte count needed for the metadata
tags.metadataRange.buffer    // bytes [0, end), sliced from what we read.
                             //   Same kind as the input (ArrayBuffer or
                             //   Node Buffer). Save this to disk to keep
                             //   only the metadata-bearing prefix.
tags.metadataRange.fetched   // bytes actually read. May be greater than
                             //   `end` because the loop reads in fixed-size
                             //   chunks and so may read a little past `end`.
tags.metadataRange.requests  // number of IO calls performed
```

ExifReader fetches an initial 128 KiB prefix and parses it. Only if the
metadata extends further does it issue an HTTP `Range` request (or a
follow-up filesystem read / `File.slice`) for exactly the additional bytes
needed. Most files finish in a single request. HEIC/AVIF files where the
Exif/XMP `iloc` entries point near the end of the file typically take two.

`length: 'auto'` is supported for the four IO inputs:

- URL via browser `fetch` (uses HTTP `Range`)
- URL via Node `http(s)` (uses HTTP `Range`)
- Local file path via Node `fs`
- Browser `File` object via `File.slice`

It is also accepted for in-memory inputs (`ArrayBuffer`, `Buffer`,
`SharedArrayBuffer`, `DataView`) as a convenience. There is no IO loop to
run in that case, but `metadataRange.buffer` is still attached as the
trimmed slice of what you passed in.

Requirements and caveats:

- `expanded: true` and `includeOffsets: true` are required. Passing
  `length: 'auto'` without them throws a clear error (at runtime, and also a
  type error in TypeScript).
- The return is always a `Promise` because the loop is asynchronous.
- Not supported for plain TIFF or bare JPEG XL codestreams. They have no
  leading metadata container, so the loop has no convergence signal and
  will reject with an error.
- If the loop fails to converge in 4 iterations (very rare, defensive
  bound for unknown-size HTTP responses or files whose parsers can't
  bootstrap from a small prefix), ExifReader reads the rest of the file
  and emits a single `console.warn`. Correctness is preserved. The
  bandwidth savings are forfeit.

**Why `excludeTags: {mpf: true}` is in the example.** Most modern phone
cameras (and many DSLRs) emit JPEGs with a Multi-Picture-Format sub-image
preview embedded a few MB into the file. Without `excludeTags: {mpf:
true}` ExifReader treats those sub-images as part of the metadata range,
so `metadataRange.end` lands near EOF and `length: 'auto'` ends up
reading most of the file anyway. On a corpus of about 1.5 GiB of phone
photos this single option took `length: 'auto'` from a ~17% slowdown vs
a regular full read to a **6× speedup with 98% of bytes saved**. The
regular `tags.exif`, `tags.xmp`, `tags.icc`, `tags.gps` and the rest are
unaffected. Drop the option only if you actually need the
`tags.mpf.Images` array.

#### Locating metadata in the file (`includeOffsets`)

Pass `includeOffsets: true` together with `expanded: true` to learn where in
the file the metadata sits. Useful for persisting only the metadata-bearing
prefix of an image rather than the whole file. The resulting slice is enough
for re-extracting metadata with ExifReader, not for opening the slice as an
image.

```javascript
const tags = ExifReader.load(fileBuffer, {expanded: true, includeOffsets: true});

tags.metadataRange.start    // lowest block start (informational, can be deep
                            //   into the file for HEIC/AVIF; do NOT use as a
                            //   slice boundary, always slice from 0)
tags.metadataRange.end      // exclusive end. Store bytes 0..end to keep all metadata
tags.metadataRange.complete // false when the input was truncated before all metadata
tags.metadataRange.blocks   // [{type, start, end}, ...] sorted by start
```

Offsets are byte indices into the data you passed in. `metadataRange` is
attached only when both `expanded: true` and `includeOffsets: true` are set,
so default and flat-mode output are unchanged.

Block `type` reuses the filtering-group vocabulary (`exif`, `iptc`, `xmp`,
`icc`, `mpf`, `jfif`, `file`, `png`, `riff`, `gif`), plus `mpfImage` for the
sub-images of an MPF Multi-Picture JPEG. The same type may appear more than
once (multi-chunk ICC, extended XMP), one entry per physical container.

Typical workflow:

```javascript
const headChunk = await fetchFirstNBytes(url, 256 * 1024);
const tags = ExifReader.load(headChunk, {expanded: true, includeOffsets: true});

if (!tags.metadataRange.complete) {
    // 256 KiB was not enough, fetch more
} else {
    const minimalSlice = headChunk.slice(0, tags.metadataRange.end);
    await uploadMetadata(minimalSlice);
}
```

Notes and limitations:

- Not supported for plain TIFF or bare JPEG XL codestreams (no leading
  metadata container). `metadataRange` is omitted in both cases.
- For JPEG XL containers, blocks cover the `Exif` and `xml ` boxes but not
  the `jxlc`/`jxlp` codestream box (encoded image data, not metadata).
- `complete: true` means ExifReader did not detect truncation. For JPEG and
  PNG it observed `SOS` or `IEND`, for standalone XMP it observed an
  `<?xpacket end=?>` or `</x:xmpmeta>` token. For other formats it is
  best-effort, a buffer ending cleanly between metadata segments can still
  report `complete: true`. Non-standard trailers (JPEG-XT, post-SOS markers)
  are never detected. Note the converse: re-parsing a slice produced by
  `buffer.slice(0, metadataRange.end)` may report `complete: false` for
  JPEG/PNG even though every metadata segment is intact, because the
  slice ends before `SOS`/`IEND`. The block list and `tags.exif` etc. are
  still complete in that case.
- Only the metadata segments ExifReader actively parses are present in
  `blocks`. JPEG segments ExifReader does not currently extract tags from
  (APP3-APP12, `COM` comments, JPEG-XT extensions) do not appear and are
  not guaranteed to be preserved by a `0..end` slice. The slice is
  enough to re-extract everything ExifReader itself reads, not to
  preserve every byte a third-party tool might care about.
- For HEIC/AVIF, metadata can sit near the end of the file, so
  `metadataRange.end` often lands close to the file size and trimming
  saves little. The `icc` block here covers just the ICC profile bytes;
  for JPEG/PNG/WebP it covers the full container wrapper.
- For MPF JPEGs, sub-images appear as `mpfImage` blocks and push `end`
  toward the file size. If MPF parsing is suppressed by a tag filter (any
  `excludeTags: {mpf: ...}` or `includeTags` that does not include `mpf`),
  the `mpfImage` blocks are not emitted and `metadataRange.end` shrinks.
  The parent `mpf` segment block always remains, since it is a real
  metadata segment found during the header scan.
- Offsets come from length fields in the file. Treat `metadataRange.end`
  and `block.end` as untrusted input. Clamp to your buffer length before
  slicing, allocating, or issuing remote range requests.

#### Asynchronous tags

Some tags need to be parsed asynchronously. Currently this is the case for some
PNG tags (compressed tags in zTXt, iTXt, and iCCP chunks) and JPEG XL files with
Brotli-compressed metadata (very common). To enable this, either use the
asynchronous API mentioned above or pass in `async: true` in the options
parameter:

```javascript
const tags = await ExifReader.load(file);
// or
const tags = await ExifReader.load(fileBuffer, {async: true});
```

For the compressed PNG tags to work, the environment needs to support the
[Compression Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API#browser_compatibility).
Most modern browsers support deflate decompression through this API.

For JPEG XL Brotli decompression, Firefox, Safari, and Node.js have built-in
support through the Compression Streams API. Chrome does not currently support
Brotli in its Compression Streams API (April 2026). For environments without
built-in Brotli support, you can provide a custom decompression function via the
`decompress` option (see below).

The reason for having an option to enable asynchronous parsing is to not break
backwards compatibility. This will probably be the default in the next major
version.

#### Custom decompression

You can provide custom decompression functions for Brotli and/or deflate via the
`decompress` option. This is useful for JPEG XL files with Brotli-compressed
metadata in environments that don't have built-in Brotli support (e.g. Chrome),
or to override the built-in deflate decompression for PNG files. The custom
functions take a `Uint8Array` of compressed data and should return a
`Uint8Array` or `ArrayBuffer` with the decompressed data (or a `Promise` that
resolves to one).

```javascript
const tags = await ExifReader.load(file, {
    async: true,
    decompress: {
        brotli: async (compressedData) => myBrotliDecompress(compressedData),
    }
});
```

If a custom function is provided for a compression type, it takes priority over
the built-in Compression Streams API. If not provided, the library falls back to
the Compression Streams API when available.

A lightweight browser-compatible Brotli decompression library is
[brotli-dec-wasm](https://www.npmjs.com/package/brotli-dec-wasm), though it is
not widely used. A more popular alternative is
[brotli-wasm](https://www.npmjs.com/package/brotli-wasm), but it&apos;s also
larger:

```javascript
import brotliPromise from 'brotli-wasm';
const brotli = await brotliPromise;

const tags = await ExifReader.load(file, {
    async: true,
    decompress: {
        brotli: async (data) => brotli.decompress(data),
    }
});
```

#### Limiting decompressed metadata size

To avoid excessive memory use from pathological compressed inputs, ExifReader
caps the size of any single decompressed metadata block. The default limit is
128 MiB, which is well above any realistic legitimate value. You can override
it via the `maxDecompressedSize` field on the `decompress` option (in bytes):

```javascript
const tags = await ExifReader.load(file, {
    async: true,
    decompress: {
        maxDecompressedSize: 16 * 1024 * 1024 // 16 MiB
    }
});
```

If a compressed block would expand beyond the limit, that block is skipped, a
warning is logged via `console.warn`, and the rest of the tags are returned as
usual. The limit applies to the built-in Compression Streams paths and to any
result returned by a custom `brotli`/`deflate` function.

#### Parsing XMP tags when not in a DOM environment

When using for example Node.js or a web worker, there is no native
[`DOMParser`](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser)
available for parsing the XML used in XMP. In this case – and if XMP support is
important to you – you can pass in a third-party parser to ExifReader. The
parser needs to have a `parseFromString` method with the same API as the
[`parseFromString` from the DOMParser Web
API](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString).

Two libraries have been tested,
[`xmldom`](https://www.npmjs.com/package/@xmldom/xmldom) and
[`linkedom`](https://www.npmjs.com/package/linkedom), but more might work if
they follow the spec.

Here is an example using `xmldom`:

```javascript
import {DOMParser, onErrorStopParsing} from '@xmldom/xmldom';
// ...
const tags = ExifReader.load(fileBuffer, {domParser: new DOMParser({onError: onErrorStopParsing})});
```

The `onError` option is needed to avoid a [seemingly infinite loop for some
XMLs](https://github.com/xmldom/xmldom/issues/501). Unfortunately `linkedom` has
the same problem but does not have this option and will therefore get stuck on
these XMLs. If this happens to you, switch to `xmldom` and pass in the `onError`
option. (NOTE: The native `DOMParser` that a web browser uses does not seem to
have this issue.)

#### Using React Native

Import ExifReader like this:

```javascript
import ExifReader from './node_modules/exifreader/src/exif-reader.js';
```

Make sure to update the path to point to where your `node_modules` is located.

For local files on the device you need to load the file yourself first, then
pass in the buffer to ExifReader. Here is a template from user @hungdev:

```javascript
import RNFS from 'react-native-fs';
import {decode} from 'base64-arraybuffer';
import ExifReader from 'exifreader';

const b64Buffer = await RNFS.readFile('YOUR IMAGE URI', 'base64') // Where the URI looks like this: "file:///path/to/image/IMG_0123.HEIC"
const fileBuffer = decode(b64Buffer)
const tags = ExifReader.load(fileBuffer, {expanded: true});
```

If you're having trouble getting the GPS location, see [this comment and
thread](https://github.com/mattiasw/ExifReader/issues/177#issuecomment-1172228225)
and the [GPS section below](#gps) for more details.

### GPS

If `expanded: true` is specified in the options, there will be a `gps` group.
This group currently contains `Latitude`, `Longitude`, and `Altitude` which will
be negative for values that are south of the equator, west of the IRM, or below
sealevel. These are often more convenient values for regular use. For some
elaboration or if you need the original values, see [Notes](#notes) below.

If you're having trouble getting the GPS values on Android (and possibly also
IOS) and are using a `type="file"` input to upload the image, make sure you are
**not** setting `accept="image/*"` on the input element. Apparently
setting the `accept` attribute to this value [will strip the GPS
values](https://github.com/mattiasw/ExifReader/issues/378#issuecomment-2302705983).

### Using the thumbnail

The thumbnail and its details will be accessible through `tags['Thumbnail']`.
There is information about e.g. width and height, and the thumbnail image data
is stored in `tags['Thumbnail'].image`.

How you use it is going to depend on your environment. For a web browser you can
either use the raw byte data in `tags['Thumbnail'].image` and use it the way you
want, or you can use the helper property `tags['Thumbnail'].base64` that is a
base64 representation of the image. It can be used for a data URI like this:

```javascript
const tags = ExifReader.load(fileBuffer);
imageElement.src = 'data:image/jpg;base64,' + tags['Thumbnail'].base64;
```

If you're using node, you can store it as a new file like this:

```javascript
const fs = require('fs');
const tags = ExifReader.load(fileBuffer);
fs.writeFileSync('/path/to/new/thumbnail.jpg', Buffer.from(tags['Thumbnail'].image));
```

See the [examples site](https://mattiasw.github.io/ExifReader/) for more
details.

### Optimizing build size

The most important step will be to [use a custom
build](#configure-a-custom-build) so please do that.

If you are using Webpack 4 or lower and are only targeting web browsers, make
sure to add this to your Webpack config (probably the `webpack.config.js` file):

```javascript
    node: {
        Buffer: false
    }
```

`Buffer` is only used in Node.js but if Webpack sees a reference to it it will
include a `Buffer` shim for browsers. This configuration will stop Webpack from
doing that. Webpack 5 does this automatically.

Configure a Custom Build
------------------------

Configuring a custom build can reduce the bundle size significantly.

**NOTE 1:** This functionality is in beta but should work fine. Please file an
issue if you're having problems or ideas on how to make it better.

**NOTE 2:** This only changes the built file (`exifreader/dist/exif-reader.js`),
not the source code. That means it's not possible to use the ES module (from the
`src` folder) or any tree shaking to get the benefit of a custom build. Tree
shaking will actually have close to no effect at all here so don't rely on it.

This is for npm and yarn users that use the built file. To specify what
functionality you want you can either use include pattern (start with an empty
set and include) or exclude pattern (start with full functionality and exclude).
If an include pattern is set, excludes will not be used.

For Exif and IPTC it's also possible to specify which tags you're interested in.
Those tag groups have huge dictionaries of tags and you may not be interested in
all of them. (Note that it's not possible to specify tags to exclude.)

The configuration is added to your project's `package.json` file.

**Example 1:** Only include JPEG files and Exif tags (this makes the bundle
almost half the size of the full one (non-gzipped)):

```javascript
"exifreader": {
    "include": {
        "jpeg": true,
        "exif": true
    }
}
```

**Example 2:** Only include TIFF files, and the Exif `DateTime` tag and the GPS
tags (resulting bundle will be ~19 % of a gzipped full build):

```javascript
"exifreader": {
    "include": {
        "tiff": true,
        "exif": [
            "DateTime",
            "GPSLatitude",
            "GPSLatitudeRef",
            "GPSLongitude",
            "GPSLongitudeRef",
            "GPSAltitude",
            "GPSAltitudeRef"
        ]
    }
}
```

**Example 3:** Exclude XMP tags:

```javascript
"exifreader": {
    "exclude": {
        "xmp": true
    }
}
```

Then build the custom bundle. If you haven't installed ExifReader yet:

```bash
npm install exifreader
npx exifreader build
```

If it is already installed, or you just changed the configuration, run:

```bash
npx exifreader build
```

This reads the `exifreader` configuration from your `package.json`, rebuilds the
library, and writes the result to `node_modules/exifreader/dist/exif-reader.js`.
It works the same way with npm, yarn, and pnpm.

**Re-run after every install.** A fresh `npm install`, `npm ci`, or upgrade
restores the full bundle, so run `npx exifreader build` again afterwards. The
most reliable approach is to wire it into your own build step, for example (in
your own `package.json`):

```json
"scripts": {
    "prebuild": "exifreader build"
}
```

**Deprecated: automatic rebuild on install.** Older versions rebuilt the library
automatically through an npm `postinstall` script, so a plain install or `npm
rebuild exifreader` (or `yarn rebuild exifreader` with yarn 2+, or `yarn add
exifreader` with yarn 1) produced the custom bundle. This still works in v4 but
is deprecated and will be removed in v5, because npm is phasing out automatic
install scripts. Use `npx exifreader build` instead, which also avoids the yarn
2+ `node_modules` and `nodeLinker` caveats.

If you are using `vite`, you will need to [clear the dependency cache](https://vitejs.dev/guide/dep-pre-bundling.html#file-system-cache)
after a rebuild.

**Advanced and monorepos.** Instead of the `package.json` configuration you can
pass the same `include`/`exclude` object as JSON in the `EXIFREADER_CUSTOM_BUILD`
environment variable, which takes priority over `package.json`. In a monorepo,
`npx exifreader build` reads the configuration from the package you run it in,
but a hoisted `node_modules/exifreader` is shared by every workspace package, so
there is only one custom bundle and the last build wins.

If you're using the include pattern config, remember to include everything you
want to use. If you want `xmp` and don't specify any file types, you will get
"Invalid image format", and if you specify `jpeg` but don't mention any tag
types no tags will be found.

Possible modules to include or exclude:

| Module        | Description                                            |
| ------------- | -------------------------------------------------------|
| `jpeg`        | JPEG images.                                           |
| `tiff`        | TIFF images.                                           |
| `png`         | PNG images.                                            |
| `heic`        | HEIC/HEIF images.                                      |
| `avif`        | AVIF images.                                           |
| `webp`        | WebP images.                                           |
| `jxl`         | JPEG XL images.                                        |
| `gif`         | GIF images.                                            |
| `file`        | JPEG file details: image width, height etc.            |
| `jfif`        | JFIF details in JPEG files: resolution, thumbnail etc. |
| `png_file`    | PNG file details: image width, height etc.             |
| `exif`        | Regular Exif tags. If excluded, will also exclude `photoshop`, `maker_notes`, and `thumbnail`. For TIFF files, excluding this will also exclude IPTC, XMP, and ICC. |
| `iptc`        | IPTC tags.                                             |
| `xmp`         | XMP tags.                                              |
| `icc`         | ICC color profile tags.                                |
| `mpf`         | Multi-picture Format tags. Does not need `exif`.       |
| `photoshop`   | Photoshop tags. Needs `exif`.                          |
| `maker_notes` | Proprietary camera maker tags. Needs `exif`.           |
| `thumbnail`   | Thumbnail image. Needs `exif`.                         |

Notes
-----

-   In Exif data, the full GPS information is split into two different tags for
    each direction: the coordinate value (`GPSLatitude`, `GPSLongitude`) and the
    reference value (`GPSLatitudeRef`, `GPSLongitudeRef`). Use the references to
    know whether the coordinate is north/south and east/west. Often you will see
    north and east represented as positive values, and south and west
    represented as negative values (e.g. in Google Maps). This setup is also
    used for the altitude using `GPSAltitude` and `GPSAltitudeRef` where the
    latter specifies if it's above sea level (positive) or below sea level
    (negative). If you don't want to calculate the final values yourself, see
    [the section on GPS](#gps) for pre-calculated ones.
-   Some XMP tags have processed values as descriptions. That means that e.g. an
    `Orientation` value of `3` will have `Rotate 180` in the `description`
    property. If you would like more XMP tags to have a processed description,
    please file an issue or create a pull request.
-   Some text tags use TextDecoder to decode their content. If your specific
    environment does not support it at all or a specific encoding, you will not
    be able to see the decoded value. One example is when [Node.js wasn't
    compiled with support for the specific encoding](https://nodejs.org/api/util.html#util_whatwg_supported_encodings).
-   The `description` property of tags can change in a minor update. If you
    want to process a tag's value somehow, use the `value` property to be sure
    nothing breaks between updates.
-   Some of the composite tags may have values that are very off if e.g. the
    image has been resized.

Client/Browser Support
----------------------

The library makes use of the DataView API which is supported in Chrome 9+,
Firefox 15+, Internet Explorer 10+, Edge, Safari 5.1+, Opera 12.1+. For Node.js
at least version 10 is required if you want to parse XMP tags, otherwise earlier
versions will also work.

Examples
--------

Full HTML example pages and a Node.js example are located on the
[examples site](https://mattiasw.github.io/ExifReader/).

Tips
----

-   After parsing the tags, consider deleting the MakerNote tag if you know you
    will load a lot of files and storing the tags. It can be really large for
    some manufacturers. See the
    [examples site](https://mattiasw.github.io/ExifReader/) to see how you can
    do that.
-   In some cases it can make sense to only load the beginning of the image
    file since that is where the metadata is located. It's unfortunately not
    possible to know how big the metadata will be in an image, but if you limit
    yourself to regular Exif tags you can most probably get by with only reading
    the first 128 kB. This may exclude IPTC and XMP metadata though (and
    possibly Exif too if they come in an irregular order) so please check if
    this optimization fits your use case. Use the `length` option to only read
    the beginning of the file. See above for more details on that.
-   Excluding MPF (Multi-Picture Format) makes a big difference for phone
    photos. Modern phone JPEGs embed a sub-image preview a few MB into the
    file. If you do not need it, pass `excludeTags: {mpf: true}`. This is
    especially impactful when combined with `length: 'auto'` (multi-x
    speedup, see that section), but it also lets a numeric `length`
    boundary land well before the sub-image. Regular Exif/XMP/ICC tags are
    unaffected.

Known Limitations
-----------------

-   The descriptions for UserComment, GPSProcessingMethod and GPSAreaInformation
    are missing for other encodings than ASCII.
-   For Canon maker notes, `makerNotes.LensType` is currently returned as a raw
    numeric code (for example `48`). If you need a human-readable lens name, see
    ExifTool's Canon LensType mapping list:
    <https://exiftool.org/TagNames/Canon.html#LensType>.

Contributing
------------

Questions, bug reports, suggestions, and pull requests are very much welcome. If
you've been using another Exif package, you probably have some good insights on
what's missing in this one. See [CONTRIBUTING.md](CONTRIBUTING.md) for more
info.

Code of Conduct
---------------

This project is released with a
[Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this
project you agree to abide by its terms.

License
-------

ExifReader uses the Mozilla Public License 2.0 (MPL-2.0). In short that means
you can use this library in your project (open- or closed-source) as long as you
mention the use of ExifReader and make any changes to ExifReader code available
if you would to distribute your project. But please read the
[full license text](https://mozilla.org/MPL/2.0/) to make sure your specific
case is covered.

---
_Source: https://npm.io/package/exifreader · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
