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

  1. Use a local service (e.g. @astrojs/sharp) if you rely on the built-in optimization endpoint.
  2. If you use an external service, depend on its generated URLs instead of calling the local endpoint.
  3. 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

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


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/4144b665689eea8f. Report an issue: GitHub.