---
title: Resize and Convert a Hosted Image to WebP or AVIF by URL
description: Add width and format query values to an imgd.dev URL, then verify the output type and dimensions.
slug: resize-convert-image-webp-avif
date: 2026-08-20
updated: 2026-08-20
last_tested: 2026-08-20
summary: Add w and fmt query values to one public image URL to request resized WebP, AVIF, JPEG, or PNG output.
cluster: Automation workflows
intent: workflow
sources:
  - title: imgd.dev OpenAPI document
    url: https://imgd.dev/openapi.json
  - title: imgd.dev agent reference
    url: https://imgd.dev/llms.txt
  - title: Cloudflare Images transformation features
    url: https://developers.cloudflare.com/images/optimization/features/
---

# Resize and Convert a Hosted Image to WebP or AVIF by URL

Add `?w=800&fmt=webp` or `?w=800&fmt=avif` to an imgd.dev image URL. The original content-addressed URL remains available without the query string. Wait for a `live` result before you verify the final format.

imgd.dev charges a one-time **$1 per GB** of source-image storage. It has **no free tier**, so fund the account before the first upload. Every original and transformed imgd.dev image is public. Do not transform private or confidential content.

## Current accepted transform values

The current image route reads two query values:

- `w` sets the output width.
- `fmt` sets the output format.

Use a positive whole-number width from `1` through `4096`. The route clamps a larger positive numeric value to `4096`.

A missing, zero, negative, or nonnumeric `w` value does not resize the image. Use the documented whole-number range instead of depending on coercion behavior.

The current route accepts these `fmt` values:

- `webp`
- `avif`
- `jpeg`
- `jpg`, as an alias for JPEG
- `png`

The route converts the format value to lower case. Use the lower-case forms for clear URLs.

Do not use another `fmt` value. The current route can fall back to the source type instead of returning a format error.

## Upload one source image

Use a source wider than 800 pixels for this example. Keep `IMGD_KEY` outside the repository.

```bash
set -euo pipefail
test -n "$IMGD_KEY"

response_file="$(mktemp)"
trap 'rm -f "$response_file"' EXIT
http_status="$(
  curl --silent --show-error \
    --output "$response_file" \
    --write-out '%{http_code}' \
    -X POST https://imgd.dev/v1/upload \
    -H "Authorization: Bearer $IMGD_KEY" \
    -F "file=@source-image.png;type=image/png"
)"

if [[ "$http_status" != "200" && "$http_status" != "202" ]]; then
  jq . "$response_file" >&2
  exit 1
fi

cp "$response_file" upload.json
jq . upload.json
```

A new source returns HTTP `202` with this complete JSON shape:

```json
{
  "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "url": "https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "status": "processing",
  "mime": "image/png",
  "bytes": 18452,
  "width": 1200,
  "height": 800,
  "alt_text": null,
  "unpublish_at": null,
  "note": "the url works immediately, serving a blurred placeholder until the moderation check finishes"
}
```

An identical source can return HTTP `200` with `"deduplicated": true`.

The immediate URL can serve a blurred JPEG while moderation runs. That temporary response ignores the final transform format.

## Wait for the final image state

Poll the authenticated metadata endpoint with a fixed limit:

```bash
HASH="$(jq -r '.hash' upload.json)"
for attempt in $(seq 1 20); do
  curl --fail --silent --show-error \
    -H "Authorization: Bearer $IMGD_KEY" \
    "https://imgd.dev/v1/images/$HASH" > metadata.json
  status="$(jq -r '.status' metadata.json)"
  case "$status" in
    live)
      jq . metadata.json
      break
      ;;
    review|blocked|error)
      jq . metadata.json >&2
      exit 1
      ;;
    processing)
      if [[ "$attempt" == "20" ]]; then
        echo "Moderation did not finish within 60 seconds." >&2
        exit 1
      fi
      sleep 3
      ;;
    *)
      echo "Unexpected image status: $status" >&2
      exit 1
      ;;
  esac
done
```

A `review` result is terminal and serves a blurred image. A `blocked` result removes the bytes. Do not build production variants from either result.

## Build WebP and AVIF URLs

Validate and keep the original URL:

```bash
ORIGINAL_URL="$(
  jq -er \
    '.url | select(test("^https://i\\.imgd\\.dev/i/[0-9a-f]{64}$"))' \
    upload.json
)"
WEBP_URL="${ORIGINAL_URL}?w=800&fmt=webp"
AVIF_URL="${ORIGINAL_URL}?w=800&fmt=avif"

printf 'Original: %s\n' "$ORIGINAL_URL"
printf 'WebP:    %s\n' "$WEBP_URL"
printf 'AVIF:    %s\n' "$AVIF_URL"
```

