Skip to content

Install the feedback widget ​

The widget brings FeedLog Agent into your product for questions, help articles, feedback, and progress updates. See Introduction to FeedLog Agent and Setting up and launching Agent for the customer experience and workspace configuration.

Install ​

bash
npm install @feedlog/widget

The package provides ES modules and TypeScript declarations for applications using a bundler, with no runtime dependencies. The SDK displays the feedback button and loads the feedback interface from your FeedLog workspace.

Use a coding agent

Copy the Widget integration prompt into a coding agent with access to your project. It will inspect the codebase, implement the integration, and verify the result.

Configure the widget ​

Enable the widget in Settings → Widget, then choose how customers will use it.

Guest-only integration ​

Enable guest posting in your workspace and initialize the widget on the client:

ts
import { createWidget } from '@feedlog/widget'

createWidget({
  baseUrl: 'https://acme.feedlog.ai',
  theme: 'auto',
})

Use your workspace URL, including a custom domain if you have one. Guest voting and commenting are controlled separately in workspace settings. Guest-only integrations can skip the sign-in and token sections below.

Integrate existing product accounts ​

Provide auth to identify customers using your product's accounts. Connect this example to the server-side token endpoint described below:

ts
import { createWidget } from '@feedlog/widget'

createWidget({
  baseUrl: 'https://acme.feedlog.ai',
  auth: {
    getToken: async () => {
      const res = await fetch('/api/feedlog-token')
      if (!res.ok) throw new Error('Token endpoint unavailable')
      const { token } = await res.json()
      if (token === null || (typeof token === 'string' && token.length > 0)) return token
      throw new Error('Invalid token response')
    },
    login: () => new Promise<void>((resolve) => {
      openYourSignInModal({ onClose: resolve })
    }),
  },
  theme: 'auto',
})

openYourSignInModal represents your application's sign-in function. Its close handler must run on every exit, including cancellation. Workspace permissions determine whether signed-out customers can use guest mode.

Where to call it ​

Call createWidget() once per page in client-only startup code, such as a Nuxt .client plugin or a React useEffect. The package can be imported during server rendering; initialization runs only in the browser.

Repeated calls are ignored. Use updateWidget() for subsequent display changes. After calling createWidget(), you can call the control methods without waiting for the widget to finish loading.

Report the current sign-in state ​

auth.getToken() reports the current user. The SDK checks it when opening the panel and refreshing authentication, so it should complete quickly.

ResultMeaningWidget behavior
A JWT stringSigned in as the token's emailUses that identity for the FeedLog session
nullConfirmed signed outClears the signed-in session; offers guest access or sign-in according to workspace permissions
Throw or rejectTemporary failureKeeps the session and offers a retry when needed

Return null only for a confirmed sign-out

Network errors, timeouts, and unexpected responses must throw. Returning null for these failures clears the customer's cached session.

Opening your own sign-in UI ​

auth.login runs when a customer selects sign-in inside the widget and getToken() confirms they are signed out. Connect it to your existing modal, popup, or sign-in route.

For a modal or popup, return a Promise that settles when the interaction ends, including cancellation. The widget temporarily hides its panel and button while it waits, then checks getToken() again and restores the widget. Resolving or rejecting the Promise ends the wait; the returned token determines whether sign-in succeeded.

If the callback returns void, the widget stays visible and checks identity again immediately. For a full-page redirect, use your application's return URL handling; the panel reopens when the customer returns within approximately five minutes.

Session refresh checks getToken() silently. Sign-in UI opens only after an explicit sign-in request from the customer.

Sign the token on your server ​

Create an SSO secret in Developer → Single Sign-On. Sign the JWT with HS256 on your server, using the user from your application's authenticated session. email and exp are required; name and picture are optional. Expiration must be no more than 24 hours away.

ts
import jwt from 'jsonwebtoken'

export default handler(async (req) => {
  const user = await currentUser(req)
  if (!user) return { token: null }

  return {
    token: jwt.sign(
      { email: user.email, name: user.name, picture: user.avatarUrl },
      process.env.FEEDLOG_SSO_SECRET,
      { algorithm: 'HS256', expiresIn: '1h' },
    ),
  }
})

Adapt handler and currentUser to your server framework. Keep the SSO secret on the server and derive claims from the authenticated session, never from identity fields supplied by the browser.

The SDK exchanges the JWT for a FeedLog session and caches it. See Single sign-on for secret management, claim requirements, and exchange errors. The same signing code and secret can serve browser SSO and widget authentication.

Customize the entry and display ​

The panel and floating button are controlled separately. Opening or closing the panel preserves the button's current state; changing the button's state leaves the panel's open/closed state unchanged.

Open feedback from your own button ​

Connect an existing Help or Feedback entry to openWidget():

ts
import { openWidget } from '@feedlog/widget'

document.querySelector('#help-entry')?.addEventListener('click', openWidget)

Call this after rendering your entry, or use your framework's click handler. To use only your own entry, include launcher: { state: 'hidden' } in the initial createWidget() options. openWidget() still opens the panel while the floating button is hidden.

Close the panel for a business dialog ​

Call closeWidget() when your application opens a dialog that needs the space:

ts
import { closeWidget } from '@feedlog/widget'

function showCheckout() {
  closeWidget()
  openCheckoutDialog()
}

Closing preserves the conversation and draft. The customer can reopen feedback from the floating button or your own entry.

Follow your application's theme ​

