LmCast :: Stay tuned in

Bun.Image

Recorded: May 23, 2026, 11:59 p.m.

Original Summarized

Image - BunSkip to main contentBun home pageSearch...⌘KInstall BunSearch...NavigationUtilitiesImageRuntimePackage ManagerBundlerTest RunnerGuidesReferenceBlogFeedbackGet StartedWelcome to BunInstallationQuickstartTypeScriptTypeScript 6 and 7bun initbun createCore RuntimeBun RuntimeWatch ModeDebuggingREPLbunfig.tomlFile & Module SystemFile TypesModule ResolutionJSXAuto-installPluginsFile System RouterHTTP serverServerRoutingCookiesTLSError HandlingMetricsNetworkingFetchWebSocketsTCPUDPDNSData & StorageCookiesFile I/OStreamsBinary DataArchiveSQLSQLiteS3RedisConcurrencyWorkersProcess & SystemEnvironment VariablesShellSpawnWebViewCronInterop & ToolingNode-APIFFIC CompilerTranspilerUtilitiesCSRF ProtectionSecretsConsoleTOMLYAMLMarkdownJSON5JSONLHTMLRewriterImageHashingGlobSemverColorUtilsStandards & CompatibilityGlobalsBun APIsWeb APIsNode.js CompatibilityContributingRoadmapBenchmarkingContributingBuilding WindowsBindgenLicenseOn this pageInputMetadataResizeRotate · flipModulateOutput formatsTerminalsPlaceholdersBun.serve integrationClipboardPlatform backendsUtilitiesImageCopy pageDecode, transform, and encode images with a fast native pipelineCopy pageDocumentation IndexFetch the complete documentation index at: https://bun.com/docs/llms.txtUse this file to discover all available pages before exploring further.Bun.Image is a chainable image pipeline for decoding, resizing, rotating, and re-encoding JPEG, PNG, WebP, HEIC, and AVIF — built on libjpeg-turbo, spng, libwebp, and SIMD geometry kernels, with zero npm dependencies and no native addon build step.
await Bun.file("photo.jpg").image().resize(400, 400, { fit: "inside" }).webp({ quality: 80 }).write("thumb.webp");

The API is shaped after Sharp: construct from an input, chain transforms, pick an output format, then await a terminal method. Nothing runs until the terminal is awaited, and the work executes off the JavaScript thread.
​Input
The constructor accepts a path, bytes, or a Blob — including Bun.file() and Bun.s3(). Blob#image() is shorthand for new Bun.Image(blob):
new Bun.Image("./photo.jpg"); // file path
new Bun.Image(buffer); // Buffer / ArrayBuffer / TypedArray
new Bun.Image(Bun.file("photo.jpg")); // BunFile (read lazily, off-thread)
Bun.file("photo.jpg").image(); // same as above
Bun.s3("bucket/photo.jpg").image(); // S3File

The format is sniffed from the bytes — extensions and Content-Type are ignored.
Path strings are filesystem paths. Don’t pass user-controlled strings directly to the constructor — that’s an arbitrary-file-read primitive. Read untrusted input into a Buffer (e.g. via fetch/Bun.file with your own validation) and pass the bytes.
When passing a TypedArray/ArrayBuffer, don’t mutate it while a terminal is pending — decode runs off-thread and borrows the bytes. SharedArrayBuffer and resizable buffers are refused; use buf.slice() to pass a fixed view.
A second options argument guards against decompression bombs and controls EXIF handling:
new Bun.Image(input, {
// Reject if width*height > this. Checked after reading the header,
// before allocating the pixel buffer. Default matches Sharp (~268 MP).
maxPixels: 4096 * 4096,
// Apply JPEG EXIF Orientation before any other op. Default: true.
autoOrient: true,
});

​Metadata
Read width, height, and format without decoding pixel data:
const { width, height, format } = await new Bun.Image(input).metadata();
// => { width: 1920, height: 1080, format: "jpeg" }

​Resize
img.resize(800); // width 800, keep aspect ratio
img.resize(800, 600); // exactly 800×600 (stretch)
img.resize(800, 600, { fit: "inside" }); // fit within 800×600
img.resize(800, 600, { withoutEnlargement: true }); // never upscale
img.resize(800, 600, { filter: "mitchell" });

