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:
wsets the output width.fmtsets 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:
webpavifjpegjpg, as an alias for JPEGpng
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.
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:
{
"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:
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:
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:
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:
Content-Type: image/webp
Content-Type: image/avif
Verify that the original still has its source type:
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:
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:
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:
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:
{
"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_hashmeans the path is not a 64-character lowercase SHA-256 value. - HTTP
404 image_not_foundmeans the hash does not exist on this host. - HTTP
404 image_unpublishedmeans the owner must publish the same URL again. - HTTP
404 image_not_availablecan mean moderation removed the image bytes. - HTTP
409 moderation_pendingoccurs 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_KEYoutside 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.
- Host images for HTML email.
- Store an OpenAI-generated image on a stable URL.
For an agent, follow the integration procedure. For human setup, open the start page. Read the service evaluation before you select a host.
Sources
- imgd.dev OpenAPI document
- imgd.dev agent reference
- Cloudflare Images transformation features
- Current route behavior in
worker/index.ts, checked on 2026-08-20.