npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@userband/widget

v0.4.0

Published

Userband Widget SDK - TypeScript loader for embedding Userband feedback widget

Downloads

38

Readme

@userband/widget

TypeScript SDK for seamlessly embedding Userband feedback widget into your web application

npm version License: MIT TypeScript

✨ Features

  • 🚀 Easy Integration - Just a few lines of code to get started
  • 📘 TypeScript Native - Full type safety with comprehensive TypeScript definitions
  • 🎯 Zero Dependencies - Lightweight SDK with no external dependencies
  • 🔄 Auto Loading - Automatically loads and initializes the widget script
  • Optimized Bundle - Only ~275KB gzipped for initial load with smart code splitting
  • 🎨 Rich API - Complete control over widget behavior and appearance
  • 📱 Responsive - Works seamlessly across all devices
  • 🔐 User Identification - Built-in support for authenticated users

📦 Installation

Using npm:

npm install @userband/widget

Using pnpm:

pnpm add @userband/widget

Using yarn:

yarn add @userband/widget

🚀 Quick Start

Get up and running in less than a minute:

import Userband from "@userband/widget"

// Initialize the widget
await Userband.init({
  clientKey: "YOUR_CLIENT_KEY",
})

That's it! The widget will automatically load and appear on your page.

🔧 Configuration

Basic Configuration

import Userband from "@userband/widget"

await Userband.init({
  clientKey: "YOUR_CLIENT_KEY", // Required: Your Userband client key
  hostUrl: "https://api.userband.com", // Optional: API host URL
  showLauncher: true, // Optional: Show the launcher button
  debug: true, // Optional: Enable debug logging
})

Configuration Options

| Option | Type | Required | Default | Description | | -------------- | --------- | -------- | -------------------------- | ------------------------------------------------------ | | clientKey | string | ✅ Yes | - | Your Userband client key from the dashboard | | hostUrl | string | No | https://api.userband.com | API host URL for your Userband instance | | widgetUrl | string | No | https://i.userband.com | Widget script URL (for custom hosting) | | userId | string | No | - | User ID to identify the user on init | | userTraits | object | No | - | Additional user information (email, name, etc.) | | showLauncher | boolean | No | true | Whether to show the launcher button | | debug | boolean | No | Auto-detected | Enable debug mode for logging (auto-detects localhost) |

Getting Your Client Key

  1. Sign in to your Userband Dashboard
  2. Navigate to SettingsWidget
  3. Copy your Client Key

Debug Mode

The widget supports debug mode for development and troubleshooting. When enabled, it logs detailed information to the browser console.

Auto-Detection:

By default, debug mode is automatically enabled in local development environments:

  • localhost
  • 127.0.0.1
  • Local network IPs (192.168.x.x, 10.x.x.x, etc.)

In production environments, debug mode is automatically disabled.

Manual Control:

You can explicitly enable or disable debug mode:

// Force enable debug mode (useful for staging environments)
await Userband.init({
  clientKey: "YOUR_CLIENT_KEY",
  debug: true,
})

// Force disable debug mode (useful for local production testing)
await Userband.init({
  clientKey: "YOUR_CLIENT_KEY",
  debug: false,
})

What Debug Mode Shows:

When enabled, you'll see console logs for:

  • Widget initialization and loading
  • User identification and session updates
  • Panel open/close events
  • API calls and responses
  • Errors and warnings

Example Output:

[Userband Widget] Initializing widget...
[Userband Widget] Widget loaded successfully
[Userband Widget] User identified: user-123
[Userband Widget] Panel opened: home

📚 API Reference

Initialization

init(config)

Initializes the widget with your configuration. This must be called before using any other methods.

Parameters:

  • config (WidgetConfig): Configuration object

Returns: Promise<void>

Example:

try {
  await Userband.init({
    clientKey: "YOUR_CLIENT_KEY",
    hostUrl: "https://api.userband.com",
  })
  console.log("Widget initialized successfully")
} catch (error) {
  console.error("Failed to initialize widget:", error)
}

