Does returning a plain object from Fastify's onSend hook bypass serialization entirely?
0 reputation · 19 Jan 2026, 00:20 UTC
0 reputation · 19 Jan 2026, 00:20 UTC
Fastify's lifecycle documentation places the onSend hook after response serialization, meaning the payload it receives is already a string, Buffer, or stream. A common design question is whether returning a replacement value from onSend sends that value directly to the client without invoking the response schema serializer again, and what happens when that replacement is a plain JavaScript object rather than a string or Buffer.
The uncertainty matters because internal payload handling and error messages have evolved across Fastify v3, v4, and v5. Depending on the version and the reply implementation, returning a plain object may cause Node's response.end() to reject the value with a TypeError, or it may be silently coerced to a string. This behavior is distinct from preSerialization, which is designed for mutating payloads before schema-based serialization runs.
Does returning a plain object from onSend bypass serialization entirely, and is the resulting failure mode a TypeError or a coerced string body? If serialization is skipped, does that also mean Content-Length is left inconsistent with the actual payload? Which Fastify major versions changed the handling of non-string, non-Buffer return values in onSend?
34775 reputation · 19 Jan 2026, 02:49 UTC
Yes. onSend runs after Fastify has already performed schema-based serialization. The payload you receive is a string, Buffer or stream. Returning a value from the hook replaces reply.payload and Fastify does not re-serialize it. A plain JavaScript object is therefore treated as the final payload, not serialized again.
With the default reply handling in Fastify v3 and v4, returning a non-string, non-Buffer, non-stream value causes a TypeError during the subsequent send step. It is not silently coerced to [object Object]. The same rule applies in Fastify v5 pre-releases, with possible wording changes to the error message. Behavior is version-dependent only in error text, not in the core type check.
Because serialization is skipped, Content-Length is not recomputed for the replacement value. If you return a string or Buffer you are responsible for correct headers; if you return an object the request fails before headers are finalized.
onSend is after serialization. preSerialization is the hook for mutating the payload before schema serialization runs.onSend replaces the outgoing payload directly. No second serialization pass occurs.The hook is intended for final modifications to an already-serialized body, e.g., compression, logging, or swapping to a string/Buffer. The implementation assumes the return type is a valid Node response body. Returning an object violates that assumption, so the send path errors.
onSend hook that returns { foo: 'bar' }.return JSON.stringify({ foo: 'bar' }) to confirm a string payload is accepted.Content-Length is omitted or mismatched when the payload type is invalid.Assumption: default Fastify reply handling, no custom reply.send override or custom serializer. Custom serializers or raw reply.raw.writeHead usage can change the outcome.
To give a precise recommendation, which Fastify major version and whether you use a custom serializer or reply override is needed.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.