npx skills add ...
npx skills add netlify/context-and-tools --skill netlify-image-cdn
Transform, resize, crop, reformat, and optimize images on demand via Netlify Image CDN's /.netlify/images endpoint. Use when adding responsive images, generating thumbnails, converting formats (avif/webp/png), cropping to aspect ratios, tuning image quality, creating blurred placeholders, allowlisting remote image domains, serving user-uploaded images, or wiring framework image components (Next.js, Astro, Nuxt, Angular, Gatsby) to Netlify. Triggers on tasks like "optimize images", "add image thumbnails", "resize images on the fly", "serve images from an external domain", or "add blur placeholders".
npx skills add netlify/context-and-tools --skill netlify-image-cdn
Transform images by requesting /.netlify/images with query parameters. No function or file authoring required — it's a built-in edge endpoint.
There is no legacy/deprecated form — the endpoint above is the only programmatic surface. Use framework image components where available (below) rather than hand-building URLs.
GET /.netlify/images?url=<source>&...
| Param | Values | Notes |
|---|---|---|
url | relative path or full remote URL | REQUIRED. Only required param. |
w | integer px | width |
h | integer px | height |
fit | contain (default), cover, fill | resize behavior |
position | center (default), top, bottom, left, right | only applies when fit=cover |
fm | avif, jpg, png, webp, gif, blurhash | output format; webp/gif can be animated |
q | integer 1–100 (default 75) | only for avif, jpg, gif, webp |
fit behaviorfit= | aspect ratio kept | crops excess | returns exact dimensions |
|---|---|---|---|
contain | yes | no | no — one dimension may be smaller |
cover | no | yes | yes — scaled proportionally, then cropped |
fill | no | no | yes — stretched/squished if needed |
fit=cover requires BOTH w and h. Supplying only one silently misbehaves.contain with one dimension calculates the other to preserve aspect ratio.url, no size/format): image is unchanged in size/shape but still reformatted to avif/webp based on the browser's Accept header.fm specified → webp if accepted, else avif if accepted, else original.fm=blurhash returns a BlurHash text string, not image bytes. Pointing <img src> or a CSS background at it renders nothing. Fetch the string server-side/ahead of time, decode it client-side with a BlurHash library (https://blurha.sh), then load the real image as a separate request without fm=blurhash.404.200 with content + content-type.304.Remote url values require allowlisting the domain in netlify.toml:
Then percent-encode the remote URL and request it:
encodeURIComponent the remote URL before placing it in url — URLs containing ? or & break otherwise.remote_images patterns, escape only the dot: 'https://example\.com/.*'. Forward slashes are NOT regex metacharacters — do not write https:\/\/.Authorization or Cookie headers to remote sources. For auth-required images use self-authorizing URLs (e.g. S3 presigned URLs) and make sure your remote_images pattern matches them.Reuse the same params across many images via a redirect:
_redirects:
netlify.toml:
Then GET /transform-small/owl.jpeg yields a 50×50 transform. Avoid cross-site redirects for transformations — they hurt performance.
_headers:
Cache-Control on source images applies only to browsers/CDNs in front of Netlify, not the Netlify Cache itself.Use the framework's native image component/handling; it wires to Image CDN automatically. Configure the remote allowlist per framework:
| Framework | Prerequisite | Remote allowlist |
|---|---|---|
| Angular | none — NgOptimizedImage auto-uses it | [images] remote_images in netlify.toml |
| Astro | none — <Image /> auto-uses it | image.domains / image.remotePatterns in astro.config.mjs |
| Nuxt | none — nuxt/image auto-uses it | image.domains in nuxt.config.ts |
| Next.js | Next 13.5+ and adapter v5 | remotePatterns in next.config.js |
| Gatsby | env NETLIFY_IMAGE_CDN=true + Contentful/Drupal/WordPress source plugin | [images] remote_images in netlify.toml |
Run netlify dev (Netlify CLI) to test transformations locally — it mimics production including Image CDN.
404 on /.netlify/images almost always means a framework dev server (vite, next dev, astro dev) is running instead of netlify dev. The endpoint, [images] allowlisting, and image redirects only exist under netlify dev. The URL itself is usually fine.Transformed results are uniquely cached on Netlify's edge. Atomic deploys are respected: changing a source image in a new deploy re-runs transformations on new requests so stale assets aren't served.
For user-uploaded image pipelines (Functions + Blobs + Image CDN composed), see references/user-uploads.md in this skill.
These are org conventions, not docs facts — merged into the rendered skill by ctx-gen and never generated. Owned by the skills maintainer.
references/user-uploads.md in this skill — an authored
guide with no single docs source.url
parameter (encodeURIComponent) — URLs containing ? or & break
otherwise.fm=blurhash returns a BlurHash TEXT string, not image bytes. Pointing an
<img src> (or CSS background) at it renders nothing — fetch the string
ahead of time, decode it client-side with a BlurHash library, and load the
real image as a separate request without fm=blurhash./.netlify/images almost always means a framework dev
server (vite, next dev, astro dev) is running instead of
netlify dev — the endpoint, [images] allowlisting, and image redirects
only exist under netlify dev. The URL itself is usually fine.remote_images patterns, the meaningful regex escape is the dot;
forward slashes are not metacharacters — do not write https:\/\/.
In netlify.toml, use a single-quoted literal string
('https://example\.com/.*') or double the backslash in a
double-quoted string ("https://example\\.com/.*") — a bare \.
inside double quotes is invalid TOML.const src = `/.netlify/images?url=${encodeURIComponent("https://my-images.com/owl.jpeg")}`;/transform-small/* /.netlify/images?url=/:splat&w=50&h=50 200[[redirects]]
from = "/transform-small/*"
to = "/.netlify/images?url=/:splat&w=50&h=50"
status = 200/source-images/*
Cache-Control: public, max-age=604800, must-revalidate