User Identification

identify(distinctId, traits?)

Identifies a user with the widget. Use this to associate feedback and interactions with specific users.

Parameters:

  • distinctId (string): Unique identifier for the user
  • traits (UserTraits, optional): Additional user information

Returns: void

Example:

// Basic identification
Userband.identify("user-123")

// With user traits
Userband.identify("user-123", {
  email: "[email protected]",
  name: "John Doe",
  image: "https://example.com/avatar.jpg",
  // Any custom properties
  plan: "premium",
  company: "Acme Inc",
  signupDate: "2024-01-15",
})

Widget Control

open()

Opens the widget panel.

Returns: void

Example:

Userband.open()

close()

Closes the widget panel.

Returns: void

Example:

Userband.close()

destroy()

Completely removes the widget from the page.

Returns: void

Example:

Userband.destroy()

Launcher Control

launcher.show()

Shows the launcher button.

Returns: void

Example:

Userband.launcher.show()

launcher.hide()

Hides the launcher button.

Returns: void

Example:

Userband.launcher.hide()

Panel Control

panel.open(pageType, options?)

Opens the panel with a specific page type.

Parameters:

  • pageType (PageType): Type of page to open
    • 'home' - Home page with all features
    • 'feedback-create' - Create new feedback
    • 'feedback-list' - View user's feedback list
    • 'feedback-detail' - Specific feedback detail
    • 'changelog-detail' - Specific changelog detail
  • options (PanelOpenOptions, optional): Additional options
    • changelogId (string): Changelog ID when opening changelog-detail
    • feedbackId (string): Feedback ID when opening feedback-detail

Returns: void

Examples:

// Open home page
Userband.panel.open("home")

// Open feedback creation form
Userband.panel.open("feedback-create")

// Open user's feedback list
Userband.panel.open("feedback-list")

// Open specific feedback detail
Userband.panel.open("feedback-detail", {
  feedbackId: "feedback-xyz789",
})

// Open specific changelog
Userband.panel.open("changelog-detail", {
  changelogId: "changelog-abc123",
})

State Access

state

Gets the current widget state.

Returns: WidgetState

Properties:

  • isLauncherVisible (boolean): Whether the launcher is visible
  • isPanelOpen (boolean): Whether the panel is open
  • user (object): Current user information
    • userId (string | undefined): User ID
    • userTraits (object | undefined): User traits

Example:

const state = Userband.state

console.log("Launcher visible:", state.isLauncherVisible)
console.log("Panel open:", state.isPanelOpen)
console.log("Current user:", state.user.userId)

🎯 Usage Examples

React Integration

import { useEffect } from "react"
import Userband from "@userband/widget"

function App() {
  useEffect(() => {
    // Initialize widget on mount
    Userband.init({
      clientKey: process.env.REACT_APP_USERBAND_CLIENT_KEY!,
    }).catch(console.error)
  }, [])

  return (
    <div>
      <button onClick={() => Userband.open()}>
        Open Feedback
      </button>
    </div>
  )
}

React with User Authentication

import { useEffect } from "react"
import Userband from "@userband/widget"
import { useAuth } from "./auth" // Your auth provider

function App() {
  const { user, isAuthenticated } = useAuth()

  useEffect(() => {
    // Initialize widget
    Userband.init({
      clientKey: process.env.REACT_APP_USERBAND_CLIENT_KEY!,
    }).catch(console.error)
  }, [])

  useEffect(() => {
    // Identify user when authenticated
    if (isAuthenticated && user) {
      Userband.identify(user.id, {
        email: user.email,
        name: user.name,
        image: user.avatar,
      })
    }
  }, [isAuthenticated, user])

  return <YourApp />
}

Next.js Integration

// app/layout.tsx
"use client"

import { useEffect } from "react"
import Userband from "@userband/widget"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  useEffect(() => {
    Userband.init({
      clientKey:
        process.env.NEXT_PUBLIC_USERBAND_CLIENT_KEY!,
      hostUrl: process.env.NEXT_PUBLIC_USERBAND_HOST_URL,
    }).catch(console.error)
  }, [])

  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

