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

Action return values are serialized with devalue so the client can reconstruct them. When the handler returns something devalue cannot serialize — functions, class instances without registrations, symbols, or a raw Response object — serialization throws, and Astro reports ActionsReturnedInvalidDataError with a targeted hint when it detects a Response was returned.

Solutions

  1. Return plain serializable data: objects, arrays, strings, numbers, booleans, null, Date, Map/Set of primitives
  2. For redirects, return a status/URL field and perform the redirect at the call site (router.push or Astro.redirect) — see the hint's docs link
  3. If you truly need to return a Response, use a server endpoint (src/pages/api/) instead of an action
  4. Map ORM rows to DTOs before returning (drop methods, proxies, and circular refs)

Example fix

// before
handler: async (input, ctx) => {
  return Response.redirect('/dashboard', 302); // not serializable
}

// after
handler: async (input, ctx) => {
  return { ok: true, redirectTo: '/dashboard' };
}
// client: const res = await actions.buy(input);
// if (res.data?.redirectTo) router.push(res.data.redirectTo);
Defensive patterns

Strategy: validation

Validate before calling

// assert serializability of handler output in dev builds
import { stringify } from 'devalue';
export async function safeHandler(input, ctx) {
  const data = await realWork(input);
  if (import.meta.env.DEV) stringify(data, { URL: (v) => v instanceof URL && v.href });
  return data;
}

Type guard

function isSerializableValue(v: unknown): boolean {
  if (v === null) return true;
  const t = typeof v;
  if (t === 'function' || t === 'symbol' || t === 'bigint') return false;
  if (t !== 'object') return true;
  if (v instanceof Response || v instanceof Function) return false;
  if (Array.isArray(v)) return v.every(isSerializableValue);
  if (v instanceof Date || v instanceof URL || v instanceof Map || v instanceof Set) return true;
  return Object.values(v).every(isSerializableValue);
}

Try / catch

try {
  const result = await Astro.callAction(actions.buy)(input);
} catch (e) {
  if (e instanceof AstroError && e.code === ActionsReturnedInvalidDataError.code) {
    // inspect e.hint: Response -> redirect at call site or use an endpoint
  } else throw e;
}

Prevention

When it happens

Trigger: A handler returning new Response()/redirect(...) (very common when porting endpoints to actions); returning class instances, Maps of custom objects, DB model instances, or values containing functions; returning undefined fields mixed with unsupported types.

Common situations: Returning fetch() results or Response objects to trigger redirects after an action; ORM rows with methods and circular relations; helper closures attached to returned objects; migrating API endpoint logic into actions verbatim.

Understand the failure class

Related errors


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

Appendix: source

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

		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 52e6c34790)