Generate deterministic fingerprint hashes from filesystem state and arbitrary content inputs.
Given a directory, fs-fingerprint hashes the files inside it and produces a single fingerprint string. If any file changes, the fingerprint changes. You can also mix in text, JSON data, and environment variables as additional inputs.
This is useful for cache invalidation -- when you need to know whether source files (or configuration) have changed since the last build, deploy, or test run.
Speed matters because fingerprinting often runs on every build or CI job. The library is benchmarked on every change (bun run bench), has a single runtime dependency (tinyglobby), and weighs around 30 KB unpacked.
npm install fs-fingerprintAlso works with yarn, pnpm, and bun.
import { calculateFingerprint } from "fs-fingerprint";
const { hash } = await calculateFingerprint("/project/path", {
files: ["ios/", "package.json"],
ignores: ["ios/build/"],
});A synchronous version is also available:
import { calculateFingerprintSync } from "fs-fingerprint";
const { hash } = calculateFingerprintSync("/project/path", {
files: ["src/"],
});async function calculateFingerprint(
basePath: string,
options?: {
files?: string[]; // Glob patterns to include (default: all files)
ignores?: string[]; // Glob patterns to exclude
contentInputs?: ContentInput[]; // Additional non-file inputs
hashAlgorithm?: string; // "sha1" (default), "sha256", "sha512", etc.
gitIgnore?: boolean; // Exclude git-ignored paths (default: false)
},
): Promise<Fingerprint>;Returns a Fingerprint object:
interface Fingerprint {
hash: string; // Combined fingerprint hash
files: FileHash[]; // Individual file hashes
content: ContentHash[]; // Individual content input hashes
}When gitIgnore is enabled, git errors are silently ignored (missing git binary, not a git repo, etc.).
See the API reference for the full API, including low-level helpers.
const { hash } = await calculateFingerprint("/project/path", {
files: ["src/", "package.json"],
ignores: ["**/*.test.ts", "dist"],
});Content inputs let you include non-file data in the fingerprint. Import the helpers alongside the main function:
import { calculateFingerprint, textContent, jsonContent, envContent } from "fs-fingerprint";
const { hash } = await calculateFingerprint("/project/path", {
contentInputs: [
textContent("app-config", "debug=true"),
jsonContent("app-metadata", { version: "1.0", env: "prod" }),
envContent("app-envs", ["BUILD_ENVIRONMENT", "FEATURE_FLAG"]),
envContent("signing-key", ["API_KEY"], { secret: true }), // hashed but not included in output details
],
});JSON inputs are key-sorted before hashing, so property order doesn't affect the fingerprint.
const { hash } = await calculateFingerprint("/project/path", {
gitIgnore: true,
});const { hash } = await calculateFingerprint("/project/path", {
hashAlgorithm: "sha512",
});- Node.js >= 20
- ESM only (no CommonJS)
- Glob-match files under
basePathusing thefilesandignorespatterns - Hash each matched file by content
- Hash any content inputs (text, JSON, env vars)
- Sort everything by path/key and combine into a single fingerprint hash
File paths are part of the fingerprint, so renaming a file changes the hash even if the content is identical. File metadata (timestamps, permissions) is ignored -- only content matters.
PRs welcome! Keep it awesome.
MIT 💝
Made with 💻 and ☕️ by MDJ