Utilities
The small helpers around tokens: base64url codecs, type guards, claim checks, and the plumbing that sign and verify are built from.
Note
ELI5. A JWT is three chunks of base64url text joined by dots. Most of the time sign() and verify() handle everything, and you never touch the chunks. Sometimes you need a piece of what they do on its own: turn bytes into URL-safe text, ask whether a value is a key and what kind, check that a set of claims has not expired, or turn "15m" into seconds. unjwt/utils hands you those pieces, built with the same strict rules the token functions use. None of them checks a signature, so none of them can tell you whether a token is genuine.
Encoding
Strict base64url and base64, byte helpers, shared text codecs and secure random bytes.
Type guards
Narrow an unknown value to a JWK, a JWK Set, a CryptoKey, or an error class.
Claims and durations
validateJwtClaims(), the Duration format, algorithm inference and sanitizers.
unjwt/internal
JOSE plumbing for tests and tooling, with no semver guarantee.
#How they fit together
The program below signs a token, then takes it apart by hand. It reads the claims without verifying the signature, which is fine for a debugging tool and never fine for an authorization decision.
import { sign } from "unjwt/jws";
import { generateJWK } from "unjwt/jwk";
import {
base64UrlParse,
inferJWSAllowedAlgorithms,
isJWTContent,
isSymmetricJWK,
safeJsonParse,
validateJwtClaims,
} from "unjwt/utils";
const key = await generateJWK("HS256");
isSymmetricJWK(key); // true
inferJWSAllowedAlgorithms(key); // ["HS256"]
const token = await sign({ sub: "user_42" }, key, {
expiresIn: "15m",
currentDate: new Date("2026-10-15T12:00:00Z"),
});
const [headerSegment, payloadSegment] = token.split(".");
const header = safeJsonParse(base64UrlParse(headerSegment));
// { kid: "A0nyp_aeagG7…", alg: "HS256", typ: "JWT" }
isJWTContent(header); // true
const claims = safeJsonParse(base64UrlParse(payloadSegment));
// { sub: "user_42", iat: 1792065600, exp: 1792066500 }
validateJwtClaims(claims, { subject: "user_42", currentDate: new Date("2026-10-15T12:10:00Z") });
// passes
validateJwtClaims(claims, { currentDate: new Date("2026-10-15T12:20:00Z") });
// JWTError ERR_JWT_EXPIRED: JWT "exp" (Expiration Time) Claim validation failed: Token has expired (exp: 2026-10-15T12:15:00.000Z)verify() and decrypt() run the same validateJwtClaims() on a JSON payload once the signature or decryption has succeeded, so the claim options you pass there behave exactly as they do here. The JOSE types (JWK, JWTClaims, Duration, …) are exported from unjwt.
#Every export
Everything below is a named export of unjwt/utils. The root entry point unjwt re-exports the whole module flat, so import { base64UrlParse } from "unjwt" works too.
| Page | Exports |
|---|---|
| Encoding | base64UrlStringify, base64UrlParse, base64Stringify, base64Parse, concatUint8Arrays, maybeArray, textEncoder, textDecoder, secureRandomBytes |
| Type guards | isJWK, isJWKSet, isSymmetricJWK, isAsymmetricJWK, isPrivateJWK, isPublicJWK, isCryptoKey, isCryptoKeyPair, assertCryptoKey, isJWTContent, UnsecureError, isUnsecureError |
| Claims and durations | validateJwtClaims, computeDurationInSeconds, inferJWSAllowedAlgorithms, inferJWEAllowedAlgorithms, sanitizeObject, sanitizeObjectCopy, safeJsonParse |
| Types | BytesSource, DecodeOptions, DecodeReturnAs, UnsecureErrorCode, InferAlgorithmsOptions and StrictOmit |
isJWTError lives next to JWTError and is exported from unjwt, unjwt/jws, unjwt/jwe and unjwt/jwk. The type guards page covers it with the others.
#Errors
The codec, random and sanitize helpers come from unsecure and throw its UnsecureError (MALFORMED, INVALID_TYPE, OUT_OF_RANGE). Everything unjwt wrote itself throws a JWTError. When a token function fails because a segment did not decode, it throws a JWTError and keeps the UnsecureError as cause. See Errors for the full list of codes.