# unjs/ipx: URL-driven image resizing with sharp, from a directory or an allowed domain list

> IPX turns image paths into modifier URLs, so /w_512,f_webp/photos/buffalo.png returns a resized WebP without any pre-generated files. It is aimed at developers who want on-demand transforms without a hosted image CDN, and it ships as both a CLI and a programmatic API.

**unjs/ipx** — 🖼️ High performance, secure and easy-to-use image optimizer.

- Repository: https://github.com/unjs/ipx
- Stars: 2,458 · Forks: 94
- Language: TypeScript
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/unjs-ipx

## The problem IPX solves: one source image, many URL variants

Most image pipelines start with a build step that writes every size and format you might need. That works until a designer asks for a 512px WebP that nobody generated, or until a client sends an accept header that prefers avif. IPX takes the other route: the transform is encoded in the request path, and the work happens when the request arrives.

The README states the pitch directly: point IPX at a directory or a list of allowed domains, and every image is available in any size, format and quality straight from its URL. The example it gives is `/w_512,f_webp/photos/buffalo.png`. There is no manifest, no build output and no per-variant file on disk.

Who this is for is fairly narrow. You need a Node process that can reach the source images, either on local disk or over HTTP, and you need to be comfortable letting a URL decide how much CPU a request consumes. The README describes IPX as powered by sharp and svgo, so the transform quality and format support are sharp's, and SVG handling goes through svgo. If your stack is already Node and you resent maintaining a folder of pre-rendered thumbnails, this is the shape of tool you want.

## How the modifier URL is parsed and validated

The route format is `/<modifiers>/<id>`. Modifiers are separated by commas, and the arguments inside a modifier are separated by underscores. A bare underscore stands for no modifier, so `/_/static/buffalo.png` returns the original image.

Validation happens before sharp is called. The README says invalid input is rejected with `400 IPX_INVALID_MODIFIER_ARG`, or `400 IPX_MISSING_MODIFIER_ARG` when a required argument is absent, rather than failing the request. That distinction matters: a malformed URL is a client error, not a server crash. The README also notes a third case. Some arguments can only be validated by libvips once it runs, which happens after the whole pipeline is set up, and those surface as `400 IPX_INVALID_MODIFIER`.

Trailing arguments can be omitted to keep the sharp default, except where the documentation says otherwise. Colours for `background` and `tint` accept hex with an optional leading `#`, which cannot appear inside a URL path and so may be dropped, or a CSS colour name such as `red`. Booleans accept `true` and `false` as well as `1` and `0`. The modifier set is wide: `width`/`w`, `height`/`h`, `resize`/`s`, `kernel`, `fit`, `position`/`pos`, `extend`, and format selection through `f_webp` or `f_auto`. The README describes `f_auto` as negotiating avif, webp or jpeg from the browser's accept header.

One parsing detail is easy to miss. Because `_` separates arguments, multi-word values use a hyphen, so a position is written `pos_right-top`. If you generate modifier strings in application code, that substitution has to happen before the URL is built.

## Installing IPX and serving a first image

The README's quick start does not ask for a global install. It runs the CLI straight from the registry against the current directory:

```bash
npx ipx serve --dir ./
```

With bun, the equivalent command uses bunx:

```bash
bunx ipx serve --dir ./
```

The CLI prints a URL. According to the README, opening that URL and adding modifiers to any image path gives you the transformed image, for example `http://localhost:3000/w_200/buffalo.png`. That request returns the image at 200 pixels wide, keeping the original png format.

Format negotiation is a separate modifier. The README lists avif, webp and jpeg as the candidates for `f_auto`, negotiated from the browser's accept header, so a request to `http://localhost:3000/f_auto/static/buffalo.png` returns whichever of those the client accepts. Check the returned content type rather than assuming avif.

For embedding rather than serving, the package exposes a programmatic API and the repository carries three examples: `examples/express.ts`, `examples/h3.ts` and `examples/serve.ts`. The package entry point is `./dist/index.mjs` with types at `./dist/index.d.mts`, and the binary is `./dist/cli.mjs`. The package requires Node `^20.16.0` or newer, and `unstorage` is declared as an optional peer dependency, which suggests a storage-backed source is possible but not mandatory.

## The v4 beta label is a real constraint, not a footnote

The package version in the repository is `4.0.0-beta.1`, and the README opens with a note that this is the active development branch for IPX v4, pointing v3 users at a separate branch for v3 docs. The recent release history backs that up: `4.0.0-alpha.1`, `4.0.0-alpha.2` and `4.0.0-beta.1` are the three most recent tags.

That matters for adoption. If you pin IPX in a production image pipeline, you are pinning a pre-release line, and the URL syntax and modifier names are the surface most likely to move between alpha and beta. The README does not document a rollback path or a migration guide from v3 to v4 beyond the link to the release notes, so a version bump is something you would have to evaluate against your own templates.

There is a second boundary that is easy to underestimate: every unique modifier combination is a fresh transform. A page that requests the same image at ten widths and three formats is asking for up to thirty encodes unless something caches them. IPX itself is not described in the README as a cache. If you put it behind a CDN, the CDN is doing the caching; if you do not, the origin does it every time. That is a deployment decision, not a library feature, and it is the main thing to think through before pointing production traffic at it.