fitBehavior"fill" (default)Stretch to exactly width × height"inside"Preserve aspect ratio; result fits within the box
filter selects the resampling kernel. The default "lanczos3" is the right choice for photographs.
FilterUse when"lanczos3" (default)General-purpose, sharpest for photos"lanczos2"Slightly softer, fewer ringing artifacts"mitchell"Smooth gradients; the classic bicubic compromise"cubic"Catmull-Rom — sharper than Mitchell, can ring"mks2013" / "mks2021"”Magic Kernel Sharp”; used by Facebook/Instagram"bilinear" / "linear"Fast, soft"box"Area-average; good for large integer downscales"nearest"Pixel art / hard edges
When the source is a JPEG and the target is at most half the source size, decode skips straight to the nearest M/8 IDCT scale, so generating a thumbnail from a 24 MP photo never materializes the full-resolution buffer.
​Rotate · flip
img.rotate(90); // 90° clockwise (multiples of 90 only)
img.flip(); // mirror vertically (about the x-axis)
img.flop(); // mirror horizontally (about the y-axis)

​Modulate
img.modulate({
brightness: 1.2, // 1 = unchanged
saturation: 0, // 0 = greyscale, 1 = unchanged, >1 = boost
});

​Output formats
Calling a format method sets the encode target; without one, the source format is reused.
img.jpeg({ quality: 85 }); // 1–100, default 80
img.png({ compressionLevel: 6 }); // zlib level 0–9
img.png({ palette: true, colors: 64, dither: true }); // indexed PNG
img.webp({ quality: 80 });
img.webp({ lossless: true });
img.heic({ quality: 80 }); // macOS / Windows only
img.avif({ quality: 60 }); // macOS / Windows only

palette: true quantizes to a ≤256-color palette and emits an indexed (color-type 3) PNG, optionally with Floyd–Steinberg dither. This is typically 3–5× smaller than truecolor for screenshots and UI assets.
​Terminals
A pipeline does no work until one of these is awaited:
await img.bytes(); // Uint8Array
await img.buffer(); // Buffer
await img.blob(); // Blob with .type set to the output MIME
await img.toBase64(); // string
await img.dataurl(); // "data:image/png;base64,…"
await img.write("out.webp"); // number (bytes written)
await img.write(Bun.s3("bucket/out.webp"));

.write() accepts the same destinations as Bun.write — a path string, Bun.file(), Bun.s3(), or an fd. If you didn’t chain a format method and the destination is a path string, the extension picks one (.jpg/.png/.webp/.heic/.avif).
​Placeholders
For a low-quality placeholder to inline in HTML before the real image loads, .placeholder() returns a ThumbHash-rendered ≤32px blur as a data: URL — ~400–700 bytes, no client-side decoder needed:
const lqip = await Bun.file("hero.jpg").image().placeholder();
// <img src={lqip} … /> — then swap to the real URL on load.

For coarse-to-fine rendering of the image itself, encode a progressive JPEG:
img.jpeg({ progressive: true });

After the first terminal resolves, img.width and img.height reflect the output dimensions (they’re -1 before).
​Bun.serve integration
A Bun.Image pipeline is a valid Response body and sets Content-Type automatically. To keep the encode off the JS thread in a server handler, await a terminal first:
Bun.serve({
routes: {
"/avatar/:id": async req => {
// Validate before touching the filesystem (see the Input note above).
if (!/^[a-z0-9]+$/.test(req.params.id)) return new Response(null, { status: 400 });
const out = await Bun.file(`avatars/${req.params.id}.png`).image().resize(128, 128).webp().blob();
return new Response(out);
},
},
});

Passing the pipeline directly (new Response(img)) also works, but currently runs the encode synchronously during body init.
​Clipboard
const img = Bun.Image.fromClipboard();
if (img) {
const png = await img.resize(800, 800, { fit: "inside" }).png().bytes();
}

