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
npm install @feedlog/widgetThe 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:
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:
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.
| Result | Meaning | Widget behavior |
|---|---|---|
| A JWT string | Signed in as the token's email | Uses that identity for the FeedLog session |
null | Confirmed signed out | Clears the signed-in session; offers guest access or sign-in according to workspace permissions |
| Throw or reject | Temporary failure | Keeps 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.
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():
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:
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:
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():
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:
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:
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:
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.
| Function | Purpose |
|---|---|
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
| Option | Description |
|---|---|
baseUrl | Required workspace URL, including the scheme |
auth.getToken | Required when supplying auth: () => Promise<string | null> |
auth.login | Optional () => void | Promise<void> for your sign-in UI |
theme | light, dark, or auto (default) |
zIndex | Integer from 0 to 2147483647; default 40 |
launcher | Placement and state fields listed below |
onUnreadChange | Optional (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
| Field | Description |
|---|---|
alignment | left or right; uses the workspace setting when omitted |
bottomOffset | Non-negative safe integer in CSS pixels; defaults to the workspace setting |
sideOffset | Non-negative safe integer in CSS pixels; defaults to the workspace setting |
state | button, 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.