## When IPX is the wrong tool

IPX is the wrong choice when the images are not reachable from the process. The README frames the source as a directory or a list of allowed domains. If your assets live in a private bucket that needs per-request signed credentials, or in a database as blobs, the README does not describe a path for that, and `unstorage` is only an optional peer dependency rather than a documented adapter list.

It is also the wrong tool when the transform has to be approved rather than requested. Nothing in the README mentions signed URLs, expiring tokens, per-user quotas or request logging. A URL like `/w_512,f_webp/photos/buffalo.png` is public by construction once the server is reachable, and anyone who can guess an id can ask for any permitted modifier. If your threat model includes hotlinking or CPU exhaustion from crafted modifier combinations, you are adding a reverse proxy or a rate limiter in front, and IPX is only the transformer.

Finally, if your build already produces a fixed, small set of variants and never changes, a build-time resizer is simpler. You get static files, no runtime dependency on libvips, and no request that can fail with a 400. IPX earns its place when the variant set is open-ended or unknown at build time.

## How IPX differs from a hosted image CDN

The obvious alternative is a hosted image service that takes an origin URL and a transform query and returns a transformed image, with caching, a dashboard and a global edge in front. The difference in approach is where the work and the state live. A hosted CDN owns the cache and the configuration; you send it a URL and trust its edge. IPX is a library and a CLI you run yourself, and the README describes it as resolving images from a directory or an allowed domain list. There is no control plane, no account and no per-transform billing.

That trade is concrete. With IPX you own the compute, the cache and the failure modes, and in exchange the transform rules are visible in your own code and the whole thing is MIT licensed. With a hosted service you own none of that, and the transform rules live in someone else's dashboard.

Within the self-hosted space, the nearest comparison is a build-time resizer driven by the same sharp library. The mechanism differs at the point of decision: a build-time tool decides the variant set when you run the build, while IPX decides it when the request arrives, from the path. That is the whole design, and it is also the whole cost.

## Licence, maintenance and upgrade cost

The repository declares the MIT licence, and the package.json carries `"license": "MIT"`. That is permissive and imposes no copyleft obligation on your own code. It does not settle the licences of the image codecs sharp and libvips link against, which vary by build and platform, so if you redistribute a binary rather than installing from npm, check the codec set you actually ship. That is a packaging question, not something the IPX repository answers.

On maintenance, the last push to the default branch was on 2026-09-28, and the repository is not archived. The recent tags are all v4 pre-releases, so the project is publishing work, but the version string itself says beta.

Upgrade cost is dominated by two things. First, the URL grammar is the public interface, so any rename of a modifier touches every template and stored URL you have. Second, the runtime dependency is sharp, which is a native module; the package requires Node `^20.16.0` or newer, so an upgrade of IPX can force an upgrade of your Node runtime. The README does not document a deprecation policy for modifiers, so treat the modifier table as the thing to re-read on each release.

## Conclusion

Adopt IPX if you already run Node and want size, format and quality variants derived from the URL rather than generated ahead of time, and if you can point it at a directory or a fixed allowlist of domains. Do not adopt it if you need a hosted dashboard, signed URLs or an audit trail, because the README documents none of those. Before rolling it out, confirm two things in your own environment: that sharp resolves for your platform, and that the v4 URL and modifier syntax matches what you plan to put in templates, since v4 is still published as a beta and the README points v3 users at a separate branch for older docs.

## FAQ

### How do I install unjs/ipx and start a server?

The README's quick start runs the CLI without a global install, using npx ipx serve --dir ./ or bunx ipx serve --dir ./ with bun. The server prints a URL, and you append modifiers to any image path under the served directory, for example http://localhost:3000/w_200/buffalo.png.

### What does a modifier URL like /w_512,f_webp/photos/buffalo.png mean?

The route is a list of modifiers followed by the id of the source image. Modifiers are separated by commas and their arguments by underscores, so w_512 sets the width to 512 pixels and f_webp converts the output to WebP. A bare underscore, as in /_/static/buffalo.png, returns the original image.

### Can unjs/ipx pick the best format for the browser automatically?

Yes. The README lists f_auto as negotiating avif, webp or jpeg from the browser's accept header. Other formats are requested explicitly, for example f_webp, and any format not requested is kept as in the source image.

### Is unjs/ipx stable enough for production?

The repository is on the v4 line and the current package version is 4.0.0-beta.1, with the README noting this is the active development branch for IPX v4 and pointing v3 users at a separate branch. The last push to the default branch was on 2026-09-28 and the repository is not archived, so work is ongoing, but the version string itself is a pre-release.

## Sources

- [Issues](https://github.com/unjs/ipx/issues)
- [License: MIT](https://github.com/unjs/ipx/blob/main/LICENSE)
- [README](https://github.com/unjs/ipx/blob/main/README.md)
- [Releases](https://github.com/unjs/ipx/releases)
- [unjs/ipx on GitHub](https://github.com/unjs/ipx)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/unjs-ipx
