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.

#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.

PageExports
Encodingbase64UrlStringify, base64UrlParse, base64Stringify, base64Parse, concatUint8Arrays, maybeArray, textEncoder, textDecoder, secureRandomBytes
Type guardsisJWK, isJWKSet, isSymmetricJWK, isAsymmetricJWK, isPrivateJWK, isPublicJWK, isCryptoKey, isCryptoKeyPair, assertCryptoKey, isJWTContent, UnsecureError, isUnsecureError
Claims and durationsvalidateJwtClaims, computeDurationInSeconds, inferJWSAllowedAlgorithms, inferJWEAllowedAlgorithms, sanitizeObject, sanitizeObjectCopy, safeJsonParse
TypesBytesSource, 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.