{"record":{"id":"d3d6d6373e1377e8","repo":"withastro/astro","slug":"actionsreturnedinvaliddataerror","errorCode":"ActionsReturnedInvalidDataError","errorMessage":"Action handler returned invalid data. Handlers should return serializable data types like objects, arrays, strings, and numbers. Parse error: ${error}","messagePattern":"Action handler returned invalid data\\. Handlers should return serializable data types like objects, arrays, strings, and numbers\\. Parse error: (.+?)","errorType":"exception","errorClass":"AstroError","httpStatus":null,"severity":"error","filePath":"packages/astro/src/actions/runtime/server.ts","lineNumber":515,"sourceCode":"\t\treturn {\n\t\t\ttype: 'empty',\n\t\t\tstatus: 204,\n\t\t};\n\t}\n\tlet body;\n\ttry {\n\t\tbody = devalueStringify(res.data, {\n\t\t\t// Add support for URL objects\n\t\t\tURL: (value) => value instanceof URL && value.href,\n\t\t});\n\t} catch (e) {\n\t\tlet hint = ActionsReturnedInvalidDataError.hint;\n\t\tif (res.data instanceof Response) {\n\t\t\thint = REDIRECT_STATUS_CODES.includes(res.data.status as any)\n\t\t\t\t? '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.'\n\t\t\t\t: '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';\n\t\t}\n\t\tthrow new AstroError({\n\t\t\t...ActionsReturnedInvalidDataError,\n\t\t\tmessage: ActionsReturnedInvalidDataError.message(String(e)),\n\t\t\thint,\n\t\t});\n\t}\n\treturn {\n\t\ttype: 'data',\n\t\tstatus: 200,\n\t\tcontentType: 'application/json+devalue',\n\t\tbody,\n\t};\n}\nfunction toArrayBuffer(buffer: Uint8Array): ArrayBuffer {\n\tconst copy = new Uint8Array(buffer.byteLength);\n\tcopy.set(buffer);\n\treturn copy.buffer;\n}\n","sourceCodeStart":497,"sourceCodeEnd":533,"githubUrl":"https://github.com/withastro/astro/blob/52e6c34790cc8ac4e69e6135ace06049867e5c4a/packages/astro/src/actions/runtime/server.ts#L497-L533","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nhandler: async (input, ctx) => {\n  return Response.redirect('/dashboard', 302); // not serializable\n}\n\n// after\nhandler: async (input, ctx) => {\n  return { ok: true, redirectTo: '/dashboard' };\n}\n// client: const res = await actions.buy(input);\n// if (res.data?.redirectTo) router.push(res.data.redirectTo);","handlingStrategy":"validation","validationCode":"// assert serializability of handler output in dev builds\nimport { stringify } from 'devalue';\nexport async function safeHandler(input, ctx) {\n  const data = await realWork(input);\n  if (import.meta.env.DEV) stringify(data, { URL: (v) => v instanceof URL && v.href });\n  return data;\n}","typeGuard":"function isSerializableValue(v: unknown): boolean {\n  if (v === null) return true;\n  const t = typeof v;\n  if (t === 'function' || t === 'symbol' || t === 'bigint') return false;\n  if (t !== 'object') return true;\n  if (v instanceof Response || v instanceof Function) return false;\n  if (Array.isArray(v)) return v.every(isSerializableValue);\n  if (v instanceof Date || v instanceof URL || v instanceof Map || v instanceof Set) return true;\n  return Object.values(v).every(isSerializableValue);\n}","tryCatchPattern":"try {\n  const result = await Astro.callAction(actions.buy)(input);\n} catch (e) {\n  if (e instanceof AstroError && e.code === ActionsReturnedInvalidDataError.code) {\n    // inspect e.hint: Response -> redirect at call site or use an endpoint\n  } else throw e;\n}","preventionTips":["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"],"tags":["astro-actions","serialization","devalue","response-object","return-value"],"backgroundTag":"non-serializable-return-value","analyzedSha":"52e6c34790cc8ac4e69e6135ace06049867e5c4a","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}