
Company News
Socket Named Top Sales Organization by RepVue
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.
@sitemark/exifr
Advanced tools
Fork from: https://github.com/MikeKovarik/exifr/commit/bf9fc270b00a87347fd691f7a7df14c3263a8328
@sitemark/exifr is published on npm.
We use Changesets to manage versioning and changelogs. The release process is mostly automated. Here’s how it works:
When making changes that require a version bump, run the following command to create a changeset:
npx @changesets/cli
Follow the prompts to describe your changes and select the appropriate version bump (major, minor, or patch).
Commit the generated changeset file:
git add .changeset/
git commit -m "Add changeset for <description>"
After your PR is merged, the Changesets bot will automatically create a new PR containing the version bump and changelog updates. Review and merge this PR.
Once the version bump PR is merged, the bot will automatically publish the new version to GitHub Packages and create a corresponding release on GitHub.
Works everywhere and accepts pretty much everything you throw at it.
ArrayBuffer, Uint8Array, DataView, <img> elements, string URL and paths, Object URL, Base64 URLimport, require() or <script> it in your .mjs, .js or .html file.ESM in Node.js
import * as exifr from '@sitemark/exifr'
// exifr handles disk reading. Only reads a few hundred bytes.
exifr.parse('./myimage.jpg')
.then(exif => console.log('Camera:', exif.Make, exif.Model))
.catch(console.error)
CJS in Node.js
let exifr = require('exifr')
let fs = require('fs').promises
// Or read the file on your own and feed the buffer into exifr.
fs.readFile('./myimage.jpg')
.then(exifr.parse)
.then(exif => console.log('lat lon', exif.latitude, exif.longitude))
.catch(console.error)
UMD in Browser
<img src="./myimage.jpg">
<script src="./node_modules/exifr/index.js"></script>
<script>
// UMD module exposed on global window.exifr object
exifr.parse(document.querySelector('img'))
.then(exif => console.log('Exposure:', exif.ExposureTime))
</script>
ESM in Browser
<input id="filepicker" type="file" multiple>
<script type="module">
import {parse} from './node_modules/exifr/index.js'
document.querySelector('#filepicker').addEventListener('change', async e => {
let files = Array.from(e.target.files)
let promises = files.map(parse)
let exifs = await Promise.all(promises)
let dates = exifs.map(exif => exif.DateTimeOriginal.toGMTString())
console.log(`${files.length} photos taken on:`, dates)
})
</script>
Extracting thumbnail
let img = document.querySelector("#thumb")
document.querySelector('input[type="file"]').addEventListener('change', async e => {
let file = e.target.files[0]
img.src = await exifr.thumbnailUrl(file)
})
let thumbBuffer = await exifr.thumbnailBuffer(imageBuffer)
Usage in WebWorker
let worker = new Worker('./worker.js')
worker.postMessage('../test/IMG_20180725_163423.jpg')
worker.onmessage = e => console.log(e.data)
// worker.js
importScripts('./node_modules/exifr/index.js')
let exifr = self.exifr // UMD
self.onmessage = async e => postMessage(await exifr.parse(e.data))
exifr exports parse, thumbnailBuffer, thumbnailUrl functions and ExifParser class
parse(input[, options]) => Promise<object>Accepts any input argument, parses it and returns exif object.
thumbnailBuffer(input) => Promise<Buffer|ArrayBuffer>Extracts embedded thumbnail from the photo and returns it as a Buffer (Node.JS) or an ArrayBuffer (browser).
Only parses as little EXIF as necessary to find offset of the thumbnail.
thumbnailUrl(input) => Promise<string>Browser only - exports the thumbnail wrapped in Object URL.
User is expected to revoke the URL when not needed anymore.
ExifParser classAfore mentioned functions are wrappers that internally instantiate new ExifParse(options) class, then call parser.read(input), and finally call either parser.parse() or parser.extractThumbnail().
To do both parsing EXIF and extracting thumbnail efficiently you can use this class yourself.
let parser = new ExifParser(options)
let exif = await parser.read(input)
let thumb = await parser.extractThumbnail()
inputcan be:
string file pathstring URLstring Base64string Object URL / Blob URLBufferArrayBufferUint8ArrayDataViewFileBlob<img> elementoptionsis optional argument and can be either:
object with granular settingsboolean shortcut to enable parsing all segments and blocksIn browser it's sometimes better to fetch a larger chunk in hope that it contains the whole EXIF (and not just its beginning like in case of options.seekChunkSize) in prevention of additional loading and fetching. options.parseChunkSize sets that number of bytes to download at once. Node.js only relies on the options.seekChunkSize.
If you're not concerned about performance and time (mostly in Node.js) you can tell exifr to just read the whole file into memory at once.`
options.wholeFile bool/undefined default undefined
Sets whether to read the file as a whole or just by small chunks.
Used when file path or url to the image is given.
true - whole file mode
undefined - chunked mode, default value
seekChunkSize) and allows reading/fetching additional chunks.
false - chunked mode
parseChunkSize) in hopes that the EXIF isn't larger then the chunk.
options.seekChunkSize number default: 512 Bytes (0.5 KB)
Byte size of the first chunk that will be read and parsed for EXIF.
EXIF is usually within the first few bytes of the file. If not than there likely is no EXIF. It's not necessary to read through the whole file.
Node.js: Used for all input types.
Browser: Used when input arg is buffer. Otherwise, parseChunkSize is used.
options.parseChunkSize number default: 64 * 1024 (64KB)
Size of the chunk to fetch in the browser in chunked mode.
Much like seekChunkSize but used in the browser (and only if we're given URL) where subsequent chunk fetching is more expensive than fetching one larger chunk with hope that it contains the EXIF.
Node.js: Not used.
Browser: Used when input arg is string URL. Otherwise, seekChunkSize is used.
If parsing file known to have EXIF fails try:
seekChunkSizeparseChunkSize in the browser if file URL is used as input.options.tiff: true - APP1 - TIFF
options.exif: true - Sub Exif.options.gps: true - GPS latitue and longitude data.options.thumbnail: false - Size and other information about embedded thumbnail.options.interop: false - This is a thing too.options.xmp: false - APP1 - XMP
options.icc: false - APP2 - ICCoptions.iptc: false - APP13 - IPTC
options.postProcess number default: true
Translate enum values to strings, convert dates to Date instances, etc...
options.mergeOutput number default: true
Changes output format by merging all segments and blocks into a single object.
As you've already read, this lib was built to be fast. Fast enough to handle whole galleries.
We're able to parse image within a couple of milliseconds (tens of millis on phones) thanks to selective disk reads (Node.js) and Blob / ArrayBuffer (Browser) manipulations. Because you don't need to read the whole file and parse through a MBs of data if we an educated guess can be made to only read a couple of small chunks where EXIF usually is. Plus each supported data type is approached differently to ensure the best performance.
Observations from testing with +-4MB pictures (Highest quality, highest resolution Google Pixel photos, tested on a decade old quad core CPU). Note: These are no scientific measurements.
The library is already production ready and battle-tested, but there's always room for improvement
Probably as an additional opt-in extension file to keep the core as light as possible.
MIT, Mike KovaĹ™Ăk, Mutiny.cz
FAQs
đź“‘ The fastest and most versatile JavaScript EXIF reading library.
We found that @sitemark/exifr demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.

Security News
NIST will stop enriching most CVEs under a new risk-based model, narrowing the NVD's scope as vulnerability submissions continue to surge.

Company News
/Security News
Socket is an initial recipient of OpenAI's Cybersecurity Grant Program, which commits $10M in API credits to defenders securing open source software.