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-vueCreate 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.
| Prop | Behavior |
|---|---|
| embedKey | Required public key beginning with mk_live_. |
| roomName / room | Persistent named room or existing room code. |
| mode / position | inline or bubble, with bottom-right or bottom-left bubble placement. |
| name / lang / accent | Prefill a display name, choose UI language or set a hex accent color. |
| autoJoin | Join immediately only for an open-door room; browser media permissions still apply. |
| baseUrl | Meeto 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)"
/>| Event | Payload or meaning |
|---|---|
| ready | The iframe completed its handshake. |
| phase | prejoin, room, finished or error. |
| joined | The visitor entered; includes room and identity. |
| left | The visitor left; includes reason and durationSec. |
| participants | The participant count changed. |
| error | The 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.
| Plan | Current limit |
|---|---|
| Free | Up to 10 participants and 60-minute calls; cloud recording and AI summary are not included. |
| Pro | Up to 25 participants, no Free time limit within the paid concurrency allowance, recording and 10 AI-summary hours per month. |
| All plans | Powered 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.