Quick examples

Every recipe below runs as written. The flags are documented on the CLI page and the keys on Configuration โ€” this page is only the composition.

Squash one image

Output defaults to the current directory, so this rewrites nothing and drops the compressed copy where you are:

npx poops-images photo.jpg --out ./out

Squash a whole directory

Structure is preserved, so src/images/gallery/photo.jpg lands at dist/images/gallery/photo.jpg:

npx poops-images --in src/images --out dist/images

Convert to WebP, and go lighter

npx poops-images --in src/images --out dist/images --format webp
npx poops-images --in src/images --out dist/images --format webp -q 60

Let it pick the smaller file

smart encodes both JPEG and WebP for every variant and keeps whichever came out smaller โ€” which is the only honest way to answer the question, because the answer changes per image:

npx poops-images --in src/images --out dist/images --format smart

Add AVIF next to the smart pick when you want the modern format as well:

npx poops-images --in src/images --out dist/images --format smart,avif

A responsive set for a srcset

Three widths, two formats, per-format quality โ€” six variants per image plus the original:

npx poops-images src/images --out dist/images \
  --widths 300,768,1024 \
  --format webp,avif \
  --quality webp:70,avif:50

The filenames come out as photo-300w.webp, photo-768w.webp, โ€ฆ which is exactly what Poops reads to assemble the srcset for you.

Square thumbnails, cropped from the center

Widths on the command line are proportional; a hard crop needs a config file:

{
  "in": "src/images",
  "out": "dist/images",
  "sizes": [
    { "name": "thumb", "width": 300, "height": 300, "crop": true }
  ]
}
npx poops-images

A hero banner that keeps the sky

A wide, short crop anchored to the top, so the horizon survives and the ground is what goes:

{
  "in": "src/images",
  "out": "dist/images",
  "sizes": [
    { "name": "hero", "width": 1920, "height": 600, "crop": ["center", "top"] }
  ]
}

The other eight anchors are in Sizes and crops.

A WordPress-shaped size set

The four sizes WordPress gives you, if that is the vocabulary already in your head:

{
  "in": "src/images",
  "out": "dist/static/images",
  "sizes": [
    { "name": "thumbnail", "width": 150, "height": 150, "crop": true },
    { "name": "medium", "width": 300, "height": 300 },
    { "name": "medium_large", "width": 768, "height": 0 },
    { "name": "large", "width": 1024, "height": 1024 }
  ]
}

A blurred placeholder to load first

The LQIP trick: a 32px-wide blurred copy, small enough to inline, shown until the real image arrives. skipOriginal keeps it from also emitting a full-size blur nobody wants:

{
  "in": "src/images",
  "out": "dist/images",
  "sizes": [{ "name": "large", "width": 1600 }],
  "preprocessors": [
    {
      "name": "lqip",
      "operations": [{ "type": "blur", "sigma": 30 }],
      "sizes": [{ "width": 32 }],
      "skipOriginal": true
    }
  ]
}

Out comes photo-large-1600w.jpg and photo-lqip-32w.jpg.

A grayscale copy for a hover effect

{
  "name": "gray",
  "operations": [{ "type": "grayscale" }],
  "sizes": [{ "name": "card", "width": 400, "height": 300, "crop": true }],
  "skipOriginal": true
}

input resolves relative to the config file, so the watermark travels with the config:

{
  "name": "watermarked",
  "operations": [
    { "type": "composite", "input": "assets/watermark.png", "gravity": "southeast" }
  ]
}

A one-off filter with no config file

npx poops-images --in src/images --out dist --preprocess blur:20
npx poops-images --in src/images --out dist --preprocess grayscale,blur:10

Files come out named photo-preprocessed-โ€ฆ, because a CLI preprocessor has no name to give them.

Work while you work

Watch mode processes only what changed, and removes the variants of a source you delete:

npx poops-images --in src/images --out dist/images --watch

Find out what it would do

--dry-run writes nothing and logs the plan. Pair it with --force when the output looks stale and you want to know whether the cache is the reason:

npx poops-images --in src/images --out dist/images --dry-run
npx poops-images --in src/images --out dist/images --force --verbose

Tip

--verbose is worth having on any run you are debugging โ€” quiet is the default, so without it a skipped file and a processed file look identical from outside.