Vue and Nuxt video call integration

Add a video call to Vue 3 or Nuxt

Install @meeto/embed-vue for a ready-made MeetoRoom component or use the useMeetoRoom composable when your Vue app needs direct controls. The wrapper is SSR-safe and creates the browser iframe after mount.

Install the Vue 3 wrapper

Install the Vue package in a Vue 3 or Nuxt application. Vue 3.3 or newer is the declared peer dependency; the package uses @meeto/embed-core underneath.

npm install @meeto/embed-vue

Create a public key in the widget dashboard and add the local and production origins before rendering a component. The public key can be in client code; a server API secret cannot.

Render the MeetoRoom component

MeetoRoom renders a container whose iframe fills its size. Set an inline height or a CSS class. The prop is embedKey, not key.

<script setup lang="ts">
import { MeetoRoom } from "@meeto/embed-vue";

function onJoined(event: { room: string }) {
  console.log("joined", event.room);
}
</script>

<template>
  <MeetoRoom
    embed-key="mk_live_…"
    room-name="support"
    name="Visitor"
    lang="en"
    style="height: 520px"
    @joined="onJoined"
  />
</template>

Use roomName for a persistent destination such as support. Use room for a room code your app already owns. Set mode="bubble" for the floating button and panel.

MeetoRoom props
PropBehavior
embedKeyRequired public key beginning with mk_live_.
roomName / roomPersistent named room or existing room code.
mode / positioninline or bubble, with bottom-right or bottom-left bubble placement.
name / lang / accentPrefill a display name, choose UI language or set a hex accent color.
autoJoinJoin immediately only for an open-door room; browser media permissions still apply.
baseUrlMeeto address for a self-hosted installation; defaults to https://meeto.me.

Use useMeetoRoom for imperative controls

The composable returns a container ref, the live widget, the current room code and control functions. Pass a getter when options can change.

<script setup lang="ts">
import { useMeetoRoom } from "@meeto/embed-vue";

const { containerRef, room, join, leave, setCamera } = useMeetoRoom(() => ({
  embedKey: "mk_live_…",
  roomName: "support",
  mode: "inline",
  onEvent: (event) => console.log(event.type),
}));
</script>

<template>
  <div ref="containerRef" style="height: 520px" />
  <button type="button" @click="join('Visitor')">Join</button>
  <button type="button" @click="setCamera(false)">Camera off</button>
  <button type="button" @click="leave">Leave {{ room ?? "" }}</button>
</template>

Calling join before the iframe handshake is safe: the core buffers the command until the widget is ready. The composable destroys the instance on unmount and when an identity option changes.

Handle Vue events

MeetoRoom emits typed event names that match the core protocol. Use individual listeners for common states or on-event for one handler.

<MeetoRoom
  embed-key="mk_live_…"
  style="height: 520px"
  @phase="phase = $event.phase"
  @participants="count = $event.count"
  @error="report($event.code)"
  @left="saveDuration($event.durationSec)"
/>
Events emitted by @meeto/embed-vue
EventPayload or meaning
readyThe iframe completed its handshake.
phaseprejoin, room, finished or error.
joinedThe visitor entered; includes room and identity.
leftThe visitor left; includes reason and durationSec.
participantsThe participant count changed.
errorThe widget reports a code and message.

The composable's onEvent callback receives the same event union. A prop or option identity change rebuilds the iframe, while changing only the callback does not.

Production checklist and limits

Allow the exact origin

Add the scheme, host and any non-default port that will render the component. Nuxt preview and production hosts are separate origins.

Give the container a height

The inline iframe stretches to 100% of the component container. Use a responsive class or an inline minimum height.

Keep secrets server-side

mk_live_… is a public origin-locked key. Do not pass API or host credentials through embedKey or any other client prop.

Test the full browser path

Join from a second browser, grant camera and microphone permissions, and verify joined, participants, error and left handlers before shipping.

Current embedded-call limits
PlanCurrent limit
FreeUp to 10 participants and 60-minute calls; cloud recording and AI summary are not included.
ProUp to 25 participants, no Free time limit within the paid concurrency allowance, recording and 10 AI-summary hours per month.
All plansPowered by Meeto branding remains visible and the public key stays origin-locked.

The widget uses WebRTC encryption in transit and is not end-to-end encrypted. See privacy details before documenting your own data handling or compliance.

Frequently asked questions

Does @meeto/embed-vue work with Nuxt server rendering?

Yes. The component and composable create MeetoWidget in onMounted, so the server can render the container and Nuxt can hydrate it in the browser. The container still needs a height and a browser must support the requested media devices.

Why is the prop called embedKey instead of key?

Vue reserves key for virtual-DOM identity. @meeto/embed-vue names the authentication prop embedKey, which maps to the core widget's public key option.

When should I use useMeetoRoom instead of MeetoRoom?

Use MeetoRoom for a ready-made container and Vue events. Use useMeetoRoom when your component needs a template ref, imperative join or leave controls, the current room code, or a single handler for all typed events.

What happens when widget props change?

Changes to the embed key, room, roomName, mode, position, label, name, language, accent, autoJoin or baseUrl destroy and recreate the iframe. The onEvent callback itself can change without rebuilding the call.

Put it to work

Open your Meeto account to set it up, or explore the working product first.