withastro/astro · error · Error
Configured image service is not a local service
Error message
Configured image service is not a local service
What it means
The dev/SSR image optimization endpoint (GET in generic.ts) calls getConfiguredImageService() and requires a LocalImageService (one exposing a transform method). External image services (CDN passthrough that only generate URLs) lack transform, so the endpoint cannot optimize and throws a plain Error.
Source
Thrown at packages/astro/src/assets/endpoint/generic.ts:19
// @ts-expect-error
import { imageConfig } from 'astro:assets';
import { isRemotePath } from '@astrojs/internal-helpers/path';
import { isRemoteAllowed } from '@astrojs/internal-helpers/remote';
import * as mime from 'mrmime';
import type { APIRoute } from '../../types/public/common.js';
import { getConfiguredImageService } from '../internal.js';
import { etag } from '../utils/etag.js';
import { loadImage } from './loadImage.js';
/**
* Endpoint used in dev and SSR to serve optimized images by the base image services
*/
export const GET: APIRoute = async ({ request }) => {
try {
const imageService = await getConfiguredImageService();
if (!('transform' in imageService)) {
throw new Error('Configured image service is not a local service');
}
const url = new URL(request.url);
const transform = await imageService.parseURL(url, imageConfig);
if (!transform?.src) {
throw new Error('Incorrect transform returned by `parseURL`');
}
let inputBuffer: ArrayBuffer | undefined = undefined;
const isRemoteImage = isRemotePath(transform.src);
if (isRemoteImage && isRemoteAllowed(transform.src, imageConfig) === false) {
return new Response('Forbidden', { status: 403 });
}
const sourceUrl = new URL(transform.src, url.origin);View on GitHub (pinned to d081033d5f)
Solutions
- Use a local service (e.g. @astrojs/sharp) if you rely on the built-in optimization endpoint.
- If you use an external service, depend on its generated URLs instead of calling the local endpoint.
- Ensure any custom service implements transform (extend baseService and add it).
Example fix
// before - astro.config.mjs
image: { service: entrypoint({ entrypoint: 'passthroughImageService' }) }
// caller requests /_image?... -> throws
// after - use a local service for optimization
image: { service: passthroughImageService },
// or
image: { service: sharpImageService } Defensive patterns
Strategy: validation
Validate before calling
// At config load, ensure the image service is local when you use the optimization endpoint.
import { isLocalService } from 'astro/assets';
const svc = await getConfiguredImageService();
if (!isLocalService(svc)) throw new Error('Optimization endpoint requires a local image service (e.g. sharp).'); Type guard
import type { ImageService, LocalImageService } from 'astro/assets';
function isLocalImageService(s: ImageService | undefined): s is LocalImageService {
return !!s && 'transform' in s;
} Prevention
- Use a local service (sharp) when you depend on the _image optimization endpoint.
- For external/CDN services, consume generated URLs and avoid the local endpoint.
- Make custom services implement transform.
When it happens
Trigger: Hitting the _image optimization endpoint when image.service is configured as an external service (a CDN service that only returns URLs), or a custom service missing the transform method.
Common situations: Switched to an external/CDN image service but something still requests the local optimization endpoint; wrote a custom service that only implements parseURL/getURL without transform.
Related errors
- Configured image service is not a local service
- Incorrect transform returned by `parseURL`
- CONTENT_TOO_LARGE
- MissingGetFontFileRequestUrl
- RemoteImageNotAllowed
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/4144b665689eea8f.
Report an issue: GitHub.