fromClipboard() reads PNG, TIFF, HEIC, JPEG, WebP, GIF, or BMP from the system pasteboard on macOS and Windows; the regular decode pipeline takes it from there. Returns null if there’s no image, and always null on Linux — call wl-paste/xclip yourself and pass the bytes to the constructor.
For a passive “image in clipboard, press ⌘V” hint, poll clipboardChangeCount() (a single integer read) and call hasClipboardImage() only when it moves; macOS has no clipboard-change notification, so this is the documented pattern.
​Platform backends
LinuxmacOSWindowsJPEG / PNG / WebPlibjpeg-turbo · spng · libwebpsamesameBMP / GIF (decode)built-inImageIOWICTIFF (decode)❌ImageIOWICResize / rotate / flipHighway SIMDAccelerate vImageHighway SIMDHEIC / AVIF❌ ERR_IMAGE_FORMAT_UNSUPPORTEDImageIO ²WIC ¹Clipboard❌ returns nullNSPasteboardWin32
¹ Windows requires the HEIF Image Extensions / AV1 Video Extension from the Microsoft Store.
² AVIF encode needs an OS AV1 encoder — Apple Silicon M3+ only. Intel Mac and M1/M2 reject with ERR_IMAGE_FORMAT_UNSUPPORTED; AVIF decode works everywhere ImageIO does (macOS 13+).
When a system-backend format isn’t available on the current machine, the terminal rejects with error.code === "ERR_IMAGE_FORMAT_UNSUPPORTED" — branch on that to fall back to a portable format:
const out = await img
.avif({ quality: 50 })
.bytes()
.catch(e => {
if (e.code === "ERR_IMAGE_FORMAT_UNSUPPORTED") return img.webp({ quality: 80 }).bytes();
throw e;
});

Formats handled by the system backend (TIFF, HEIC, AVIF, clipboard) inherit the OS’s patch level — keep macOS / Windows updated. JPEG, PNG, and WebP go through the same statically-linked codecs on every platform, so encoded output is byte-identical across Linux, macOS, and Windows. To force the portable Highway path for geometry too — e.g. for golden-image tests — set the process-global backend:
Bun.Image.backend = "bun"; // default is "system" on macOS/Windows
Was this page helpful?YesNoSuggest editsRaise issueHTMLRewriterPreviousHashingNext⌘IxgithubdiscordyoutubePowered byThis documentation is built and hosted on Mintlify, a developer documentation platform

The Bun.Image API provides a chainable pipeline for image processing, enabling fast native operations for decoding, resizing, rotating, and re-encoding various formats including JPEG, PNG, WebP, HEIC, and AVIF. This system is engineered upon high-performance components such as libjpeg-turbo, spng, libwebp, and SIMD geometry kernels, crucially operating without any npm dependencies or native addon build steps. The API structure mirrors the approach of systems like Sharp: an input is provided, a sequence of transformations is chained, an output format is selected, and finally, a terminal method is awaited to execute the work asynchronously off the JavaScript thread.

Input handling accepts file paths, raw bytes, or Blobs, utilizing methods like Bun.file() or Bun.s3() for lazy reading. Developers must exercise caution when using path strings, as this presents an arbitrary-file-read primitive, requiring validation of untrusted input. When dealing with byte arrays or TypedArrays, the API ensures that decoding runs off-thread, borrowing the data without mutation while the terminal operation is pending; shared or resizable buffers are explicitly disallowed in favor of fixed views. Options allow configuration, such as setting maximum pixel dimensions to mitigate decompression bombs or controlling EXIF orientation handling. The API also allows for reading basic metadata, such as width, height, and format, without fully decoding the pixel data.

Transformations are executed through methods that manipulate the image state. Resizing permits various behaviors, including fitting within a specified box while preserving aspect ratio, exactly stretching to target dimensions, or setting specific filtering algorithms like lanczos3 for sharpness or mitchell for smooth gradients. Rotation and flipping are supported for geometric manipulation, and modulation allows adjustments to brightness and saturation. The system defines specific resampling kernels, such as lanczos3, mitchell, cubic, and nearest, to control the quality of geometric operations.

Output formats are handled via specific methods, allowing encoding to JPEG with adjustable quality, PNG with options for palette inclusion and dithering, and support for modern formats like WebP and HEIC, as well as AVIF. The process of generating output is asynchronous, dictated by waiting for a terminal method to resolve, which can yield raw bytes, memory buffers, descriptive blobs, Base64 strings, or paths for writing to the filesystem or S3. Furthermore, placeholder generation allows for the creation of low-quality, minimally decoded data URLs for rapid rendering in contexts like HTML before the full image loads.

The API integrates into server environments through Bun.serve, where the image pipeline can be used to generate network responses, automatically setting the Content-Type. For handling image data from the clipboard, Bun.Image.fromClipboard() reads various image types from the system pasteboard, acknowledging platform differences across Linux, macOS, and Windows. The system employs platform-specific backends, such as ImageIO on macOS and Win32 on Windows, for decoding, and includes logic to handle unsupported system formats by automatically falling back to a more portable format, such as WebP, ensuring resilience against hardware or OS limitations. The configuration allows setting a global backend identifier, which can be used to force portable geometry testing, and the system mandates keeping operating systems updated to support advanced formats like AVIF.