withastro/astro · error · AstroError

ActionsReturnedInvalidDataError

ActionsReturnedInvalidDataError

Error message

Action handler returned invalid data. Handlers should return serializable data types like objects, arrays, strings, and numbers. Parse error: ${error}

What it means

The action handler's return value is serialized with devalueStringify (devalue) before being sent to the client. If the data is not devalue-serializable (a Response object, a function, an unsupported class instance, Symbols), serialization fails and AstroError ActionsReturnedInvalidDataError is thrown, with an extra hint when a Response was returned.

Source

Thrown at packages/astro/src/actions/runtime/server.ts:514

		return {
			type: 'empty',
			status: 204,
		};
	}
	let body;
	try {
		body = devalueStringify(res.data, {
			// Add support for URL objects
			URL: (value) => value instanceof URL && value.href,
		});
	} catch (e) {
		let hint = ActionsReturnedInvalidDataError.hint;
		if (res.data instanceof Response) {
			hint = REDIRECT_STATUS_CODES.includes(res.data.status as any)
				? 'If you need to redirect when the action succeeds, trigger a redirect where the action is called. See the Actions guide for server and client redirect examples: https://docs.astro.build/en/guides/actions.'
				: 'If you need to return a Response object, try using a server endpoint instead. See https://docs.astro.build/en/guides/endpoints/#server-endpoints-api-routes';
		}
		throw new AstroError({
			...ActionsReturnedInvalidDataError,
			message: ActionsReturnedInvalidDataError.message(String(e)),
			hint,
		});
	}
	return {
		type: 'data',
		status: 200,
		contentType: 'application/json+devalue',
		body,
	};
}
function toArrayBuffer(buffer: Uint8Array): ArrayBuffer {
	const copy = new Uint8Array(buffer.byteLength);
	copy.set(buffer);
	return copy.buffer;
}

View on GitHub (pinned to d081033d5f)

Solutions

  1. Return plain serializable data: objects, arrays, strings, numbers, booleans, null, Date, URL (supported via the URL plugin), Maps/Sets.
  2. For redirects, trigger the redirect at the call site (client or Astro.callAction) instead of returning a Response.
  3. If you truly need to return a Response, use a server endpoint (API route) rather than an action.
  4. Convert class instances to plain objects before returning.

Example fix

// before
export const go = defineAction({ handler: async () => new Response(null, { status: 302, headers: { Location: '/done' } }) });
// after - return data, redirect at the call site
export const go = defineAction({ handler: async () => ({ ok: true }) });
// caller (client): const { data } = await actions.go(); if (data?.ok) window.location.href = '/done';
Defensive patterns

Strategy: try-catch

Validate before calling

// Sanity-check the handler's return shape in dev before returning it.
function assertSerializable(value: unknown) {
  if (value instanceof Response) throw new Error('Actions cannot return a Response; use an endpoint or redirect at the call site.');
  if (typeof value === 'function') throw new Error('Actions cannot return a function.');
}
// inside handler:
const result = await doWork(); assertSerializable(result); return result;

Type guard

// Reject non-serializable return types up front.
function isSerializableReturn(v: unknown): boolean {
  if (v instanceof Response) return false;
  if (typeof v === 'function' || typeof v === 'symbol') return false;
  return true;
}

Prevention

When it happens

Trigger: Handler returns new Response(...) (e.g. trying to redirect), a custom class instance devalue cannot encode, a function, or Symbol-keyed/Symbol-valued structures.

Common situations: Trying to redirect by returning a Response; returning a Mongoose document / ORM model instance; returning a function or Class instance.

Understand the failure class

Related errors


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