High performance, secure and easy-to-use image optimizer powered by sharp and svgo.
Used by Nuxt Image and Netlify and open to everyone!
Note
This is the active development branch for IPX v4. Check out v3 for v3 docs.
- The server creation APIs have changed. See the Programmatic API section for examples.
- The JSON error format has changed from
{ error: string }to{ status, statusText, message }. - The
svgooption is nowsvg.optimize. SVG images are always sanitized, even when optimization is disabled. - SVG optimization now applies SVGO's
preset-defaultunless custompluginsare configured (previously only the configured plugins ran), so SVG output is restructured more than before. See the SVG Images section.
You can use ipx command to start server.
Using npx:
npx ipx serve --dir ./Using bun
bunx ipx serve --dir ./The default serve directory is the current working directory.
You can use IPX as a middleware or directly use IPX interface.
Example: Using built-in server
import { serveIPX, createIPX, ipxFSStorage, ipxHttpStorage } from "ipx";
const ipx = createIPX({
storage: ipxFSStorage({ dir: "./public" }),
httpStorage: ipxHttpStorage({ domains: ["picsum.photos"] }),
alias: { "/picsum": "https://picsum.photos" },
});
// http://localhost:3000/w_512/picsum/1000
serveIPX(ipx);Example: Using with h3
import { H3, serve } from "h3";
import {
createIPX,
ipxFSStorage,
ipxHttpStorage,
createIPXFetchHandler,
} from "ipx";
const ipx = createIPX({
storage: ipxFSStorage({ dir: "./public" }),
httpStorage: ipxHttpStorage({ domains: ["picsum.photos"] }),
alias: { "/picsum": "https://picsum.photos" },
});
const app = new H3();
app.mount("/ipx", createIPXFetchHandler(ipx));
// http://localhost:3000/ipx/w_512/picsum/1000
serve(app);Example: Using with express
import Express from "express";
import {
createIPX,
ipxFSStorage,
ipxHttpStorage,
createIPXNodeHandler,
} from "ipx";
import type { RequestHandler } from "express";
const ipx = createIPX({
storage: ipxFSStorage({ dir: "./public" }),
httpStorage: ipxHttpStorage({ domains: ["picsum.photos"] }),
alias: { "/picsum": "https://picsum.photos" },
});
const app = Express();
app.use("/ipx", createIPXNodeHandler(ipx) as RequestHandler);
// http://localhost:3000/ipx/w_512/picsum/1000
app.listen(3000, () => {
console.log("Server is running on http://localhost:3000");
});Get original image:
/_/static/buffalo.png
Change format to webp and keep other things same as source:
/f_webp/static/buffalo.png
Automatically convert to a preferred format (avif/webp/jpeg). Uses the browsers accept header to negotiate:
/f_auto/static/buffalo.png
Keep original format (png) and set width to 200:
/w_200/static/buffalo.png
Resize to 200x200px using embed method and change format to webp:
/embed,f_webp,s_200x200/static/buffalo.png
The parseURL option accepts a function that extracts the resource id and modifiers from the request URL, allowing any URL style you like. It receives the raw (still percent-encoded) request URL, so it is free to decode it however the URL style requires.
Example: modifiers in the filename (/<id>@@<modifiers>.<format>), which can be preferable when prerendering images for static hosting.
import { createIPXFetchHandler, parseIPXURL } from "ipx";
const handler = createIPXFetchHandler(ipx, {
parseURL(url) {
const path = decodeURIComponent(new URL(url).pathname.slice(1));
const match = path.match(/^(.+)@@(.+)\.([^.]+)$/);
if (!match) {
// Not our style, fall back to the default `/<modifiers>/<id>`
return parseIPXURL(url);
}
const [, id = "", modifiersString = "", format = ""] = match;
const modifiers = Object.fromEntries(
modifiersString.split(",").map((m) => {
const [key = "", ...values] = m.split("_");
return [key, values.join("_")];
}),
);
return { id, modifiers: { ...modifiers, format } };
},
});
// http://localhost:3000/static/buffalo.png@@s_200x200.webp
// http://localhost:3000/static/buffalo.png@@grayscale,w_200.webpThe parser may be async, and can throw an HTTPError (re-exported from ipx) to reject a request with a specific status code.
Returned values are escaped by IPX, so custom parsers don't need to do it themselves. Note this is not an access check — exactly as with the default URL style, what the resulting id is allowed to resolve to is enforced by the storage layer (ipxFSStorage's directory boundary, ipxHttpStorage's domain allowlist).
You can universally customize IPX configuration using IPX_* environment variables.
IPX_ALIAS- Default:
{}
- Default:
IPX_MAX_OUTPUT_DIMENSION- Default:
8192 - Maximum width and height (in pixels) of the output image (option:
maxOutputDimension). Requestedwidth,heightandresizedimensions are clamped to it, preserving the requested aspect ratio, andextendedges are clamped so the extended canvas stays within it. This bounds how much memory a single request can allocate: sharp only limits the input size, so without it/enlarge,s_20000x20000/image.jpg(or/extend_10000_10000_10000_10000/image.jpg) allocates gigabytes from a small source image. Set tofalseto disable, which is only safe when modifiers come from a trusted source.
- Default:
(enabled by default with CLI only)
- Default:
.(current working directory)
- Default:
300
(enabled by default with CLI only)
- Default:
[]
- Default:
300
- Default:
{}
- Default:
false
Modifier arguments are separated with _ and validated before they reach sharp. Invalid input is rejected with a 400 IPX_INVALID_MODIFIER_ARG (or 400 IPX_MISSING_MODIFIER_ARG for a required argument) instead of failing the request. Trailing arguments may be omitted to keep the sharp default, except where noted.
Colours (background, tint) accept any colour sharp understands: hex (f00, ff0000, ff000080) with an optional leading # — which cannot be used inside a URL path, so it may be dropped — or a CSS colour name (red).
| Property | Docs | Example | Comments |
|---|---|---|---|
| width / w | Docs | /width_200/buffalo.png or /w_200/buffalo.png |
Positive integer. |
| height / h | Docs | /height_200/buffalo.png or /h_200/buffalo.png |
Positive integer. |
| resize / s | Docs | /s_200x200/buffalo.png |
{width}x{height} of positive integers. A single value (/s_200/) is a square. |
| kernel | Docs | /s_200x200,kernel_nearest/buffalo.png |
Sets kernel option for resize. One of nearest, linear, cubic, mitchell, lanczos2, lanczos3 (default), mks2013 or mks2021. |
| fit | Docs | /s_200x200,fit_outside/buffalo.png |
Sets fit option for resize. One of contain, cover (default), fill, inside or outside. |
| position / pos | Docs | /s_200x200,pos_top/buffalo.png |
Sets position option for resize. A position (top, right top, right, right bottom, bottom, left bottom, left, left top), gravity (north, northeast, ..., center) or strategy (entropy, attention). Since _ separates arguments, multi-word values use - (/pos_right-top/). The numeric gravity (0-8) and strategy (16-17) constants are also accepted. |
| trim | Docs | /trim_100/buffalo.png |
Trim threshold, a number >= 0 (defaults to 10). |
| extend | Docs | /extend_10_20_10_20_mirror/buffalo.png |
Extend / pad / extrude one or more edges of the image. Format: /extend_{top}_{right}_{bottom}_{left}_{extendWith}/. Edges are integers between 0 and 10000. Optional extendWith: background (default), copy, repeat or mirror. When extending with background, the colour comes from the background / b modifier, e.g. /b_00ff00,extend_10_20_10_20/buffalo.png. |
| background / b | _ | /r_45,b_00ff00/buffalo.png |
Background colour used by extend, rotate, flatten and resize (with fit_contain). |
| extract | Docs | /extract_{left}_{top}_{width}_{height}/buffalo.png |
Extract/crop a region of the image. All four arguments are required; width and height must be positive. |
| crop | Docs | /crop_{left}_{top}_{width}_{height}/buffalo.png |
Alias for extract. Extract/crop a region of the image. |
| format / f | Docs | /format_webp/buffalo.png or /f_webp/buffalo.png |
Supported format: jpg, jpeg, png, webp, avif, gif, heif, tiff and auto (experimental only with middleware) |
| quality / q | _ | /quality_50/buffalo.png or /q_50/buffalo.png |
Integer between 1 and 100. |
| rotate | Docs | /rotate_45/buffalo.png |
Angle in degrees, between -3600 and 3600. Angles that are not a multiple of 90 are filled with the background / b colour. Without an angle (/rotate/) the image is auto-oriented from its EXIF tag. |
| enlarge | _ | /enlarge,s_2000x2000/buffalo.png |
Allow the image to be upscaled. By default the returned image will never be larger than the source in any dimension, while preserving the requested aspect ratio. |
| flip | Docs | /flip/buffalo.png |
|
| flop | Docs | /flop/buffalo.png |
|
| sharpen | Docs | /sharpen_2_1_2/buffalo.png |
{sigma}_{flat}_{jagged}. sigma is between 0.000001 and 10, flat and jagged between 0 and 1000000. Without arguments (/sharpen/) a mild sharpen is applied. |
| median | Docs | /median_10/buffalo.png |
Square mask size, an integer between 1 and 1000 (defaults to 3). |
| blur | Docs | /blur_5/buffalo.png |
Sigma, a number between 0.3 and 1000. Without an argument (/blur/) a mild blur is applied. |
| unflatten | Docs | /unflatten/buffalo.png |
|
| gamma | Docs | /gamma_2.2_1.8/buffalo.png |
{gamma}_{gammaOut}, each a number between 1.0 and 3.0 (defaults to 2.2). |
| negate | Docs | /negate/buffalo.png |
|
| normalize | Docs | /normalize/buffalo.png |
|
| threshold | Docs | /threshold_10/buffalo.png |
Integer between 0 and 255 (defaults to 128). |
| tint | Docs | /tint_00ff00/buffalo.png |
Tint colour. |
| grayscale | Docs | /grayscale/buffalo.png |
|
| flatten | Docs | /flatten/buffalo.png |
Remove alpha channel, if any, and replace with the background / b colour. |
| modulate | Docs | /modulate_2_1.2_90_10/buffalo.png |
Transforms the image using {brightness}_{saturation}_{hue}_{lightness}. brightness and saturation are numbers >= 0, hue is an integer in degrees. |
| animated / a | - | /animated/buffalo.gif or /a/buffalo.gif |
Experimental |
SVG images are not processed by sharp. They are sanitized, optimized with svgo and served as image/svg+xml. Input that is not well-formed XML (an unescaped & is a common cause) is rejected with a 400 IPX_INVALID_SVG.
createIPX({
storage,
svg: {
// SVGO config, or `false` to disable optimization
optimize: { multipass: true },
// Serve SVG images unsanitized. Only for fully trusted sources!
unsafeSkipSanitize: false,
},
});SVGO's preset-default is applied unless you configure plugins yourself. Output is always a re-serialized document, never byte-identical to the source: ids are renamed, elements that are neither visible nor referenced within the same file are dropped, shapes are converted to paths and <style> rules are inlined.
That is fine for images used as <img src> or as CSS backgrounds, but it can break consumers that reach into the document:
- Sprite sheets: a
<symbol id="icon">with no<use>in the same file is removed, so<use href="/sprite.svg#icon">renders nothing. - References by id from outside the file, since ids are renamed (
icon-homebecomesa). - Selectors in a host page that inlines the SVG, since
<rect>becomes<path>and class-based rules are inlined.
Use svg: { optimize: false } to sanitize without optimizing, or keep optimization with the structural plugins disabled (about 1% larger output for typical icons):
createIPX({
storage,
svg: {
optimize: {
plugins: [
{
name: "preset-default",
params: {
overrides: { cleanupIds: false, removeHiddenElems: false },
},
},
],
},
},
});SVG documents can carry active content, so IPX always sanitizes them before serving. Sanitization is independent of optimization: svg: { optimize: false } only disables SVGO's optimization plugins.
Removed from every SVG:
<script>elements (including namespaced ones such as<svg:script>)- Event handler attributes (any
on*attribute) - Embedded foreign documents:
<foreignObject>,<iframe>,<embed>,<object>,<base>,<link>and<meta> - Event handler elements:
<handler>and<listener> - SMIL animations (
<animate>,<animateMotion>,<animateTransform>and<set>) that assign anon*attribute or an unsafe URI, which could otherwise re-introduce a handler after load - URIs (
href,xlink:hrefandsrc) with a scheme other thanhttp:,https:,mailto:,tel:,ftp:or a non-SVGdata:image/*— in particularjavascript:, including obfuscated variants using entities or control characters - The
<!DOCTYPE>declaration and all processing instructions (<?…?>), which are serialized unescaped and can smuggle markup past an HTML parser
External references are kept. Attributes such as <image href="https://…">, <use href="…">, external fonts and @import inside <style> are preserved, since stripping them would break legitimate images. They are not a script execution vector, but they do allow the SVG to make requests to third-party origins (and thereby leak the viewer's IP address) when rendered as a document. If this matters for your threat model, host such images from a separate origin or block the requests with a Content-Security-Policy.
The bundled server sends content-security-policy: default-src 'none' with successful responses by default, which blocks both script execution and external references in browsers that honor it. Custom servers built on the programmatic API should send the same header, since sanitization cannot cover every future browser behavior on its own.
Sanitization can be disabled with svg: { unsafeSkipSanitize: true }. Only do this when every source is fully trusted: IPX will then serve SVG images with XSS payloads unchanged.