Serialization

Server function arguments and return values are serialized so they can travel between server and client.


Configuration

Set the mode on solidStart() in vite.config.ts:

import { defineConfig } from "vite";
import { solidStart } from "@solidjs/start/config";
export default defineConfig({
plugins: [
solidStart({
serialization: { mode: "json" },
}),
],
});

Modes

  • json: deserializes with JSON.parse on the client. It avoids eval, so it fits a strict CSP. This is the default.
  • js: a smaller binary format that needs eval on the client, which a strong CSP blocks.

If your app enforces a Content Security Policy, keep json.


Temporal values

SolidStart preserves JavaScript Temporal values in server function and action payloads. The value is reconstructed as the same Temporal type on the receiving side.

SolidStart does not install a Temporal implementation. Native server-runtime availability is:

RuntimeUnflagged global Temporal support
Node.js26 and later
Deno2.7 and later
BunNo stable release

Server-runtime support does not guarantee browser support. Both the client and server must provide a compatible global Temporal before a payload is serialized or deserialized. Check typeof globalThis.Temporal !== "undefined" in each target environment.

If any runtime targeted by your app does not provide Temporal natively, install a global polyfill:

pnpm add temporal-polyfill
import "temporal-polyfill/global";

Import the shared module before application initialization in both entrypoints:

import "./temporal";

Use the polyfill's global entrypoint. A local import such as import { Temporal } from "temporal-polyfill" does not define the global that serialization requires. Without a compatible global on either side, sending a Temporal value causes serialization or deserialization to fail.


Custom types

Use a custom Seroval plugin when a server function needs to accept or return a value that Seroval does not support, such as a database identifier, decimal type, or another custom class.

Set serialization.plugins to a module whose default export is an array of plugins:

import { defineConfig } from "vite";
import { solidStart } from "@solidjs/start/config";
export default defineConfig({
plugins: [
solidStart({
serialization: {
plugins: "src/seroval-plugins.ts",
},
}),
],
});

Create plugins with the API exported by @solidjs/start/serialization. The entrypoint exports createPlugin, OpaqueReference, and the related plugin types. Importing from it keeps the plugin on the same Seroval version that SolidStart uses.

import { createPlugin } from "@solidjs/start/serialization";
import { Money } from "./lib/money";
const moneyPlugin = createPlugin<Money, { cents: any }>({
tag: "app/Money",
test: (value) => value instanceof Money,
parse: {
sync: (value, ctx) => ({ cents: ctx.parse(value.cents) }),
async: async (value, ctx) => ({
cents: await ctx.parse(value.cents),
}),
stream: (value, ctx) => ({ cents: ctx.parse(value.cents) }),
},
serialize: (node, ctx) =>
`new globalThis.Money(${ctx.serialize(node.cents)})`,
deserialize: (node, ctx) => new Money(ctx.deserialize(node.cents) as number),
});
export default [moneyPlugin];

SolidStart bundles the plugin module into both the client and server builds, so it must not import server-only code. Built-in SolidStart plugins run before custom plugins.

Custom plugins apply to server function and action payloads. They do not affect the hydration payload produced by solid-js/web.

With the default json mode, deserialize rebuilds the value. If you use js mode, the code returned by serialize can only refer to globals available in the client. In the example above, Money must be assigned to globalThis.Money before deserialization.


Server function payloads

SolidStart applies extra handling for certain payload types so file uploads and binary data can flow without being serialized by Seroval. This applies to both server function arguments and return values. SolidStart bypasses Seroval for:

  • FormData
  • URLSearchParams
  • Uint8Array
  • ArrayBuffer
  • Blob
  • File
  • string

Because these values are transferred directly, this can yield smaller payloads for these cases.


Last updated: 7/29/26, 6:55 PMEdit this pageReport an issue with this page