
By going to Settings → Widget, you can easily add a widget to your site. It only takes a few lines of JavaScript to set up, providing you with a simple way to unify your support, Changelog, docs, and AI Chat for your customers. You can customize the widget's appearance to fit your site's design, listen to events & more.
Embed & initialize
Head to the widget settings page, where you'll find a copy/pastable snippet that looks like the snippet below. Or copy the code below and replace <your-widget-key /> with your workspace's widget key.
<script>
;((w)=>{const P=(w.Productlane={queue:{}});["set","open","close","toggle","on","off","init"].forEach(m=>{P[m]=(n=>function(){P.queue[n]={args:arguments}})(m)})})(window);
Productlane.init({
widgetKey: "<your-widget-key />",
})
</script>
<script
async
defer
crossorigin="anonymous" src="https://widget.productlane.com/latest.productlane-widget.min.js"
></script>That's it!
While this is all you have to do, let's briefly unwrap what happens in the snippet.
The first script sets up the global Productlane object used to communicate with the widget. It also initializes the widget via Productlane.init() with your corresponding widget key.
The second script tag loads the widget from Productlane's CDN.
The widgetKey is a unique identifier that links the snippet to your workspace's widget configuration. It is not a JSX element or selector: the angle-bracket notation (<your-widget-key />) in the snippet is just a placeholder to show you where to paste your actual key.
You can find your real widget key on your widget settings page. It looks like a short alphanumeric string (for example, wg_abc123). Copy it directly from that page and replace the placeholder.
Productlane.init({
widgetKey: "wg_abc123", // paste your actual key here, without angle brackets
});An object containing information about the current user for simple authentication. Will hide the email field in the contact form if provided.
Productlane.init({
widgetKey: "<your-widget-key />",
user: {
email: "[email protected]",
},
});To verify your users' identities and ensure conversations are securely linked to them, enable secure mode by passing a signed JWT (JSON Web Token) to the widget.
Generate the Token
You need to generate a JWT on your backend using a Signing Secret. You can find this secret in your Widget Settings.
The token must be signed using the HS256 algorithm and include the user's email, an issued-at timestamp (iat), and an expiration timestamp (exp).
The expiration timestamp can not exceed 1 hour. This does not affect the time your user is signed into the widget. After the user token is passed to the widget, it is exchanged for a session token. The session token is valid for 12 hours by default.
Here is an example using Node.js and the jsonwebtoken library:
const jwt = require('jsonwebtoken');
const secret = "wg_sec_..."
const payload = {
email: user.email,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (15 * 60) // 15 minutes (recommended)
};
const userToken = jwt.sign(payload, secret, { algorithm: 'HS256' })Pass Token to Widget
Once generated, pass the token to the widget using the userToken property. You can do this during initialization, or update it dynamically later.
On Initialization:
Productlane.init({
widgetKey: "<your-widget-key />",
userToken: "<user-token>"
});Dynamically (e.g., after login):
Productlane.set({
userToken: "<user-token>"
});To sign the user out and unset the authentication token, use the signOut method:
Productlane.signOut();Important Note on User Objects
The userToken provides secure authentication. If you subsequently set the simple user object (e.g., Productlane.set({ user: { email: ... } })), it will overwrite the authentication status and effectively sign the user out of the secure session. Ensure you use either userToken for authenticated users or user for simple identification, but avoid mixing them in the same session state.
When a user is authenticated in the widget via JWT, they are automatically signed into the portal when clicking any link that opens it. For example the roadmap, changelog, docs, or requests. This works like single sign-on (SSO): no separate login is required on the portal side.
The user authenticates in the widget using a userToken (JWT).
When they click a link to the portal (e.g., to view a roadmap item or help article), the widget requests a short-lived, one-time authorization code.
The portal exchanges this code for a session, automatically signing the user in.
To use this feature, both Identity Verification and Portal Auth must be enabled in your Widget Settings.
The widget supports dynamic context passing, which helps the AI Chat provide more relevant and page-specific answers to your users. By setting context information about the current page or feature your user is viewing, the AI can tailor its responses accordingly.
Use the setContext method to pass contextual information to the widget. Context is provided as an object with string key-value pairs.
Productlane.setContext({
page: "dashboard",
feature: "analytics",
plan: "pro"
});Common use cases:
Page tracking: Let the AI know which page the user is currently on
Feature context: Indicate which feature or section the user is interacting with
User attributes: Pass relevant user information like their plan type or role
Product state: Provide context about what the user is viewing or editing
You can retrieve the currently set context at any time:
const currentContext = Productlane.getContext();
console.log(currentContext); // { page: "dashboard", feature: "analytics" }To remove all context information:
Productlane.clearContext();When a user asks a question in the AI Chat, the context you've set is automatically included with their message. This allows the AI to:
Prioritize information relevant to the current page or feature
Provide more specific and actionable answers
Reference documentation or help articles related to what the user is currently doing
For example, if a user is on your "Analytics Dashboard" page and asks "How do I export this data?", the AI will know they're referring to analytics data specifically, not data from other parts of your application.
The widget follows the visitor's system theme by default. Use the mode option to pin it to a single scheme instead.
Productlane.init({
widgetKey: "<your-widget-key />",
mode: "dark",
});| Value | Behavior |
|---|---|
"auto" |
Follows the visitor's system setting and switches live when they change it. This is the default when mode is omitted. |
"light" |
Always renders the light theme. |
"dark" |
Always renders the dark theme. |
Pick "light" or "dark" when your site ships a single theme, so the widget matches your page rather than the visitor's operating system.
If your site has its own theme toggle, pass the new value to Productlane.set(). The widget repaints without a reload.
Productlane.set({ mode: "light" });The accent colors for each theme are configured under Settings → Widget → Theme settings, where you set a light and a dark accent. The mode option only decides which of the two applies.
To control where the widget appears on your site, use the position option. These settings let you choose which side of the screen the widget button appears on and which direction the widget opens.
position ("left", "right" or "center"):position is set to "center".position: "right" places the button on the right edge of the screen, and the widget opens toward the left.Here is an example of widget positioning:
Productlane.init({
widgetKey: "<your-widget-key />",
position: "left",
});To fine-tune the widget's distance from the edges of the screen, use the offset option. This allows you to add custom spacing from the bottom, left, or right edges.
Productlane.init({
widgetKey: "<your-widget-key />",
offset: {
bottom: "20px",
right: "20px",
},
});| Property | Type | Description |
|---|---|---|
bottom |
string | Distance from the bottom edge (e.g., "20px", "2rem") |
left |
string | Distance from the left edge (e.g., "20px", "2rem") |
right |
string | Distance from the right edge (e.g., "20px", "2rem") |
All properties are optional. Use left or right depending on your position setting. For example, if your widget is positioned on the left, use left and bottom to adjust its placement:
Productlane.init({
widgetKey: "<your-widget-key />",
position: "left",
offset: {
bottom: "80px",
left: "16px",
},
});By default, the widget renders its own floating icon button. If you want to use your own button or link to open the widget, you can hide the default icon and call the JavaScript API directly.
Changing the size of the built-in widget icon is not currently supported. If you need a differently sized or styled trigger, use a custom trigger button instead (see below).
In Settings → Widget, set the icon style to None. This hides the default floating button so your own element becomes the only trigger.
Call Productlane.open() or Productlane.toggle() from your own button's click handler.
Plain HTML example:
<button onclick="Productlane.open()">Open support</button>React / JSX example:
<button onClick={() => Productlane.toggle()}>Open support</button>Open a specific view directly:
// Opens straight to the AI Chat view
Productlane.open("AICHAT");
// Opens straight to the feedback form
Productlane.open("FEEDBACK");Make sure to call
Productlane.open()orProductlane.toggle()only after the widget has loaded. Wrap the call inside aProductlane.on("loaded", ...)handler if you need to invoke it programmatically on page load rather than on a click event.
Spotlight replaces the floating icon with an input that sits at the bottom of the page, the way a search bar does. The visitor types a question straight into it and lands in the AI agent.
Productlane.init({
widgetKey: "<your-widget-key />",
spotlight: true,
position: "center",
});Any position works. "center" suits a landing page, "right" keeps the bar where a launcher would have been.
At rest: a short pill. Its placeholder cycles through your example questions, one every five seconds.
On focus: the bar widens and shows up to three of those questions above it. Clicking one asks it.
On send: the bar grows into the chat panel, at the same width, and the agent answers there. Files can be attached once the bar is open.
After closing: the bar reads "Continue conversation" and one click reopens that chat where it left off.
The questions and the rotating placeholders both come from the example questions under Settings → AI Agent.
A landing page has no logged-in user and no support UI of its own, so a launcher in the corner asks the visitor to go looking for it. Spotlight puts the question box in front of them instead: pricing, integrations, migration, whatever they came to find out, answered from your help center and changelog without leaving the page.
Productlane.init({
widgetKey: "<your-widget-key />",
spotlight: true,
position: "center",
mode: "dark",
});Pair it with Productlane.setContext() so the agent knows which page the visitor is reading. On a pricing page, "what happens after the trial" then gets an answer about your plans rather than a general one.
Spotlight can be turned on and off without a reload, so one site can run the launcher in the product and the bar on its marketing pages.
Productlane.set({ spotlight: true });
// Back to the launcher
Productlane.set({ spotlight: false });Spotlight reaches the AI agent only. Docs, changelog, requests, and changelog popups stay behind the standard launcher.
Productlane.open() opens the agent while spotlight is on, whatever view you pass.
The bar falls back to the standard launcher when the AI agent is switched off for the workspace, and inside an embedded widget, which sizes its own container.
On a phone the bar spans the width of the screen and the chat opens full screen.
If you want to prevent the changelog notification popup from appearing and instead have the widget display as usual, you can use the disableChangelogNotification option. This can be useful, for example, when you want to hide the popup for selected users.
Productlane.init({
widgetKey: "<your-widget-key />",
disableChangelogNotification: true,
});When set to true, the changelog popup will not appear, and the widget will behave normally. Setting it to false (or omitting it) will keep the default behavior where changelog popups are shown when new changelogs were published.
To remove the Productlane branding from your widget, go to Settings → Widget and turn off the "Show branding" toggle.
Custom links open in a new tab by default. To run your own logic instead, return false from your customLinkClicked handler and the widget will skip opening the URL. You can also leave the URL field blank in Widget Settings to render a link that only fires the event.
Productlane.on("customLinkClicked", (link) => {
if (link.title === "Start tour") {
startProductTour()
return false
}
})The link object has the shape { title?: string; url?: string }. When no URL is configured, link.url is undefined.
Once the widget is initialized, you can use these methods to interact with it.
Productlane.open(view?: "INDEX" | "CHANGELOG" | "DOCS" | "FEEDBACK" | "AICHAT" | "LIVE_CHAT")
Opens the Productlane widget. By default, it opens the last active view, but you can optionally specify a view to open directly:
"INDEX" - opens the main index view with all navigation links.
"CHANGELOG" - opens the latest changelog.
"DOCS" - opens the documentation navigation menu. (Use Productlane.openDocs() method to open a specific article.)
"FEEDBACK" - opens the contact form where users can leave feedback, report bugs, or ask questions.
The
viewargument is case-insensitive, so"changelog","Changelog", and"CHANGELOG"all work the same.
Productlane.close() Closes the widget.
Productlane.toggle() Toggles between opening and closing the widget.
Productlane.disable() Disables the widget across the entire page.
Productlane.enable() Enables the widget.
Productlane.openDocs(documentUrlName | documentId) To open a specific article. You can pass either:
documentUrlName - the URL path of the article, like "get-started/quickstart"
documentId - the internal ID of the document
Productlane.set() For setting certain widget properties. Equal to the settings object in Productlane.init().
Productlane.init({
widgetKey: "<your-widget-key />",
});
Productlane.on("loaded", () => {
Productlane.set({
user: {
email: "[email protected]",
},
});
});Make sure to call any methods after the widget is initialized, e.g. like so:
Productlane.on('loaded', () => {/* Your code here */}).
You can listen to the following events:
Productlane.on("loaded", () => {}) Fires when the widget is fully loaded. Make sure to call any methods in the callback of this event.
Productlane.on("opened", () => {}) Fires after the widget is opened.
Productlane.off("opened", () => {}) Removes event listener for opened event.
Productlane.on("closed", () => {}) Fires after the widget is closed.
Productlane.off("closed", () => {}) Removes event listener for closed event.
Productlane.on("toggled", () => {}) Fires after the widget is toggled.
Productlane.off("toggled", () => {}) Removes event listener for toggled.
Productlane.on("customLinkClicked", (link) => {})
Productlane.on("openDocsPage", ({id, title, urlName}) => {}) Fires when a docs article is opened.
Productlane.on("customLinkClicked", (link) => {
console.log(link); // { "title": "Docs", "url": "productlane.com/docs" }
});Productlane.off("customLinkClicked", () => {}) Removes the event listener for the customLinkClicked event.