The hash path stays the same for all three URLs. Only the query string requests a derivative.

Use the original URL when a client cannot use the requested format. Removing the query string does not modify or replace the stored source.

## Verify response headers

Download each derivative and save its headers:

```bash
curl --fail --silent --show-error \
  -D resized-webp.headers \
  -o resized.webp \
  "$WEBP_URL"

curl --fail --silent --show-error \
  -D resized-avif.headers \
  -o resized.avif \
  "$AVIF_URL"

grep -i '^content-type: image/webp' resized-webp.headers
grep -i '^content-type: image/avif' resized-avif.headers
file resized.webp resized.avif
```

The expected media types are:

```text
Content-Type: image/webp
Content-Type: image/avif
```

Verify that the original still has its source type:

```bash
curl --fail --silent --show-error \
  -D original.headers \
  -o original.png \
  "$ORIGINAL_URL"
grep -i '^content-type: image/png' original.headers
```

## Verify output dimensions

If ImageMagick is installed, inspect both derivative files:

```bash
identify -format '%m %wx%h\n' resized.webp resized.avif
```

For a source wider than 800 pixels, each output should report a width of 800. The transform preserves the source aspect ratio when only `w` is set.

If the source is narrower than your target, select a smaller width for a clear downsize check. Also compare the files visually for acceptable quality.

## Other valid URLs

Use the same width rules with JPEG or PNG:

```text
https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef?w=800&fmt=jpeg
https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef?w=800&fmt=jpg
https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef?w=800&fmt=png
```

The `jpg` and `jpeg` values both return `Content-Type: image/jpeg`.

You can also request only a resize:

```text
https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef?w=800
```

That URL keeps the source output type.

## Recover from upload errors

| Status | Meaning | Recovery |
| --- | --- | --- |
| `401` | The key is missing or invalid. | Set the correct `IMGD_KEY`, then retry. |
| `402` | The account has no storage. | Buy storage and verify funding. There is no free tier. |
| `402` | The source exceeds the quota. | Add the suggested storage or delete unused images. |
| `413` | The source exceeds 20 MB. | Resize or compress it before upload. |
| `415` | The source type is not accepted. | Use JPEG, PNG, GIF, WebP, or AVIF with the matching type. |
| `429` | The account exceeded the rate limit. | Wait for the next minute, then retry. |

A representative `413` response is:

```json
{
  "error": "file_too_large",
  "fix": "file exceeds 20MB; resize the image below that and retry",
  "bytes": 24117248,
  "max_bytes": 20971520
}
```

## Recover from transform errors

- HTTP `400 invalid_hash` means the path is not a 64-character lowercase SHA-256 value.
- HTTP `404 image_not_found` means the hash does not exist on this host.
- HTTP `404 image_unpublished` means the owner must publish the same URL again.
- HTTP `404 image_not_available` can mean moderation removed the image bytes.
- HTTP `409 moderation_pending` occurs when a processing URL requests JSON.

If a transform returns the source media type, check the `fmt` spelling first. Use only the accepted values in this guide.

## Security notes

- Keep `IMGD_KEY` outside source files, URLs, and logs.
- Treat the original and all derivative URLs as public.
- Do not put an access token in a transform query string.
- Remove sensitive metadata before the source upload.
- Keep the original URL for format fallback and audits.
- Delete your claim when you no longer need the source. Another account can keep identical bytes public.

## When imgd.dev fits

imgd.dev fits simple public resizing and format conversion from one stable source URL. It supports common documentation, email, and report variants.

It does not fit private transformations, signed URLs, complex crop controls, video, a free trial, or files above 20 MB. Use a full media platform for broader workflows.

## Next guides

- [Host Puppeteer screenshots on a public URL](/blog/puppeteer-screenshot-upload/).
- [Host images for HTML email](/blog/host-images-html-email/).
- [Store an OpenAI-generated image on a stable URL](/blog/store-openai-generated-images/).

For an agent, follow the [integration procedure](/integrate.md). For human setup, open [the start page](/#start). Read the [service evaluation](/evaluate.md) before you select a host.

## Sources

- [imgd.dev OpenAPI document](https://imgd.dev/openapi.json)
- [imgd.dev agent reference](https://imgd.dev/llms.txt)
- [Cloudflare Images transformation features](https://developers.cloudflare.com/images/optimization/features/)
- Current route behavior in `worker/index.ts`, checked on 2026-08-20.
