Fast, cross-platform Node.js access to ExifTool. Built and supported by PhotoStructure.
Requirements: Node.js Active LTS or Maintenance LTS versions only
npm install exiftool-vendoredimport { exiftool } from "exiftool-vendored";
// Read metadata
const tags = await exiftool.read("photo.jpg");
console.log(`Camera: ${tags.Make} ${tags.Model}`);
console.log(`Taken: ${tags.DateTimeOriginal}`);
console.log(`Size: ${tags.ImageWidth}x${tags.ImageHeight}`);
// Write metadata
await exiftool.write("photo.jpg", {
XPComment: "Amazing sunset!",
Copyright: "Β© 2024 Your Name",
});
// Extract thumbnail
await exiftool.extractThumbnail("photo.jpg", "thumb.jpg");
await exiftool.end();Order of magnitude faster than other Node.js ExifTool modules. Powers PhotoStructure and 1,000+ other projects.
- Cross-platform: macOS, Linux, Windows
- Comprehensive: Read, write, extract embedded images
- Reliable: Battle-tested with extensive test coverage
- TypeScript: Full type definitions for thousands of metadata fields
- Smart dates: Timezone-aware
ExifDateTimeclasses - Auto-generated tags: Based on 10,000+ real camera samples
const tags = await exiftool.read("photo.jpg");
// Camera info
console.log(tags.Make, tags.Model, tags.LensModel);
// Capture settings
console.log(tags.ISO, tags.FNumber, tags.ExposureTime);
// Location (if available)
console.log(tags.GPSLatitude, tags.GPSLongitude);
// Always check for parsing errors
if (tags.errors?.length > 0) {
console.warn("Metadata warnings:", tags.errors);
}// Add keywords and copyright
await exiftool.write("photo.jpg", {
Keywords: ["sunset", "landscape"],
Copyright: "Β© 2024 Photographer Name",
"IPTC:CopyrightNotice": "Β© 2024 Photographer Name",
});
// Update all date fields at once
await exiftool.write("photo.jpg", {
AllDates: "2024:03:15 14:30:00",
});
// Delete tags
await exiftool.write("photo.jpg", {
UserComment: null,
});// Extract thumbnail
await exiftool.extractThumbnail("photo.jpg", "thumbnail.jpg");
// Extract preview (larger than thumbnail)
await exiftool.extractPreview("photo.jpg", "preview.jpg");
// Extract JPEG from RAW files
await exiftool.extractJpgFromRaw("photo.cr2", "processed.jpg");The Tags interface contains thousands of metadata fields from an auto-generated TypeScript file. Each tag includes semantic JSDoc annotations:
/**
* @frequency π₯ β
β
β
β
(85%)
* @groups EXIF, MakerNotes
* @example 100
*/
ISO?: number;
/**
* @frequency π§ β
β
β
β (23%)
* @groups MakerNotes
* @example "Custom lens data"
*/
LensSpec?: string;- π₯ = Found on mainstream devices (iPhone, Canon, Nikon, Sony)
- π§ = Only found on more obscure camera makes and models
- β β β β = Found in >50% of files, ββββ = rare (<1%)
- @groups = Metadata categories (EXIF, GPS, IPTC, XMP, etc.)
- @example = Representative values
- No fields are guaranteed to be present.
- Value types are not guaranteed -- assume strings may get in your numeric fields, and handle it gracefully.
- There may very well be keys returned that are not in the
Tagsinterface.
π Complete Tags Documentation β
exiftool-vendored provides two levels of configuration:
Library-wide Settings - Global configuration affecting all instances:
import { Settings } from "exiftool-vendored";
// Enable parsing of archaic timezone offsets for historical photos
Settings.allowArchaicTimezoneOffsets.value = true;Per-instance Options - Configuration for individual ExifTool instances:
import { ExifTool } from "exiftool-vendored";
const exiftool = new ExifTool({
maxProcs: 8, // More concurrent processes
useMWG: true, // Use Metadata Working Group tags
backfillTimezones: true, // Infer missing timezones
});π Complete Configuration Guide β
Images rarely specify timezones. This library uses sophisticated heuristics:
- Explicit metadata (TimeZoneOffset, OffsetTime)
- GPS location β timezone lookup
- UTC timestamps β calculate offset
const dt = tags.DateTimeOriginal;
if (dt instanceof ExifDateTime) {
console.log("Timezone offset:", dt.tzoffset, "minutes");
console.log("Timezone:", dt.zone);
}π Date & Timezone Guide β
Always call .end() on ExifTool instances to prevent Node.js from hanging:
import { exiftool } from "exiftool-vendored";
// Use the singleton
const tags = await exiftool.read("photo.jpg");
// Clean up when done
process.on("beforeExit", () => exiftool.end());For TypeScript 5.2+ projects, consider using automatic resource management:
import { ExifTool } from "exiftool-vendored";
// Automatic synchronous cleanup
{
using et = new ExifTool();
const tags = await et.read("photo.jpg");
// ExifTool automatically cleaned up when block exits
}
// Automatic asynchronous cleanup (recommended)
{
await using et = new ExifTool();
const tags = await et.read("photo.jpg");
// ExifTool gracefully cleaned up when block exits
}Benefits:
- Guaranteed cleanup: No leaked processes, even with exceptions
- Timeout protection: Automatic forceful cleanup if graceful shutdown hangs
- Zero boilerplate: No manual
.end()calls needed
Caution:
- Operating-system startup lag: Linux costs ~50-500ms to launch a new ExifTool process, but macOS can take several seconds (presumably due to Gatekeeper), and Windows can take tens of seconds due to antivirus shenanigans. Don't dispose your instance unless you're really done with it!
The Tags interface shows the most common fields, but ExifTool can extract many more. Cast to access unlisted fields:
const tags = await exiftool.read("photo.jpg");
const customField = (tags as any).UncommonTag;- Installation Guide - Electron, Docker, platform setup
- Usage Examples - Comprehensive API examples
- Date Handling - Timezone complexities explained
- Tags Reference - Understanding the 2,500+ metadata fields
- Electron Integration - Electron-specific setup
- Debugging Guide - Debug logging and common issues
- Temporal Migration - Future JavaScript Temporal API
- TypeDoc Documentation - Complete API reference
The default singleton is throttled for stability. For high-throughput processing:
import { ExifTool } from "exiftool-vendored";
const exiftool = new ExifTool({
maxProcs: 8, // More concurrent processes
minDelayBetweenSpawnMillis: 0, // Faster spawning
streamFlushMillis: 10, // Faster streaming
});
// Process many files efficiently
const results = await Promise.all(filePaths.map((file) => exiftool.read(file)));
await exiftool.end();Benchmarks: 20+ files/second/thread, 500+ files/second using all CPU cores.
- π Issues: GitHub Issues
- π Changelog: CHANGELOG.md
- π Security: SECURITY.md
- π License: MIT
Matthew McEachen, Joshua Harris, Anton Mokrushin, Luca Ban, Demiurga, David Randler