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>. Becomestrueonce the SDK's bootstrap settles (success or failure).permission—Ref<NotificationPermission | null>. MirrorsNotification.permission. It'snullon the server and during the first render before the SDK reads the browser; once resolved it's'default' | 'granted' | 'denied'.prompt()— wrapsReachBell.prompt(). Returns aSubscribeResult({ success, token?, reason? }) — branch onresult.success, notresult.ok. Also refreshespermissionafter the browser dialog closes.identify(externalId)— wrapsReachBell.identify(). Returns anApiResult({ ok, reason? }).tag(tags)— wrapsReachBell.tag(). Pass a string array. Returns anApiResult.sdk— the underlyingReachBellinstance. Escape hatch for anything the composable doesn't expose.
Because
permissioncan benull, branch on the explicit string values ('granted','denied','default') rather than truthiness. The button example above renders nothing untilisReadyistrue, by which pointpermissionhas 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 ofuseReachBell(), so it must also be called from a component underneath the app that installedReachBellPlugin. 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, callPOST /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?
- Read Concepts for the project / subscriber model the SDK maps to.
- Build the server-side counterpart with the Server SDK (
@reachbell/node) for Node backends — it wraps the Subscribers API, which you can also call directly from any language. - Group subscribers and target them with Segments.
- Connect the same audience to a welcome automation.