Vue 3 Integration

<script setup lang="ts">
import { onMounted } from "vue"
import Userband from "@userband/widget"

onMounted(async () => {
  try {
    await Userband.init({
      clientKey: import.meta.env.VITE_USERBAND_CLIENT_KEY,
    })
  } catch (error) {
    console.error("Failed to initialize widget:", error)
  }
})
</script>

<template>
  <div>
    <button @click="Userband.open()">Open Feedback</button>
  </div>
</template>

Vanilla JavaScript

<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
  </head>
  <body>
    <button onclick="openWidget()">Open Feedback</button>

    <script type="module">
      import Userband from "https://cdn.jsdelivr.net/npm/@userband/widget/+esm"

      // Initialize
      await Userband.init({
        clientKey: "YOUR_CLIENT_KEY",
      })

      // Identify user
      Userband.identify("user-123", {
        email: "[email protected]",
        name: "User Name",
      })

      // Make globally available
      window.openWidget = () => Userband.open()
    </script>
  </body>
</html>

Conditional Loading

import Userband from "@userband/widget"

// Only load in production
if (process.env.NODE_ENV === "production") {
  await Userband.init({
    clientKey: process.env.USERBAND_CLIENT_KEY!,
  })
}

Custom Trigger Button

import Userband from "@userband/widget"
import { useEffect } from "react"

function FeedbackButton() {
  useEffect(() => {
    // Hide default launcher
    Userband.launcher.hide()
  }, [])

  return (
    <button
      onClick={() => Userband.panel.open("feedback-create")}
      className="custom-feedback-button"
    >
      Send Feedback
    </button>
  )
}

Dynamic User Updates

import Userband from "@userband/widget"

// When user logs in
function onLogin(user) {
  Userband.identify(user.id, {
    email: user.email,
    name: user.name,
    plan: user.subscription.plan,
  })
}

// When user logs out
function onLogout() {
  Userband.destroy()
  // Re-initialize as anonymous if needed
  await Userband.init({
    clientKey: "YOUR_CLIENT_KEY",
  })
}

🔒 TypeScript Support

This SDK is written in TypeScript and provides complete type definitions out of the box.

Type Imports

import Userband, {
  type WidgetConfig,
  type UserTraits,
  type PageType,
  type PanelOpenOptions,
  type WidgetState,
  type UserbandInterface,
} from "@userband/widget"

Type Definitions

WidgetConfig

interface WidgetConfig {
  clientKey: string
  hostUrl?: string
  widgetUrl?: string
  userId?: string
  userTraits?: Record<string, unknown>
  showLauncher?: boolean
  debug?: boolean
}

UserTraits

interface UserTraits {
  email?: string
  name?: string
  image?: string
  [key: string]: unknown // Any custom properties
}

PageType

type PageType =
  | "home"
  | "feedback-create"
  | "feedback-list"
  | "feedback-detail"
  | "changelog-detail"

WidgetState

interface WidgetState {
  isLauncherVisible: boolean
  isPanelOpen: boolean
  user: {
    userId: string | undefined
    userTraits: Record<string, unknown> | undefined
  }
}

🌍 Environment Variables

For better security and flexibility, use environment variables for your configuration:

React / Vite

VITE_USERBAND_CLIENT_KEY=your_client_key
VITE_USERBAND_HOST_URL=https://api.userband.com
await Userband.init({
  clientKey: import.meta.env.VITE_USERBAND_CLIENT_KEY,
  hostUrl: import.meta.env.VITE_USERBAND_HOST_URL,
})

Next.js

NEXT_PUBLIC_USERBAND_CLIENT_KEY=your_client_key
NEXT_PUBLIC_USERBAND_HOST_URL=https://api.userband.com
await Userband.init({
  clientKey: process.env.NEXT_PUBLIC_USERBAND_CLIENT_KEY!,
  hostUrl: process.env.NEXT_PUBLIC_USERBAND_HOST_URL,
})

