Vue

Install the package and start it from a small Vue plugin.

Installed with the @userlily/js npm package · Written for Vue 3 with Vite

What works

ProductStatusNote
Analytics & custom eventsSupported
Feedback widgetSupported
Review badgeSupported
Web pushSupported
Roadmap embedSupported
Feedback, survey & roadmap linksSupported

1. Install the package

Terminal

npm install @userlily/js

2. Add your keys

Vite only exposes variables starting with VITE_ to the browser. They are built in - rebuild after changing them.

.env

VITE_USERLILY_HOST=https://dev.userlily.com
VITE_USERLILY_TRACKING_ID=YOUR_TRACKING_ID
VITE_USERLILY_CAMPAIGN=YOUR_CAMPAIGN_KEY
VITE_USERLILY_BROADCAST_KEY=YOUR_PROJECT_KEY

3. Create a plugin

Delete the products you do not use.

src/plugins/userlily.js

import { init } from '@userlily/js'

export default {
  install() {
    init({
      host: import.meta.env.VITE_USERLILY_HOST,
      analytics: {
        trackingId: import.meta.env.VITE_USERLILY_TRACKING_ID,
      },
      widget: {
        campaign: import.meta.env.VITE_USERLILY_CAMPAIGN,
      },
      broadcast: {
        project: import.meta.env.VITE_USERLILY_BROADCAST_KEY,
      },
    })
  },
}

4. Register it

Vue Router navigation is recorded automatically.

src/main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import userlily from './plugins/userlily'

createApp(App).use(router).use(userlily).mount('#app')

5. Send custom events

In any component

<script setup>
import { track } from '@userlily/js'
</script>

<template>
  <button @click="track('signup_completed')">Sign up</button>
</template>

6. Show the review badge

Place <ReviewBadge /> where it should appear.

components/ReviewBadge.vue

<!-- components/ReviewBadge.vue -->
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { mountSyndication } from '@userlily/js'

const host = import.meta.env.VITE_USERLILY_HOST
const target = ref(null)
let unmount = () => {}

onMounted(() => {
  unmount = mountSyndication({
    host,
    campaign: 'YOUR_CAMPAIGN_KEY',
    target: target.value,
  })
})
onUnmounted(() => unmount())
</script>

<template>
  <div ref="target" />
</template>

7. Host the push worker

The worker must be served from your own domain at /userlily-sw.js, as JavaScript, with no redirect and over HTTPS. Each of these fails silently. Vite serves public/ at the site root.

Terminal - re-run after updating the package

npx --no userlily copy-sw public

8. Ask for push permission from your own button

Optional. Works when “When to ask” is set to “Only when your own code asks”.

In any component

<script setup>
import { broadcast } from '@userlily/js'
</script>

<template>
  <button @click="broadcast.prompt()">Get notified</button>
</template>

9. Embed the public roadmap

Turn on “Enable public roadmap” in Roadmap → Settings first.

components/RoadmapEmbed.vue

<!-- components/RoadmapEmbed.vue -->
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'

const ORIGIN = 'https://dev.userlily.com'
const frame = ref(null)

function onMessage(event) {
  if (event.origin !== ORIGIN) return
  const data = event.data
  if (data?.type !== 'userlily:roadmap:height') return
  if (frame.value && data.height > 0) {
    frame.value.style.height = data.height + 'px'
  }
}

onMounted(() => window.addEventListener('message', onMessage))
onUnmounted(() => window.removeEventListener('message', onMessage))
</script>

<template>
  <iframe
    ref="frame"
    :src="ORIGIN + '/r/YOUR_ROADMAP_KEY?embed=1'"
    title="Product roadmap"
    loading="lazy"
    style="width: 100%; height: 720px; border: 0"
  />
</template>

Every option, limit and troubleshooting step is in the main docs. Logos are trademarks of their owners and are shown only to indicate compatibility.