---
title: Preset steps
url: https://base_url.placeholder/docs/presets/steps
group: Concepts
---

# Preset steps

A preset is a saved list of steps. Each step is an op from [the op catalogue](https://base_url.placeholder/docs/ops) wherever one exists — `{op, parameters}`, with a closed, validated parameter set. Where no op covers what you need — a sharpen, a colour matrix, a progressive JPEG — a step names one of the processing engine’s own operations instead: `{operation, params}`. This page is that list. You write it when you author a preset; whatever runs the preset later only names it.

```json
{
  "name": "Print sharpen",
  "slug": "print-sharpen",
  "steps": [
    {
      "op": "resize",
      "parameters": {
        "width": 1800,
        "height": 1200,
        "fit": "inside"
      }
    },
    {
      "operation": "sharpen",
      "params": {
        "sigma": 1.5,
        "m1": 1.5,
        "m2": 0.7
      }
    },
    {
      "operation": "jpeg",
      "params": {
        "quality": 100,
        "progressive": true
      }
    }
  ]
}
```

## Five rules

1. **Params go to Sharp verbatim.** `params` is handed to the [Sharp](https://sharp.pixelplumbing.com) method of the same name, so the signature is Sharp’s at the version the processing image pins. Each row below links to it; nothing here restates it.
2. **A parameter that names another image takes one of your assets — `{"assetId": "<id>"}` — or inline data.** That is `composite.images[].input`, `joinChannel.images[]`, `boolean.operand` and `overlay.input`. The asset has to be yours — any other id is `404 not_found` when the job is submitted, dry run included, and one still uploading is `400 invalid_state` — and a step that reads one runs as a job, never synchronously. A string is refused: a storage key is not a caller’s to name, and anything else the engine would read as a path on its own disk. For the same reason a list is refused there (Sharp reads it as paths to join), and so is a `{text: {fontfile}}` layer — name an installed font with `font`.
3. **Three keys are decoder directives, not Sharp methods.** `autoOrient`, `density` and `animated` are read before the first byte is decoded, because by then the way the file is opened is already decided. Their position in `steps` does not matter.
4. **Finishers run last, in your order; `maxBytes` runs after them.** `mask`, `blurRegion`, `overlay` and `caption` need the finished pixels’ size, so they run once every other step has, as one composite and one encode. A `mask` declared before a `blurRegion` erases it. The byte budget is measured on the very end, so it is always the final act.
5. **An unknown key is refused when you save; a wrong parameter shape, when the step runs.** Saving checks the key (`400 invalid_param` on `steps[i].operation`) and the image parameters of rule 2, not the rest of `params` — that is Sharp’s signature, and a second copy would drift. Sharp checks it as the step is built: synchronously the call is `400 invalid_param` naming the key; in a job the item fails on its first attempt with `invalid_param` and `retryable: false`. Never retried, never silently skipped.

One default sits under all of it: every pipeline orients the image by its EXIF tag before your first step, so `crop`, `pad` and `resize` speak the coordinates you see in a viewer. To keep the stored pixels untouched, add `{"operation": "autoOrient", "params": false}`.

## Stability

The keys below are a closed set, and the set is what is promised. A key can be removed once an op replaces it — the _replaced by_ column says which, and a step that can be written as an op should be. Parameter signatures follow the pinned Sharp version; a Sharp upgrade that changes a signature changes it here, without a compatibility layer. Ops keep a stable, validated contract for exactly this reason: prefer them.

## Transform

| Key | Sharp | Replaced by | Note |
| --- | --- | --- | --- |
| `resize` | [resize](https://sharp.pixelplumbing.com/api-resize#resize) | `resize` | kernel and withoutReduction are registry-only; the op takes background with fit: contain only. |
| `extend` | [extend](https://sharp.pixelplumbing.com/api-resize#extend) | `pad` | extendWith mirror / repeat is registry-only. No background: transparent, white on JPEG. |
| `extract` | [extract](https://sharp.pixelplumbing.com/api-resize#extract) | `crop` | Before resize crops then scales; after it scales then crops. |
| `trim` | [trim](https://sharp.pixelplumbing.com/api-resize#trim) | `trim` | lineArt and margin are registry-only. |
| `rotate` | [rotate](https://sharp.pixelplumbing.com/api-operation#rotate) | `rotate` | A number, or {angle, options: {background}}. Does not stack with the default orientation. |
| `flip` · `flop` | [flip](https://sharp.pixelplumbing.com/api-operation#flip) | `flip · flop` | Vertical / horizontal mirror. Each is a switch, not an action: writing it twice flips once. |
| `affine` | [affine](https://sharp.pixelplumbing.com/api-operation#affine) | — | Shear and arbitrary affine transforms. |
| `autoOrient` | [autoorient](https://sharp.pixelplumbing.com/api-operation#autoorient) | — | Every pipeline already orients by EXIF; the only meaningful value is params: false to keep the stored pixels. |

## Colour

| Key | Sharp | Replaced by | Note |
| --- | --- | --- | --- |
| `sharpen` | [sharpen](https://sharp.pixelplumbing.com/api-operation#sharpen) | — | {} or no params is the fast sharpen. |
| `median` | [median](https://sharp.pixelplumbing.com/api-operation#median) | — | Denoise. |
| `blur` | [blur](https://sharp.pixelplumbing.com/api-operation#blur) | — | Whole-image blur. A region is the blur_region op. |
| `flatten` | [flatten](https://sharp.pixelplumbing.com/api-operation#flatten) | `flatten` | Added for you when convert / compress writes JPEG. |
| `unflatten` | [unflatten](https://sharp.pixelplumbing.com/api-operation#unflatten) | — |   |
| `gamma` | [gamma](https://sharp.pixelplumbing.com/api-operation#gamma) | — | A number 1.0–3.0, or {gamma, gammaOut}. |
| `negate` | [negate](https://sharp.pixelplumbing.com/api-operation#negate) | — |   |
| `normalise` · `normalize` | [normalise](https://sharp.pixelplumbing.com/api-operation#normalise) | — | Stretch contrast between two percentiles. |
| `clahe` | [clahe](https://sharp.pixelplumbing.com/api-operation#clahe) | — | Local histogram equalisation. |
| `convolve` | [convolve](https://sharp.pixelplumbing.com/api-operation#convolve) | — | {width, height, kernel[], scale, offset}. |
| `threshold` | [threshold](https://sharp.pixelplumbing.com/api-operation#threshold) | — | A number 0–255, or {value, greyscale}. |
| `boolean` | [boolean](https://sharp.pixelplumbing.com/api-operation#boolean) | — | operand is another image: one of your assets, {"assetId": "<id>"} (job lane only), or inline bytes. |
| `linear` | [linear](https://sharp.pixelplumbing.com/api-operation#linear) | `adjust (contrast)` | {a, b}, per channel as arrays. adjust's contrast is a = c, b = 128 (1 − c). |
| `recomb` | [recomb](https://sharp.pixelplumbing.com/api-operation#recomb) | — | A 3×3 or 4×4 colour matrix: sepia, duotone. |
| `modulate` | [modulate](https://sharp.pixelplumbing.com/api-operation#modulate) | `adjust` | brightness, saturation, hue, lightness. |
| `tint` | [tint](https://sharp.pixelplumbing.com/api-operation#tint) | — | A CSS colour. |
| `greyscale` · `grayscale` | [greyscale](https://sharp.pixelplumbing.com/api-operation#greyscale) | `grayscale` |   |
| `pipelineColourspace` · `pipelineColorspace` | [pipelinecolourspace](https://sharp.pixelplumbing.com/api-operation#pipelinecolourspace) | — | rgb16, scrgb: a 16-bit or linear-light pipeline. |
| `toColourspace` · `toColorspace` | [tocolourspace](https://sharp.pixelplumbing.com/api-operation#tocolourspace) | `colorSpace=srgb (shared)` | The shared parameter also attaches the profile; other spaces are registry-only. |
| `dilate` · `erode` | [dilate](https://sharp.pixelplumbing.com/api-operation#dilate) | — | Morphology, width in pixels. |

## Channel

| Key | Sharp | Replaced by | Note |
| --- | --- | --- | --- |
| `removeAlpha` | [removealpha](https://sharp.pixelplumbing.com/api-channel#removealpha) | — |   |
| `ensureAlpha` | [ensurealpha](https://sharp.pixelplumbing.com/api-channel#ensurealpha) | — | 0–1. |
| `extractChannel` | [extractchannel](https://sharp.pixelplumbing.com/api-channel#extractchannel) | — | 0–3, or red / green / blue / alpha. |
| `joinChannel` | [joinchannel](https://sharp.pixelplumbing.com/api-channel#joinchannel) | — | {images: […]}; an entry is one of your assets, {"assetId": "<id>"} (job lane only), or inline — never a string. |
| `bandbool` | [bandbool](https://sharp.pixelplumbing.com/api-channel#bandbool) | — | and / or / eor. |

## Format

The last format step decides the result's Content-Type. With no format step the result keeps the input's format when it is one of the seven below; anything else — a RAW, a HEIF, a PSD, an SVG — comes out in a web format the engine picks (lossless WebP or PNG), so name a format step when the format matters. maxBytes is not an encoder option: on a jpeg, webp, avif or heif step it is the byte budget the engine meets by re-encoding after everything else has run.

| Key | Sharp | Replaced by | Note |
| --- | --- | --- | --- |
| `jpeg` | [jpeg](https://sharp.pixelplumbing.com/api-output#jpeg) | `convert · compress` | Encoder options pass through; mozjpeg is built in. |
| `png` | [png](https://sharp.pixelplumbing.com/api-output#png) | `convert · compress` | palette: true is pngquant-style quantisation, often half the bytes — registry-only. |
| `webp` | [webp](https://sharp.pixelplumbing.com/api-output#webp) | `convert · compress` | Animated input passes through with frame: all. |
| `gif` | [gif](https://sharp.pixelplumbing.com/api-output#gif) | `convert` | Lossless; maxBytes does not apply. |
| `avif` | [avif](https://sharp.pixelplumbing.com/api-output#avif) | `convert · compress` |   |
| `heif` | [heif](https://sharp.pixelplumbing.com/api-output#heif) | — | compression is av1 only (the result is AVIF); hevc is 400. |
| `tiff` | [tiff](https://sharp.pixelplumbing.com/api-output#tiff) | `convert` | Pyramid and tiled TIFF can be written. |
| `toFormat` | [toformat](https://sharp.pixelplumbing.com/api-output#toformat) | — | {format, options}; only the seven formats above. |

## Metadata

By default the output carries no EXIF, ICC or XMP — only the oriented pixels. Keeping any of it is an explicit step here, or metadata: keep on an op.

| Key | Sharp | Replaced by | Note |
| --- | --- | --- | --- |
| `keepExif` | [keepexif](https://sharp.pixelplumbing.com/api-output#keepexif) | — |   |
| `withExif` | [withexif](https://sharp.pixelplumbing.com/api-output#withexif) | — | Replaces the whole EXIF block: camera and GPS are gone with it. |
| `withExifMerge` | [withexifmerge](https://sharp.pixelplumbing.com/api-output#withexifmerge) | — | Merges into the original EXIF — a copyright written this way keeps the GPS too. |
| `keepIccProfile` | [keepiccprofile](https://sharp.pixelplumbing.com/api-output#keepiccprofile) | — |   |
| `withIccProfile` | [withiccprofile](https://sharp.pixelplumbing.com/api-output#withiccprofile) | `colorSpace=srgb (shared)` | Built-in names only: srgb, p3, cmyk. |
| `keepXmp` · `withXmp` | [keepxmp](https://sharp.pixelplumbing.com/api-output#keepxmp) | — |   |
| `keepMetadata` | [keepmetadata](https://sharp.pixelplumbing.com/api-output#keepmetadata) | `metadata=keep (shared)` | EXIF, ICC, XMP and IPTC, all of it. |
| `withMetadata` | [withmetadata](https://sharp.pixelplumbing.com/api-output#withmetadata) | — | Sets DPI via density — and keeps every block, GPS included. Setting a print resolution publishes the shooting location. Its icc takes srgb, p3 or cmyk only. |
| `timeout` | [timeout](https://sharp.pixelplumbing.com/api-output#timeout) | — | {seconds} for the pipeline. |

## Composite

| Key | Sharp | Replaced by | Note |
| --- | --- | --- | --- |
| `composite` | [composite](https://sharp.pixelplumbing.com/api-composite#composite) | `overlay · caption · mask · pad` | The general layer entry, for blends and per-pixel placement the four ops do not express. input is one of your assets — {"assetId": "<id>"} — or {create}, {text} or SVG/PNG bytes; job lane only. |

## Decoder directives

Not Sharp methods: these are read before the first byte is decoded, so the engine knows how to open the file.

| Key | Sharp | Replaced by | Note |
| --- | --- | --- | --- |
| `density` | not a Sharp method | `density (shared)` | DPI to rasterise a vector input at (72 when unset); ignored for rasters. |
| `animated` | not a Sharp method | `frame=all (shared)` | true reads every frame; the default reads the first. |

## Finishers

Not Sharp methods: they need the finished pixels' size, so they run after every other step, in the order you declare them, as one composite and one encode. maxBytes always runs last.

| Key | Sharp | Replaced by | Note |
| --- | --- | --- | --- |
| `mask` | not a Sharp method | `mask` | {shape: circle \| rounded, radius}. |
| `blurRegion` | not a Sharp method | `blur_region` | {left, top, width, height, sigma \| pixelate}. |
| `overlay` | not a Sharp method | `overlay` | {input: {"assetId": "<id>"}, gravity, opacity, scale, margin, tile}; job lane only. |
| `caption` | not a Sharp method | `caption` | {text, font, size, color, gravity, margin, background}. |

What a preset is, how versions and subjects work, and how to run one: [presets](https://base_url.placeholder/docs/presets). Worked examples, ready to save: [recipes](https://base_url.placeholder/docs/recipes). The ops themselves, with their parameter contracts: [the op catalogue](https://base_url.placeholder/docs/ops).
