How to use MessagePack with LiveView instead of JSON

In a quest to optimize the amount of data sent between the server and client I recently decided to try to use MessagePack instead of JSON. The payloads are about 75% of the size in JSON, which is nice. I have not noticed any performance issues related to encoding and decoding.

There is a StackOverflow answer that I used for the basis for this post and @chrismccord helped me with a crucial bit of information. To get LiveUploads to work, a bit of extra code is necessary.

If you spot any improvements or errors, please tell me and I will update the code.

In assets/app.js you import the MessagePack client:

import {Encoder, Decoder} from "@msgpack/msgpack";
const encoder = new Encoder({ ignoreUndefined: true });
const decoder = new Decoder();

const liveSocket = new LiveSocket("/live", Socket, {
  // hooks  and params stuff
  encode: (payload, callback) => {
    // We convert binary payloads to a Uint8Array, so MessagePack will send pack it.
    if (payload.payload instanceof ArrayBuffer) {
      payload.payload = new Uint8Array(payload.payload)
  decode: (payload, callback) => callback(decoder.decode(payload)),

The serializer looks like this :

defmodule MyAppWeb.MsgpackSerializer do
  @behaviour Phoenix.Socket.Serializer

  alias Phoenix.Socket.Reply
  alias Phoenix.Socket.Message
  alias Phoenix.Socket.Broadcast

  def fastlane!(%Broadcast{} = msg) do
    msg = %Message{topic: msg.topic, event: msg.event, payload: msg.payload}

    {:socket_push, :binary, encode_to_binary(msg)}

  def encode!(%Reply{} = reply) do
    msg = %Message{
      topic: reply.topic,
      event: "phx_reply",
      ref: reply.ref,
      payload: %{status: reply.status, response: reply.payload}

    {:socket_push, :binary, encode_to_binary(msg)}

  def encode!(%Message{} = msg) do
    {:socket_push, :binary, encode_to_binary(msg)}

  defp encode_to_binary(msg) do
    msg |> Map.from_struct() |> Msgpax.pack!()

  def decode!(message, _opts) do
    decoded = Msgpax.unpack!(mesage)
    payload = Map.get(decoded, "payload")

    # LiveUpload sends us binary, but MessagePack is always binary so we cannot use the opcode in the opts.
    if is_binary(payload) and not String.valid?(payload) do
      Map.put(decoded, "payload", {:binary, payload})
    |> Phoenix.Socket.Message.from_map!()

And then in your Enpoint module use the serializer in the socket:

socket("/live", Phoenix.LiveView.Socket,
  websocket: [
    serializer: [{MyAppWeb.MsgpackSerializer, "2.0.0"}]

Update, the check in serializer does not work when an array of zeroes is sent. It is probably better to check the “event” key in the message directly.

So the decode!/2 function in the serializer should probably use the following check:

if decoded["event"] == "chunk" do