ReachBellDocs

Vue integration

The @reachbell/vue package wraps the core SDK in a Vue 3 plugin and a pair of composables, so your components can read the current permission state, prompt the user, and identify or tag the subscriber without touching the global ReachBell object directly.

Install

npm install @reachbell/vue @reachbell/sdk

@reachbell/vue re-exports the SDK types from @reachbell/sdk (ReachBellConfig, SubscribeResult, ApiResult) for direct access when you need them, but day to day you should rarely reach past the composables.

Register the plugin

Install ReachBellPlugin once, at the app root, in main.ts. The plugin initialises the underlying SDK exactly once and exposes its surface to every component via Vue's provide/inject.

// main.ts
import { createApp } from "vue";
import { ReachBellPlugin } from "@reachbell/vue";
import App from "./App.vue";
 
createApp(App)
  .use(ReachBellPlugin, {
    apiKey: "rb_live_yourkey",
    autoPrompt: true,
  })
  .mount("#app");

Plugin options

The options object is the SDK's ReachBellConfig (minus the internal force flag):

  • apiKey — required. Your project's API key.
  • apiUrl — optional. Override the API base URL for self-hosted or regional deployments.
  • autoPrompt — optional boolean. Arms a one-shot user-gesture listener that triggers the soft prompt on the first interaction.

The API key is a public credential. It identifies your project to the SDK and is safe to ship to the browser — it cannot be used to send pushes or mutate your project. Server-side actions use a separate secret key. Treat it like a Stripe publishable key, not a Stripe secret key.

If you're loading the key from an environment variable, use Vite's import.meta.env with a VITE_-prefixed name so it's inlined into the client bundle:

createApp(App).use(ReachBellPlugin, {
  apiKey: import.meta.env.VITE_REACHBELL_KEY,
  autoPrompt: true,
});

Init-once, HMR-safe. The plugin bootstraps the SDK on the next tick and guards against double-init, and the SDK itself is idempotent. The worst case during a hot-module reload is a no-op — never a duplicate service worker registration.

Drop the service worker at the root

@reachbell/vue is a thin wrapper, not a replacement for the service worker file. You still need reachbell-sw.js served from your site's root:

https://yoursite.com/reachbell-sw.js

For a Vite project, put the file in public/ — Vite serves everything under public/ from the deployed root with no rewriting.

your-project/
  public/
    reachbell-sw.js   <-- from the dashboard integration page
  src/
    main.ts
    App.vue

After a deploy the file should be reachable at https://yoursite.com/reachbell-sw.js. Hit that URL in your browser to verify before testing the prompt.

useReachBell()

The primary composable. Call it inside any component's setup() (or <script setup>) underneath the app that installed the plugin. It returns the SDK's reactive state and a stable set of action callbacks.

<!-- SubscribeButton.vue -->
<script setup lang="ts">
import { useReachBell } from "@reachbell/vue";
 
const { isReady, permission, prompt } = useReachBell();
 
async function subscribe() {
  const result = await prompt();
  if (!result.success) console.warn("prompt failed:", result.reason);
}
</script>
 
<template>
  <span v-if="!isReady" />
  <span v-else-if="permission === 'granted'">You're subscribed.</span>
  <span v-else-if="permission === 'denied'">Notifications blocked.</span>
  <button v-else @click="subscribe">Get notified</button>
</template>

Returned shape

useReachBell() returns a ReachBellApi:

  • isReady — Ref<boolean>. Becomes true once the SDK's bootstrap settles (success or failure).
  • permission — Ref<NotificationPermission | null>. Mirrors Notification.permission. It's null on the server and during the first render before the SDK reads the browser; once resolved it's 'default' | 'granted' | 'denied'.
  • prompt() — wraps ReachBell.prompt(). Returns a SubscribeResult ({ success, token?, reason? }) — branch on result.success, not result.ok. Also refreshes permission after the browser dialog closes.
  • identify(externalId) — wraps ReachBell.identify(). Returns an ApiResult ({ ok, reason? }).
  • tag(tags) — wraps ReachBell.tag(). Pass a string array. Returns an ApiResult.
  • sdk — the underlying ReachBell instance. Escape hatch for anything the composable doesn't expose.

Because permission can be null, branch on the explicit string values ('granted', 'denied', 'default') rather than truthiness. The button example above renders nothing until isReady is true, by which point permission has resolved.

useNotificationPermission()

A lighter composable that returns only the permission ref plus a manual refresh — useful when a component cares about the permission but doesn't need to call any actions. It calls refresh() automatically on mount.

<!-- PermissionBadge.vue -->
<script setup lang="ts">
import { useNotificationPermission } from "@reachbell/vue";
 
const { permission, refresh } = useNotificationPermission();
</script>
 
<template>
  <div>
    <span>Status: {{ permission ?? "unknown" }}</span>
    <button @click="refresh">Re-check</button>
  </div>
</template>

refresh() re-reads Notification.permission synchronously and returns the current value. The composable runs it once onMounted, so most apps never need to call it manually — it's there for the rare case where you want to re-check after sending the user to their browser's site settings.

useNotificationPermission() is built on top of useReachBell(), so it must also be called from a component underneath the app that installed ReachBellPlugin. Calling either composable without the plugin throws a clear error.

Identify after login

The cleanest place to call identify() is when your auth state transitions from anonymous to logged-in. Watch your user store and fire the call once the SDK is ready.

<script setup lang="ts">
import { watch } from "vue";
import { useReachBell } from "@reachbell/vue";
import { useCurrentUser } from "./auth";
 
const { isReady, identify } = useReachBell();
const user = useCurrentUser();
 
watch(
  [isReady, user],
  async ([ready, currentUser]) => {
    if (!ready || !currentUser) return;
    const r = await identify(currentUser.id);
    if (!r.ok) console.warn("identify failed:", r.reason);
  },
  { immediate: true },
);
</script>

After identifying, you can target the subscriber by externalId from your backend instead of tracking the opaque token yourself. See the Subscribers API for the server-side counterpart.

Tag based on plan

Same pattern for tags. The example below adds a tag for the user's plan whenever it changes.

<script setup lang="ts">
import { watch } from "vue";
import { useReachBell } from "@reachbell/vue";
import { useCurrentUser } from "./auth";
 
const { isReady, tag } = useReachBell();
const user = useCurrentUser();
 
watch(
  [isReady, () => user.value?.plan],
  ([ready, plan]) => {
    if (!ready || !plan) return;
    tag([plan]);
  },
  { immediate: true },
);
</script>

Tags are additive. Each tag() call adds the given labels to the device's existing tag set — it doesn't replace them. To remove a tag, call POST /subscribers/untag (see the Subscribers API).

SSR safety

The plugin is SSR-safe. It defers the SDK bootstrap until window exists, and permission starts as null on the server so a server-rendered pass never touches browser globals. The useNotificationPermission() composable's onMounted refresh is a no-op during SSR and runs on the client after hydration. If you use Nuxt, register the plugin in a client plugin file (plugins/reachbell.client.ts) and you're done.

What's next?