Create React App

REACT_APP_USERBAND_CLIENT_KEY=your_client_key
REACT_APP_USERBAND_HOST_URL=https://api.userband.com
await Userband.init({
  clientKey: process.env.REACT_APP_USERBAND_CLIENT_KEY!,
  hostUrl: process.env.REACT_APP_USERBAND_HOST_URL,
})

🐛 Troubleshooting

Widget not loading

Problem: The widget doesn't appear after calling init().

Solution:

try {
  await Userband.init({
    clientKey: "YOUR_CLIENT_KEY",
  })
  console.log("Widget initialized successfully")
} catch (error) {
  console.error("Initialization error:", error)
  // Check:
  // 1. Is your clientKey correct?
  // 2. Is the hostUrl reachable?
  // 3. Check browser console for CORS errors
}

TypeScript errors

Problem: TypeScript can't find the module.

Solution: Make sure you have the latest version:

npm install @userband/widget@latest

If still not working, try adding to your tsconfig.json:

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "types": ["@userband/widget"]
  }
}

Widget appears but identify() doesn't work

Problem: User identification is not working.

Solution: Make sure to call identify() after the widget is initialized:

// ✅ Correct
await Userband.init({ clientKey: "YOUR_KEY" })
Userband.identify("user-123", { email: "[email protected]" })

// ❌ Wrong - don't call identify before init
Userband.identify("user-123") // This will throw an error
await Userband.init({ clientKey: "YOUR_KEY" })

Multiple widget instances

Problem: Widget appears multiple times.

Solution: The SDK uses a singleton pattern. Only call init() once:

// ✅ Correct - initialize once
useEffect(() => {
  Userband.init({ clientKey: "YOUR_KEY" })
}, []) // Empty dependency array

// ❌ Wrong - re-initializes on every render
useEffect(() => {
  Userband.init({ clientKey: "YOUR_KEY" })
}) // No dependency array

CSP (Content Security Policy) Issues

If you're using CSP, make sure to allow the widget script and ES modules:

<meta
  http-equiv="Content-Security-Policy"
  content="script-src 'self' https://i.userband.com; connect-src 'self' https://api.userband.com;"
/>

Note: The widget uses ES modules for optimal performance and code splitting. Modern browsers (Chrome 61+, Firefox 60+, Safari 11+) are fully supported.

🔧 Technical Details

Bundle Size & Performance

The widget is optimized for performance with smart code splitting:

  • Initial Load: ~275KB gzipped (main bundle)
  • Rich Text Editor: ~73KB gzipped (lazy loaded when user creates feedback)
  • Changelog Viewer: ~268KB gzipped (lazy loaded when user views changelog)

Most users will only download the initial ~275KB bundle, as the editor and viewer are loaded on-demand.

Browser Compatibility

The widget uses ES modules and supports all modern browsers:

  • Chrome 61+
  • Firefox 60+
  • Safari 11+
  • Edge 16+

For legacy browser support, you may need to configure your bundler to transpile the widget code.

How It Works

  1. SDK loads widget script - The SDK dynamically injects an ES module script tag
  2. Widget initializes - Main bundle (~275KB) loads and renders the launcher
  3. Lazy loading - Rich text editor and changelog viewer load only when needed
  4. Browser caching - All chunks are cached for subsequent visits

📄 License

MIT © Userband

🤝 Support

🚀 What's Userband?

Userband is a complete feedback and changelog platform that helps you build better products by understanding your users' needs. Our widget makes it easy to:

  • 📝 Collect Feedback - Let users submit feature requests and bug reports
  • 📢 Share Updates - Keep users informed with beautiful changelogs
  • 💬 Engage Users - Start conversations and build relationships
  • 📊 Understand Needs - Analyze feedback to prioritize your roadmap

Learn more about Userband →


Made with ❤️ by the Userband team