auto follows the operating system. If your application has its own theme selector, pass its current light or dark value during initialization and update it in the application's theme-change handler:

ts
import { updateWidget, type WidgetTheme } from '@feedlog/widget'

function onThemeChange(theme: WidgetTheme) {
  updateWidget({ theme })
}

The theme changes without interrupting the current conversation. Brand color comes from your FeedLog workspace settings.

Adjust button placement and state ​

Use launcher at initialization or through updateWidget():

ts
import { updateWidget } from '@feedlog/widget'

updateWidget({
  launcher: { alignment: 'left', bottomOffset: 80, sideOffset: 20 },
})

Set launcher.state to button, tab, or hidden to show the floating button, show the edge-mounted Help tab, or hide the entry. For example:

ts
updateWidget({ launcher: { state: 'tab' } })

At initialization, a customer's saved dragged position takes priority, followed by SDK placement fields and workspace defaults. An explicit position update through updateWidget() replaces the saved drag position; unspecified placement fields retain their current values. Updating only state, theme, or z-index preserves the position.

Dragged positions and button/tab preferences survive reloads in the same browser tab. Hiding lasts for the current page; reloading restores the button. Explicit launcher.state overrides the saved preference: initializing with hidden hides it on every load. Workspace close behavior controls the customer's close button; your application can still set the state explicitly.

Set the stacking order ​

The default zIndex is 40, shared by the button and panel. Set it during initialization or update it to match your application's navigation and dialog layers:

ts
updateWidget({ zIndex: 100 })

The appropriate value depends on your application's stacking order.

Show an unread badge on your own entry ​

Add onUnreadChange to your initial createWidget() options. It receives the unread count used by the widget's badge:

ts
createWidget({
  baseUrl: 'https://acme.feedlog.ai',
  onUnreadChange: (count) => {
    const badge = document.querySelector<HTMLElement>('#help-unread')
    if (!badge) return
    badge.textContent = String(count)
    badge.hidden = count === 0
  },
})

Add the callback to your existing initialization, alongside auth for product accounts. Start your badge hidden and render the count when reported. Your entry controls its formatting.

API and configuration reference ​

All four functions return void.

FunctionPurpose
createWidget(options)Initialize once per page
openWidget()Open the feedback panel
closeWidget()Close the panel and preserve the conversation and draft
updateWidget(options)Update theme, zIndex, and launcher fields

Initialization options ​

OptionDescription
baseUrlRequired workspace URL, including the scheme
auth.getTokenRequired when supplying auth: () => Promise<string | null>
auth.loginOptional () => void | Promise<void> for your sign-in UI
themelight, dark, or auto (default)
zIndexInteger from 0 to 2147483647; default 40
launcherPlacement and state fields listed below
onUnreadChangeOptional (count: number) => void for a custom unread indicator

updateWidget() merges supplied display fields, including individual launcher fields. Omitted fields stay unchanged. baseUrl, auth, and onUnreadChange are set at initialization. Display updates do not change workspace settings.

Launcher options ​

FieldDescription
alignmentleft or right; uses the workspace setting when omitted
bottomOffsetNon-negative safe integer in CSS pixels; defaults to the workspace setting
sideOffsetNon-negative safe integer in CSS pixels; defaults to the workspace setting
statebutton, tab, or hidden; when omitted, restores the customer's preference if allowed by the workspace close behavior, otherwise shows the button

The initial workspace placement is bottom right, 20 px from each edge. Desktop and mobile share placement settings. The SDK handles device safe areas and keeps the entry visible. The Help tab sits flush against the screen edge.

Custom embed page

embedPath is an initialization option for deployments with a custom widget page. It defaults to /widget/embed and must be an absolute path on the same origin as baseUrl, such as /custom-widget. Supply it in createWidget().

Workspace settings and browser support ​

Settings → Widget controls whether the widget is enabled, its default placement, and customer closing behavior. The SDK reads these settings when the page loads. See Set up the feedback button for previewing and saving changes.

The widget supports current Chrome, Firefox, Safari, and Edge. Its styles are isolated from your page. Desktop panels fit the available space around the button; screens up to 520 CSS pixels wide use a full-screen panel. If browser storage is restricted, saved preferences and session caches last only for the current page.

Agent page context ​

When a customer sends a message, the widget provides the current page path, title, and meta description to help Agent understand the context. Query parameters, URL fragments, and page body content are excluded. SPA navigation is reflected in the next message without additional configuration.

Common errors you may encounter

The button does not appear. Check that the widget is enabled, initialization runs on the client, and launcher.state is not explicitly set to hidden. A customer's closing action may also have hidden the button for the current page. If the console says could not load widget config, check that baseUrl resolves to your workspace and that its /api/widget/config endpoint is reachable.

Users are signed out intermittently. Check that temporary token endpoint failures throw instead of returning null.

The panel shows a sign-in prompt for a signed-in customer. Inspect the token endpoint's response and check that it can read the application's session. A null response tells the widget the customer is signed out.

The panel shows “Feedback could not be loaded.” Inspect the token endpoint and FeedLog token exchange responses in the browser's network panel. Single sign-on explains token requirements and exchange errors.

Changing the configured position does not move the button. A saved drag position takes priority over initial placement. Use an explicit runtime position update when the application needs to move it.

A navigation bar covers the widget. Compare the host's layer values with the widget's zIndex and choose an appropriate stacking order.

Open-source feedback management. Self-host it or let us run it.