---
title: useWebSocket composable
related:
  - title: useBroadcastChannel composable
    path: use-broadcast-channel.md
  - title: useEventSource composable
    path: use-event-source.md
  - title: useWebWorker composable
    path: use-web-worker.md
---
The `useWebSocket()` composable keeps a [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) connection alive from a component: it opens the socket, exposes the last message received as a reactive value, queues what you send until the socket is ready, reconnects with a backoff when the connection drops (and right away when the browser comes back online), can send a heartbeat, and closes the socket when the component gets destroyed.

> [!NOTE]
> On the server-side of SSR or SSG modes, no socket gets created: `socketStatus` stays `closed`, `sendSocketMessage()` does nothing and no message ever arrives. The socket opens on the client once the component is mounted, so the status is `closed` before hydration too.

> [!TIP]
> **Outside of a component**
>
> The composable can also be called outside of `setup()`: in a boot file, a store or a plain module. There is no mount to wait for there, so the socket opens right away (unless `lazy` is set) and nothing closes it by itself: call `closeSocket()` when you are done. It releases everything the composable holds (the socket, the queued messages, the `online`/`offline` listeners and the watcher on a reactive `url`), and a later `openSocket()` sets it all up again.

## Syntax

```js
import { useWebSocket } from 'quasar'

setup () {
  const {
    socketStatus, socketData, socketError, sendSocketMessage, openSocket, closeSocket
  } = useWebSocket(
    url, // String, URL, or a ref/getter of one;
         // '/live' and 'https://...' forms are mapped to ws(s)
    {
      // all optional:

      lazy: true, // do not open the socket on mount (default: false);
                  // openSocket() or the first sendSocketMessage() does it

      protocols: ['chat'],        // the native sub-protocol(s) (default: none)
      binaryType: 'arraybuffer',  // 'blob' or 'arraybuffer' (default: 'blob')

      autoReconnect: {  // reopen a socket that closed on its own (default: true; false disables it)
        retries: 5,     // number of attempts (default: Infinity)
        delay: attempt => 500 * (attempt + 1) // ms, a number works too (default: 1s doubling up to 30s)
      },

      heartbeat: {         // send a message at a fixed interval while open (default: false; true for the defaults below)
        message: 'ping',   // what to send (default: 'ping')
        interval: 30000    // every X ms while the socket is open (default: 30000)
      },

      onOpen (evt) { // called each time the socket opens
        // ...
      },
      onMessage (data, evt) { // called with each message received
        // ...
      },
      onClose (evt, reason) { // called when the socket closes;
        // reason: 'programmatic' | 'unmount' | 'url' | 'remote'
      },
      onError (evt) { // called with the socket's "error" event
        // ...
      },
      onReconnect (attempt, delay) { // called when a reconnect gets scheduled
        // ...
      }
    }
  )

  // ...
}
```

```ts
function useWebSocket<Data = any>(
  url: MaybeRefOrGetter<string | URL>,
  options?: {
    lazy?: boolean
    protocols?: string | string[]
    binaryType?: 'blob' | 'arraybuffer'
    autoReconnect?:
      | boolean
      | {
          retries?: number
          delay?: number | ((attempt: number) => number)
        }
    heartbeat?:
      | boolean
      | {
          message?: string | ArrayBufferLike | Blob | ArrayBufferView
          interval?: number
        }
    onOpen?: (evt: Event) => void
    onMessage?: (data: Data, evt: MessageEvent<Data>) => void
    onClose?: (
      evt: CloseEvent,
      reason: 'programmatic' | 'unmount' | 'url' | 'remote'
    ) => void
    onError?: (evt: Event) => void
    onReconnect?: (attempt: number, delay: number) => void
  }
): {
  socketStatus: Ref<'closed' | 'connecting' | 'open'>
  socketData: ShallowRef<Data | null>
  socketError: ShallowRef<Event | null>
  sendSocketMessage: (
    message: string | ArrayBufferLike | Blob | ArrayBufferView
  ) => void
  openSocket: () => void
  closeSocket: (code?: number, reason?: string) => void
}
```

## Lifecycle

The socket opens when the component is mounted (or right away, when the composable is used outside of a component) and is closed when the component gets destroyed. Set `lazy: true` for a socket that should wait for your `openSocket()` call (or your first `sendSocketMessage()`, which opens the socket by itself).

Each call of `useWebSocket()` manages one connection to one endpoint; for several sockets, call it several times.

`closeSocket(code, reason)` closes the connection with the native close code and reason, drops the queued messages and stops any reconnecting. The native constraints apply: the code is `1000` or one in the `3000` to `4999` range and the reason is at most 123 bytes of UTF-8; an invalid pair closes with the defaults instead. It is not final: a later `openSocket()` or `sendSocketMessage()` opens a fresh connection.

Once the component got destroyed, the composable is done: `openSocket()` and `sendSocketMessage()` do nothing anymore, so a late async callback cannot open a socket that nothing would close.

