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
- Return plain serializable data: objects, arrays, strings, numbers, booleans, null, Date, Map/Set of primitives
- 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
- If you truly need to return a Response, use a server endpoint (src/pages/api/) instead of an action
- 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
- Return plain data (objects/arrays/primitives/Date/Map/Set of primitives) from every action handler
- Perform redirects at the call site based on a returned field, never by returning Response
- Map ORM rows to DTOs before returning; add a dev-only devalue.stringify assertion to catch regressions
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- BAD_REQUEST
- UnsupportedConfigTransformError
- ActionCalledFromServerError
- ActionCalledFromServerError
- ActionNotFoundError
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)