CLI
@srcset/cli resizes, converts and optimizes images with sharp from the command line. It takes source globs, the same rules the bundler integrations use, and a destination directory - the result is plain files, with nothing left behind in the build.
With --module it also bakes: alongside the variants it writes an ES module importing them, so a project can commit the result and run without any bundler integration. That mode has its own page; everything below applies to it as well.
Install
Section titled “Install”pnpm add -D @srcset/cliyarn add -D @srcset/clinpm i -D @srcset/cliThe package installs a srcset binary. Run it through the project’s package manager so the local version is used:
pnpm srcset "images/*.jpg" -d static -w 1920,1280,640 -f jpg,webp,avif -vyarn srcset "images/*.jpg" -d static -w 1920,1280,640 -f jpg,webp,avif -vnpm exec srcset -- "images/*.jpg" -d static -w 1920,1280,640 -f jpg,webp,avif -v-v prints every written file, so the run above says exactly what it did:
images/hero.jpg -> static/images/hero@1920w.jpgimages/hero.jpg -> static/images/hero@1280w.jpgimages/hero.jpg -> static/images/hero@640w.jpgimages/hero.jpg -> static/images/hero@1920w.webpimages/hero.jpg -> static/images/hero@1280w.webpimages/hero.jpg -> static/images/hero@640w.webpimages/hero.jpg -> static/images/hero@1920w.avifimages/hero.jpg -> static/images/hero@1280w.avifimages/hero.jpg -> static/images/hero@640w.avifimages/photo.jpg -> static/images/photo@1920w.jpg...Three widths times three formats per source. Keeping jpg in the list is what matters: it is the variant a browser without avif or webp support can load, and the one a --module default export falls back to when the source format is not in the list.
Command
Section titled “Command”srcset [...sources] [...options]| Option | Description |
|---|---|
[...sources] | Glob patterns for the source images. Quote them, or the shell expands them itself. |
--dest, -d | Destination directory. Required, unless the config sets dest. |
--width, -w | Widths to resize to. A value of 1 or less is a multiplier of the source width, above 1 it is absolute pixels and must be an integer. Defaults to the source width. |
--format, -f | Formats to convert to: avif, webp, jpg, png or gif - the first one is the fallback when the source format is not among them. Defaults to the source format. svg is accepted only as a passthrough for svg sources; nothing is ever converted to it. |
--match, -m | Glob pattern or CSS media query to match images by name or size. Repeat to add more - all of them must match. |
--module | Bake an image module: ts, js, ts-dir or js-dir. Off by default. |
--placeholder | Add the placeholder module export - a tiny variant inlined as a data-url. --no-placeholder switches off one enabled in the config. |
--placeholder-width | Placeholder width in pixels, 16 by default. Implies --placeholder. |
--placeholder-format | Placeholder format: webp (default) or jpg. Implies --placeholder. |
--select-id | Module default export: the variant with this resource id, e.g. webp640. |
--select-format | Module default export: the variant of this format. |
--select-width | Module default export: the variant of this width; 1 or less is a multiplier. |
--skip-optimization | Do not re-encode the variant that is neither resized nor converted - it is copied byte for byte - and skip the custom optimization functions. |
--no-scaling-up | Drop the variants requested wider than the source instead of capping them at the source width. |
--concurrency | Concurrency limit of the sharp work. Defaults to the number of available CPU cores. |
--config, -c | Config file path. Defaults to looking up srcset.config.js in the current directory. |
--verbose, -v | Print every written file as source -> output. |
--help, -h | Print the usage and exit. |
Anything the parser did not recognise is reported instead of being taken for a glob:
Unknown option: "--widht".Multi-value flags
Section titled “Multi-value flags”-w and -f take several values either comma-separated or as repeated flags - -w 1920,1280 and -w 1920 -w 1280 are the same thing.
-m is different: its argument is taken as it is, because a comma is a legal character in both a brace glob and a media query list. Pass several patterns as repeated flags, and remember they combine with and:
# only the jpg files whose name starts with "hero"pnpm srcset "images/*" -d static -m '**/*.jpg' -m '**/hero*' -w 1For “either” use one brace glob, -m '**/*.{jpg,png}', or one media query list.
Output paths
Section titled “Output paths”The destination mirrors the source tree relative to the current directory, so images/photo.jpg with -d static lands at static/images/photo.jpg - and src/images/photo.jpg lands at static/src/images/photo.jpg, with the src/ level and all. To flatten the tree, run the command from the directory holding the images:
cd images && pnpm srcset "*.jpg" -d ../static/images -w 1280,640 -f jpg,webpResized variants get an @<width>w postfix built from the actual width: static/images/photo@1280w.jpg. The variant requested as the multiplier 1 gets no postfix at all, so it keeps the source file name.
Sources outside the current directory keep only their file name, which means two of them can land on one output path. That stops the run rather than losing a file quietly:
Output path collision: "static/photo.jpg". Run from a directory containing every source, or process them separately.Config file
Section titled “Config file”Everything the flags cannot express lives in srcset.config.js, looked up in the current directory. It is an ES module with the options object as the default export, so the project needs "type": "module" in its package.json - or an .mjs file passed with --config.
export default { src: 'images/**/*', dest: 'static', rules: [ { match: '**/*.png', width: [1, 0.5], format: ['png', 'webp'] }, { match: '**/*.gif', width: [1, 0.5], format: ['gif', 'webp'] }, { width: [1, 0.5], format: ['jpg', 'webp', 'avif'] } ]}With that file in place the whole run is pnpm srcset.
| Key | Description |
|---|---|
src | Source glob pattern or an array of them. The command line sources win over it. |
dest | Destination directory. |
rules | Array of rules: a match plus what to generate. Defaults to a single empty rule - every image, source width, source format. |
module | ts, js, ts-dir or js-dir. See Baking modules. |
placeholder | true for the defaults, or { width, format }. |
select | { id, format, width } - which variant the module’s default export points at. |
resourceId | (width, requestedWidth, format) => string, the id of a variant. Defaults to `${format}${width}`, e.g. webp640. |
processing | sharp output options per format. Merged per format over the defaults - jpg.mozjpeg and png.palette. |
optimization | Custom optimizer functions per format, (contents, format) => Buffer. The only way to touch svg. |
skipOptimization | Same as --skip-optimization. |
scalingUp | false is the same as --no-scaling-up. |
postfix | A string, or a formatter (width, requestedWidth, format) => string. Defaults to @<width>w, empty for the multiplier 1. |
concurrency | Same as --concurrency. |
verbose | Same as --verbose. |
placeholder, select and resourceId shape the baked module, so they do nothing without module.
rules, placeholder, select, resourceId, processing, optimization, skipOptimization, scalingUp, postfix and concurrency are exactly the options of the bundler integrations, so a config can be lifted from a Vite or webpack setup as is.
Arguments vs config
Section titled “Arguments vs config”An argument wins over the config value it corresponds to. Some of them replace rather than merge:
-m,-wand-fbuild one rule that replaces the wholeruleslist. With the config above,pnpm srcset -f webpdoes not add webp to the three rules - it generates a single webp variant of every image, png and gif included.- The select flags replace the whole
selectobject.--select-format webpdrops aselect.widthfrom the config. - The placeholder flags replace the whole
placeholderobject.--placeholder-width 24against a configplaceholder: { format: 'jpg' }leaves{ width: 24 }, back on the default webp.--no-placeholderbeats them all and switches the export off.
So only two things actually require the config file, because no flag can express them: more than one rule, and the options that are functions or nested objects - resourceId, processing, optimization, a formatter postfix, a matcher function. Everything else has a flag.
Recipes
Section titled “Recipes”Convert a folder of photos to modern formats, keeping the original as the fallback (a folder with png or gif in it needs rules of their own - a flat -f list would convert them too):
pnpm srcset "assets/**/*.jpg" -d dist -f jpg,webp,avifMake one image responsive at several widths:
pnpm srcset "images/hero.jpg" -d static -w 1920,1280,640 -f jpg,webpHalf-size copies of everything, originals untouched:
pnpm srcset "images/*" -d thumbs -w 0.5Re-encode a whole tree with the sharp defaults - no resizing, no conversion:
pnpm srcset "images/**/*" -d optimizedMake it repeatable: put the sources, the destination and the rules in srcset.config.js and add a script.
{ "scripts": { "images": "srcset" }}Verify
Section titled “Verify”Run with -v and read the source -> output lines: one per written variant, plus one per module when baking. The count should match the rule that matched - widths times formats. Then check the destination tree.
Nothing is written when nothing matched, and that is a silent, successful run - see the pitfalls below.
Pitfalls
Section titled “Pitfalls”- Quote the source globs. Unquoted, the shell expands them first:
**silently loses its recursive meaning in shells withoutglobstar, and zsh errors out when a pattern matches nothing. --width 0.5is a multiplier,--width 500is pixels.-w 1means “the source width”, which is how the untouched-size variant is requested. A fractional value above 1 is rejected:Invalid width: 1.5.- Widths above the source width are capped, not upscaled. For a 640px source,
-w 1920,1280,640resolves all three to the same file,thumb@640w.jpg, written once. With--no-scaling-upthose variants are dropped instead - and a run asking only for oversized widths then writes nothing at all. - Without
-wand-fthe run only re-encodes the originals into the destination. A valid optimize-only pass, but it produces nosrcset. - A flat
-flist converts everything it matched.-f jpg,webpover a folder with a png turns the png into a jpg, transparency and all. Per-format rules need the config file - that is what the**/*.pngand**/*.gifrules in the example above are for. - An image no rule matched is skipped silently. Nothing is written for it, the run exits
0, and in--modulemode no module is written either. The same happens to an svg under a raster-only-f: svg passes through only when the format list is unset or includessvg. - A source sharp cannot decode fails the whole run:
Cannot read image "images/broken.jpg": Input buffer contains unsupported image format. That is deliberate - a corrupt file the run was pointed at should not be passed over as an unmatched image. - Re-running never cleans the destination. Dropping a width or a format leaves the files it used to write in place; delete the destination directory when the rules change.