`socketStatus` is `connecting` from the moment the socket is requested until it is open, `open` while messages flow, and `closed` when it was never opened, when you closed it, or when reconnecting was given up. While waiting to reconnect the status is `connecting` too, as the composable is still working on it.

When the `url` is a ref or a getter and its value changes while the socket is wanted, the current connection is closed and a new one is opened to the new URL (a token in the query string, a different room).

The `url` does not have to be a `ws://` or `wss://` one: a relative URL (`'/api/live'`) is resolved against the page and an `http://` or `https://` one is mapped to `ws://` or `wss://`. A socket that follows the page's own scheme this way can never be blocked as mixed content on an https page. The newest browsers accept such URLs natively; the composable does it for the older ones Quasar supports too.

## Sending and receiving

`sendSocketMessage(message)` sends a String, `Blob`, `ArrayBuffer` or typed array. Messages sent while the socket is not open yet are queued and sent, in order, as soon as it opens; those sent from within `onOpen` go first, so a handshake (authentication, a subscription) reaches the server before the queued ones. Calling `sendSocketMessage()` on a closed socket opens it. `closeSocket()` drops whatever is still queued.

`socketData` holds the `data` of the last message received and `socketError` the last `error` event of the socket. The `onMessage`, `onError`, `onOpen` and `onClose` hooks get called in the same situations, so you do not need to watch the refs. Messages arrive as they were sent: parse them yourself (`JSON.parse()`) in `onMessage` if your protocol is JSON.

`onClose(evt, reason)` is called for every close, with the native `CloseEvent` (its `code`, `reason` and `wasClean` tell how the connection ended) and a second argument saying who asked for it: `programmatic` for your `closeSocket()` call, `unmount` when the component got destroyed, `url` when the socket was moved to a new URL, and `remote` when the socket closed on its own (the server closed it, or the connection dropped). The hook runs when the close event arrives, so for `unmount` the component is already gone by then. For `remote` it runs before any reconnect gets scheduled, so a `closeSocket()` call from within it keeps the socket closed.

## Reconnecting

A socket that closes on its own (the server went away, the network dropped) is reopened after a delay: 1s, then doubling up to 30s, for as long as it takes. Tune it with `autoReconnect: { retries, delay }`, where `delay` is a number of ms or a function of the attempt index (starting at 0), or turn it off with `autoReconnect: false`. A successful connection resets the attempt count. `onReconnect(attempt, delay)` is called each time a reconnect gets scheduled, with the attempt number (starting at 1) and the ms to wait, so a "reconnecting..." notice can say how long; when the retries run out, `socketStatus` becomes `closed`.

No attempt is made while the browser reports being offline: the composable waits for the `online` event and reconnects right away when it fires (`onReconnect(1, 0)`), starting a fresh run of attempts, even when the retries had run out. `closeSocket()` ends all that: the socket stays closed until you call `openSocket()` again.

## Heartbeat

Some proxies and load balancers drop a connection that stays silent for a while. `heartbeat: true` sends the String `'ping'` every 30 seconds while the socket is open; set `{ message, interval }` to match what your server expects. The heartbeat stops with the socket and starts over on each (re)connection.

## Example

The example below talks to a public echo server, which greets each new connection then repeats everything it receives. The socket waits for your click (`lazy`); open it, close it, or turn your network off and on to see the status follow.

Example "Basic":

```vue
<template>
  <div class="row items-center q-gutter-sm q-mb-md">
    <q-input
      v-model="message"
      dense
      outlined
      label="Message"
      style="width: 220px"
      @keyup.enter="sendMessage"
    />
    <q-btn
      color="primary"
      label="sendSocketMessage()"
      no-caps
      :disable="message === ''"
      @click="sendMessage"
    />
    <q-btn
      v-if="socketStatus === 'closed'"
      color="positive"
      label="openSocket()"
      no-caps
      @click="openSocket"
    />
    <q-btn
      v-else
      color="negative"
      label="closeSocket()"
      no-caps
      @click="closeSocket()"
    />
  </div>

  <div class="q-mb-sm">
    Status:
    <q-badge
      :color="
        socketStatus === 'open'
          ? 'positive'
          : socketStatus === 'connecting'
            ? 'warning'
            : 'grey'
      "
      :label="socketStatus"
    />
  </div>

  <div v-if="log.length === 0">No message received yet</div>
  <div v-for="(entry, index) in log" :key="index" class="text-caption">{{
    entry
  }}</div>
</template>

<script setup>
import { ref } from 'vue'
import { useWebSocket } from 'quasar'

const message = ref('Hello Quasar')
const log = ref([])

// a public echo server: it greets each connection, then repeats
// every message it receives
const { socketStatus, sendSocketMessage, openSocket, closeSocket } =
  useWebSocket('wss://echo.websocket.org', {
    lazy: true,
    onMessage(data) {
      log.value.unshift(`received: ${data}`)
    }
  })

function sendMessage() {
  log.value.unshift(`sent: ${message.value}`)
  sendSocketMessage(message.value)
}
</script>
```

> [!TIP]
> **Content Security Policy**
>
> A socket URL must be allowed by the `connect-src` directive of your CSP.
