How to get there: Go to Settings → Share & embed → Widgets → click a board card or pick another widget kind.
ProductLift widgets let your users interact with your boards, submit feedback, or see your changelog without leaving your app or website. With single sign-on, they don't even need to log in to ProductLift.
You can run multiple widgets side-by-side, for example a roadmap embed and a changelog popup.
When you open the Widgets page, the create flow is board-first:
The list page shows your existing widgets as clean rows; click a row to open it.
Each widget's edit page has three tabs (in this order):
For a Feedback Form (post widget), you can attach page context automatically to help investigate bug reports.
log, warn, and error messages from the page, recorded after the ProductLift SDK starts capturing. For example, a checkout error can include Payment failed: request timed out.These options use the JavaScript SDK on the page where feedback is submitted. A standalone iframe cannot capture its parent page's console logs, screenshot, or system info. Test from your installed widget so you can confirm the captured context comes from your website.
Open the submitted post as an admin to view its captured environment data, screenshot, and console logs. This context is admin-only.
For example, enable all three options for a checkout bug-report widget. A customer describes a stuck payment, previews the screenshot, and submits. Your team can then inspect the checkout URL, browser details, console error, and screenshot together with the report.
On the Installation tab, the Where will you install this widget? picker has presets for the most common platforms:
A "More platforms" group adds:
Each platform shows a tailored hint and (for embed widgets) the simplest install method, usually an iframe code block with an inline Copy button. For sidebar/popup widgets the page shows the SDK trigger snippet.
For embed widgets you can also expand Install via SDK to use the JavaScript SDK instead of an iframe (gives you SSO, dynamic resizing, and more control).
Sidebars slide in from the side of the page on demand. Add a trigger button or link, and the widget opens over your current page.

Embed widgets render the board inline on a page of your choice. Just paste the iframe (or SDK) code into the area where you want the board to show up.

You can fully customize the embedded design with Custom (S)CSS.
With single sign-on (SSO), your users don't have to create a separate ProductLift account. The widget recognizes them automatically.
The Installation tab has a collapsible Single Sign-On section with three options:
functions.php or a custom HTML plugin.data-productlift-link to your links/iframes for it to attach.?sso=TOKEN on the SDK URL or any link.See the Single Sign-On guide for the full reference.
When a widget points to a changelog board and the user is identified via SSO, ProductLift can mark the trigger element when there are new posts the user hasn't seen yet. The Installation tab shows an Unread badge link with a copy-able starter CSS snippet. Paste it into your stylesheet to render a red dot on the trigger; the dot clears once the user opens the changelog.
See Changelog widgets for the full description.
Alongside the productlift-has-unread class, the SDK also sets a data-productlift-unread-count attribute on each flagged trigger. That lets you render the actual count (e.g. "3 new updates") instead of a plain dot.
Pure-CSS approach (no JavaScript needed):
.productlift-has-unread::after {
content: attr(data-productlift-unread-count);
/* your own bubble styling */
background: #e53935;
color: #fff;
border-radius: 10px;
padding: 2px 6px;
font-size: 11px;
}
Programmatic approach (React, Vue, or any framework where you want the count in state):
// Preferred: react to the SDK's own signal so you don't race with page load.
window.addEventListener('productlift:unread-loaded', function (e) {
// e.detail.counts is { widget_key: number, ... }
const n = e.detail.counts['your-widget-key'] || 0;
// …update your UI here
});
// Or read it on demand once the SDK has loaded:
const n = ProductLift.getUnreadCount('your-widget-key');
getUnreadCount(key) returns a number (0 when nothing is unread) once the map has loaded, or undefined before it loads. The productlift:unread-loaded event fires exactly once per page load with the full { widget_key: count } map.
When the user opens the widget, three things happen:
productlift-has-unread class and the data-productlift-unread-count attribute immediately, so the badge disappears without waiting for the network.localStorage (10 minutes) so a reload before the server has caught up doesn't flicker the badge back.The count is cached per user for up to 1 hour, but the cache key busts automatically whenever the user opens the tab or a new changelog post is published, so counts stay fresh in practice.
For now, the unread count only reflects changelog posts. Feedback and roadmap tabs deliberately don't emit unread counts because "unread" is ambiguous there (drafts, status changes, comments). If you need counts for those, let us know your use case.
Below is an example of how you can add a ProductLift widget in a Vue 2 app using the SDK with a server-issued SSO token:
export default {
data: () => ({
productlift_sso_token: null,
}),
async created() {
this.$http.get('/user/' + this.$auth.user().id + '/productlift_token')
.then(({data}) => {
this.productlift_sso_token = data.token
let ProductLiftScript = document.createElement('script')
ProductLiftScript.src = 'https://your-portal.productlift.dev/widgets_sdk?sso=' + this.productlift_sso_token
ProductLiftScript.defer = true;
document.head.appendChild(ProductLiftScript)
})
.catch(error => {
console.log(error.response)
})
},
}
Below is an example of how you can add ProductLift widgets in React (thanks to Mike!).
function useProductLift() {
async function init_widgets() {
return new Promise(async (resolve) => {
const { data } = await fetch('/productlift/widgets', { method: "POST" });
let ProductLiftScript = document.createElement('script');
ProductLiftScript.src = data;
ProductLiftScript.defer = true;
document.head.appendChild(ProductLiftScript);
resolve(true);
});
}
return init_widgets;
}
function FeedbackTrigger() {
const [ready, set_ready] = useState(false);
const init_widgets = useProductLift();
function init() {
init_widgets().then(() => set_ready(true));
}
useEffect(() => { init(); }, []);
if (!ready) return null;
return <a href="#" data-productlift-sidebar="WIDGET_UUID">Open Widget</a>;
}