Resize, convert, and compress images in the browser until they satisfy strict upload requirements — the kind government portals, visa/passport forms, and job application sites love to enforce:
"JPEG only, under 20KB, exactly 200×200 pixels."
No server round-trip. No general-purpose "make images smaller" library —
give it a target size and/or dimensions, and it runs a full pipeline (EXIF
correction, crop, resize, format conversion, and a binary search over encode
quality) until the output actually fits, then hands you back a File ready
to append to FormData.
packages/
core/ @poly67/core — the optimization engine (zero deps)
react/ @poly67/react — <ImageOptimizer /> + useImageOptimizer
examples/
basic-app/ — Vite demo of all three usage patterns
npm install @poly67/react # includes core as a dependency
# or, framework-agnostic:
npm install @poly67/coreimport { ImageOptimizer } from "@poly67/react";
<ImageOptimizer
maxSizeKB={20}
maxWidth={200}
maxHeight={200}
format="jpeg"
quality={0.9}
autoOptimize
preview
onProgress={(p) => console.log(p.stage, p.progress)}
onSuccess={(result) => {
const formData = new FormData();
formData.append("photo", result.file);
fetch("/api/upload", { method: "POST", body: formData });
}}
onError={(error) => console.error(error)}
/>;Or without a component:
import { optimizeImage } from "@poly67/core";
const { file } = await optimizeImage(input.files[0], {
maxSizeKB: 20,
maxWidth: 200,
maxHeight: 200,
format: "jpeg",
});Or with the hook, for a fully custom UI:
import { useImageOptimizer } from "@poly67/react";
const { optimize, isOptimizing, progress, result, error } = useImageOptimizer({
maxSizeKB: 20,
maxWidth: 200,
maxHeight: 200,
});See packages/core/README.md and
packages/react/README.md for full API
references, and examples/basic-app for a runnable
demo of all three patterns (government ID photo, job portal resume photo,
direct API call).
- Resize while preserving aspect ratio (or stretch to an exact box)
contain(fit inside, letterbox) orcover(fill and crop) modes- Convert between JPEG / PNG / WebP
- Binary-search encode quality to hit a target file size accurately
- Progressive downscale fallback for lossless formats or unreachable targets
- Optional pixel-rect crop
- White (or custom) background flattening when converting transparent PNG → JPEG
- EXIF orientation correction (hand-parsed, no dependency)
AbortControllersupport and stage-by-stage progress callbacks- Async, Promise-based API; returns both
FileandBlob - Drag-and-drop compatible out of the box
- 100% client-side — no server, no upload, no network call
- Read the file and decode it (
createImageBitmap, falling back toHTMLImageElement). - Correct EXIF orientation — phones often store landscape pixel data with a rotation flag rather than physically rotating it; this is parsed by hand from the JPEG's APP1/EXIF segment.
- Crop, if a
croprect was given. - Resize into
maxWidth/maxHeight, perfitandpreserveAspectRatio. - Encode, filling a background first if the source had transparency and
the target format doesn't support alpha (or a
backgroundwas set explicitly). - If
maxSizeKBis set: binary-search quality betweenminQualityandqualityfor the highest quality that still fits the byte budget. - If quality alone can't reach the target (lossless PNG, or a very aggressive size target): progressively downscale the canvas and repeat step 6, up to a safety limit.
- Return an
OptimizeResultwith the finalFile,Blob, dimensions, size, quality, and how many encode attempts it took.
This is an npm-workspaces monorepo.
npm install # installs and links all workspace packages
npm run build # builds packages/core then packages/react
npm run test # runs the full Vitest suite (both packages)
npm run lint # ESLint across the repo
npm run typecheck # tsc --noEmit in every workspace
npm run dev # runs the example app (examples/basic-app)Releases are managed with Changesets:
npx changeset # describe your change
# on merge to main, CI opens a "Version Packages" PR;
# merging that PR publishes to npm.Requires Blob and Canvas (or OffscreenCanvas). createImageBitmap is
used when available for faster, non-blocking decode, with an automatic
HTMLImageElement + <canvas> fallback for older engines.
Issues and PRs welcome. Please run npm run lint && npm run typecheck && npm run test
before opening a PR, and add a changeset (npx changeset) describing your change.
MIT © Keerthan D