Skip to content

Send large payloads

Every transport Roblox gives you has a ceiling, and the way you find it is usually in production, with a payload that grew. Worse, some transports do not refuse an oversized payload so much as deliver a shortened one, which reads back as a smaller value that looks entirely plausible.

Compressing buys headroom and, more importantly, makes a truncated payload fail loudly instead of quietly.

-- Text out, so it goes straight through anything that carries JSON.
local packed = Twill.Compress.Encode(bigTable)
-- Refuses a message past the 1 KB limit at the call, instead of at the platform.
Twill.Shared.Publish("WorldEvent", packed)
-- Nil rather than a throw, and nil rather than a half-read table.
local original = Twill.Compress.Decode(packed)

The result is text, so it survives every transport that carries JSON: DataStores, MessagingService, MemoryStore, attributes, and remotes.

Roblox values are handled natively. You do not need Serialize first.

Encode computes both forms and picks the smaller: plain JSON where the value can survive it, the compressed form otherwise.

So there is no size below which you should avoid it. Small values come back as JSON, because packing eight bits into a character JSON accepts always costs more than the byte it replaced.

Compression takes over once it earns its keep. A few thousand repetitive rows land near a fifth of their JSON.

local value = Twill.Compress.Decode(text)
if value == nil then
logger:Warn("payload could not be read")
return
end

Decode never throws. It answers nil for a payload that was truncated, was never a Compress payload, or had its length header altered.

A truncated payload is refused, not read as a smaller value. The byte count travels with the data precisely so that a transport with a size ceiling cannot hand you a half-table that looks complete.

Compression is lossy in specific, named ways.

ValueWhat survives
Color3, ColorSequence8 bits per channel
CFramePosition and axis angle at f32
DateTimeWhole seconds
InstanceA path, resolving to nil where it does not exist
Functions, threads, buffersnil
Cyclic branchesnil

For saved data where exactness matters, use Serialize instead, and see Store Roblox values safely. The two answer different questions:

  • Serialize keeps a value exact and readable in a shape a DataStore accepts.
  • Compress makes it small and opaque.

Reach for Serialize when a person or another system reads the field, and for Compress when only size matters.

Do not use MessagingService for player data

Section titled “Do not use MessagingService for player data”

To write to a player on another server, use Data.Edit. It routes through Store and never writes over a session it does not own.

Messages, through Twill.Shared or MessagingService directly, have no such guarantee, and a cross-server write built on them will eventually race with the owning server and lose progress.

Every transport imposes its own limit, and the result here is text. Compression buys headroom. It does not remove the ceiling.

See Platform limits.