The bcryptjs npm package is a pure-JavaScript implementation of the bcrypt password-hashing algorithm with zero native dependencies, which makes it the go-to choice when you cannot compile a native addon. It is API-compatible with the native bcrypt package but runs anywhere JavaScript runs, including serverless functions, edge runtimes, and slim Docker images where a C++ toolchain is unavailable.
If you are here because you typed npm i bcryptjs and want to be sure you are using it correctly, this review covers the safe patterns, the real limitations, and the mistakes that quietly weaken your authentication.
bcryptjs vs the native bcrypt package
There are two mainstream options on npm. The native bcrypt package wraps a C++ implementation and is faster. The bcryptjs package is written entirely in JavaScript. The functional behavior is compatible: a hash produced by one verifies with the other.
The reason teams reach for npm bcryptjs instead of the native binding usually comes down to deployment friction:
- No node-gyp, no build tools, no platform-specific binaries to rebuild when you change your base image or Node version.
- It runs in environments that reject native modules, such as some serverless and edge platforms.
- Fewer "works on my machine, breaks in CI" incidents caused by prebuilt binary mismatches.
The cost is speed. The pure-JS implementation is roughly 30 percent slower than the native binding. For password hashing that is usually fine, because you want the operation to be deliberately slow, but it does mean you should benchmark your chosen cost factor on your actual hardware rather than copying a number from a blog post.
Installing and importing
Installation is a single command:
npm i bcryptjs
The current release line is 3.x, and it ships an ECMAScript module with a UMD fallback, so both import and require work:
// ESM
import bcrypt from 'bcryptjs';
// CommonJS
const bcrypt = require('bcryptjs');
That is the whole setup. There is no post-install compile step, which is the entire point of the package.
The correct way to hash a password
Use the async API and let bcrypt generate the salt for you. The one-call form handles salt generation, hashing, and encoding the salt into the output string:
import bcrypt from 'bcryptjs';
const SALT_ROUNDS = 12;
async function hashPassword(plain) {
return bcrypt.hash(plain, SALT_ROUNDS);
}
async function verifyPassword(plain, stored) {
return bcrypt.compare(plain, stored);
}
A few things are load-bearing here:
The cost factor (SALT_ROUNDS) controls how expensive each hash is. Every increment roughly doubles the work. Twelve is a reasonable 2025 baseline for interactive logins; measure it so a single hash lands in the tens-to-low-hundreds of milliseconds on your servers, then raise it as hardware improves.
The output of hash is a 60-character string that already embeds the algorithm identifier, cost factor, and salt. Store that whole string. Do not store the salt separately, and do not try to parse it apart.
Always compare with bcrypt.compare, never with ===. The compare function re-derives the hash using the salt baked into the stored value and checks it in a way that avoids leaking timing information about how many characters matched.
The 72-byte limit that surprises people
bcrypt only considers the first 72 bytes of input. Anything beyond that is silently ignored. This matters in two ways.
First, UTF-8 characters can consume up to 4 bytes each, so a password of emoji or non-Latin script hits the limit sooner than 72 visible characters. Second, and more dangerously, if you "pre-hash" a password with something that produces long output and feed it in as text, you can accidentally push the distinguishing bytes past the cutoff.
A common defensive pattern for very long inputs is to pre-hash with SHA-256 and base64-encode the result before passing it to bcrypt, keeping the input inside the byte budget. If you do not have that requirement, just enforce a sensible maximum password length in your validation layer and move on.
Is bcryptjs a supply-chain risk?
The honest answer: bcrypt as an algorithm is showing its age relative to memory-hard functions like Argon2 and scrypt, which resist GPU and ASIC cracking better. bcrypt is still acceptable for password storage in 2025, but if you are designing a new system with no compatibility constraints, evaluate Argon2id first.
As for the package itself, treat it like any dependency: pin it in your lockfile, watch for advisories, and keep it current. Zero runtime dependencies is a genuine security advantage here, because there is no transitive tree to audit. An SCA tool such as Safeguard can confirm the resolved version has no open advisories, which our SCA product does as part of a normal dependency scan. If you want the broader context on why hashing choices matter, the academy has a walkthrough on credential storage.
Common mistakes to avoid
- Hashing on the client. Password hashing belongs on the server. A client-side hash becomes the de facto password and buys you nothing.
- Using the sync API in a request handler.
bcrypt.hashSyncblocks the event loop; under load it will tank your throughput. Use the promise-based calls. - Reusing a low cost factor forever. A value chosen in 2018 is too cheap now. Revisit it, and rehash on next login when you raise it.
- Storing the salt separately. It is already inside the hash string. Splitting it out just invites bugs.
FAQ
What is the difference between bcryptjs and bcrypt on npm?
bcrypt is a native C++ binding and is faster; bcryptjs is pure JavaScript with zero native dependencies and runs anywhere, including edge and serverless. Their hashes are cross-compatible, so you can verify a bcryptjs hash with the native package and vice versa.
How do I install bcryptjs?
Run npm i bcryptjs. There is no compile step, so it works in slim containers and CI without build tools. Import it with either import bcrypt from 'bcryptjs' or require('bcryptjs').
What cost factor should I use with bcryptjs?
Benchmark on your own hardware. Pick the highest cost factor that keeps a single hash within an acceptable latency for login, commonly in the range of tens to low-hundreds of milliseconds. A value around 12 is a reasonable starting point in 2025; raise it over time.
Should I use bcrypt or Argon2 for new projects?
For greenfield systems with no compatibility constraints, evaluate Argon2id first, since it is memory-hard and resists GPU cracking better. bcrypt (via bcryptjs) remains acceptable and is the pragmatic choice when you need broad compatibility or a dependency-free install.