HomeDocsDeveloperInstall the widget
Developer
Install the widget
Add the Reqio feedback widget to your site with a single script tag. It runs inside an isolated Shadow DOM with no impact on your page styles.
Embed this before </body>
<script src="https://reqio.app/widget.js" data-project-id="YOUR_PROJECT_ID" async></script>Find your project ID
- Open the Reqio dashboard.
- Select your project.
- Go to Settings → Widget.
- Copy the project ID from the embed snippet.
Paste the script tag before </body>
Replace
YOUR_PROJECT_IDwith the project ID from the previous step:<script src="https://reqio.app/widget.js" data-project-id="YOUR_PROJECT_ID" async ></script>The widget initialises automatically when the script loads. It mounts a feedback button onto your page inside an isolated Shadow DOM (
<div id="reqio-widget-root">), so your existing styles are unaffected and no CSS conflicts are possible.Verify it worked
Load your page, click the feedback button, and submit a test request. It appears in your Reqio dashboard immediately.
async is required
The async attribute is required. The widget script uses document.currentScript with a fallback querySelector so it resolves your data-project-id correctly whether the script is loaded synchronously, asynchronously, or injected dynamically after page load.
Double-load protection
The widget checks for an existing reqio-widget-root element before mounting. A second <script> tag on the same page is a no-op. window.Reqio.identify is still installed and the queue is still drained, but a second widget instance is never created.
Framework installs
import { useEffect } from "react";
type ReqioWidgetProps = {
projectId: string;
identityToken?: string;
};
export function ReqioWidget({ projectId, identityToken }: ReqioWidgetProps) {
useEffect(() => {
const script = document.createElement("script");
script.src = "https://reqio.app/widget.js";
script.async = true;
script.dataset.projectId = projectId;
if (identityToken) script.dataset.identityToken = identityToken;
document.body.appendChild(script);
return () => {
script.remove();
document.getElementById("reqio-widget-root")?.remove();
};
}, [projectId, identityToken]);
return null;
}The cleanup removes both the script element and the mounted reqio-widget-root element, so navigating away, or unmounting in a test, leaves nothing behind. This also makes the component safe under React StrictMode's dev-only double-invoke: the effect tears down fully between the two runs, and even if it didn't, the widget's own mount guard means a second mount is never created.
Use next/script with the afterInteractive strategy so the widget loads after your page is interactive, without blocking hydration. This works the same way in the App Router and the Pages Router:
import Script from "next/script";
<Script
src="https://reqio.app/widget.js"
strategy="afterInteractive"
data-project-id="YOUR_PROJECT_ID"
data-identity-token={identityToken}
/>Omit data-identity-token if you are not using signed identity, or pass it once your server has minted a token for the current user.
If you only want the widget on certain routes (a docs section, a signed-in area, a subset of pages), inject the script tag with JavaScript instead of hardcoding it in your HTML:
const script = document.createElement("script");
script.src = "https://reqio.app/widget.js";
script.async = true;
script.dataset.projectId = "YOUR_PROJECT_ID";
document.body.appendChild(script);Run this wherever your routing logic decides the widget should appear, for example a page-level effect or a router navigation handler. To pass an identity token at the same time, set it before appending the script:
script.dataset.identityToken = token; // optional, minted by your serverInjecting the script this way sets document.currentScript to null, but the loader falls back to querySelector('script[data-project-id]'), so data-project-id and data-identity-token still resolve correctly as long as they are set on the element before it is appended.
You can install the widget through a tag manager instead of editing your site's code directly:
- Create a new Custom HTML tag.
- Paste the standard embed snippet into it (the same tag from the top of this page).
- Choose a Page View trigger scoped to the pages you want the widget on (all pages, or a URL pattern for a subset).
- Publish the container.
If the tag fires more than once, for example a single-page app that re-triggers page views, or two containers loading on the same page, that's harmless: the widget's mount guard means a second load never creates a second instance.
One limitation: if you're using signed identity (data-identity-token), the token has to be minted by your server for the specific signed-in user, and a tag manager has no access to your server or the current session. Tag managers work well for the anonymous embed. Identity-token setups belong in your application code, not the tag manager.
Open a specific flow
Turn any button or link on your page into an entry point that opens the widget straight into a specific flow, instead of the normal chooser. This works for a "Report a bug" link in your footer, a "Request a feature" button on a changelog page, or a help-menu item that jumps straight to the visitor's own activity feed.
Add a data-reqio-open attribute to any element. Clicking it opens the widget directly into that flow:
<button data-reqio-open="feature">Request a feature</button>
<a href="#" data-reqio-open="bug">Report a bug</a>The loader attaches a single delegated click listener on document, so this also works on elements added to the page after the widget script has already loaded, no re-registration needed.
window.Reqio.open("question"); // opens straight into "Ask a question"
window.Reqio.open("updates"); // opens the visitor's activity feed
window.Reqio.open(); // opens the normal chooserwindow.Reqio.open is installed the same way as window.Reqio.identify: you can call it before the widget script has finished loading. Calls made early are queued and replayed, in order, once the widget initialises.
featuretargetbugtargetunexpectedtargetfeedbacktargetquestiontargetupdatestarget(omitted or unrecognised)targetIf the widget is already open, calling open() (or clicking a data-reqio-open element) just switches it to the requested flow, and never opens a second instance. An empty or unrecognised target is also safe: it falls back to the chooser instead of throwing.
This composes with identity: if the visitor has already been identified via data-identity-token or window.Reqio.identify(), the flow you open is enriched exactly the same way a chooser-opened flow would be, and the optional email ask still applies where it normally would.
Identify visitors
If your server has already minted a signed identity token for the current user, pass it inline via data-identity-token. The widget sends the token on load without any additional JavaScript call.
<script
src="https://reqio.app/widget.js"
data-project-id="YOUR_PROJECT_ID"
data-identity-token="<token minted by your server>"
async
></script>This is the recommended path for server-rendered pages (Next.js Server Components, Rails ERB, Django templates, etc.) because the token is ready before the page reaches the browser.
If you cannot pass the token at render time (for example, a SPA where the user logs in after the initial page load), call window.Reqio.identify() after the user authenticates:
window.Reqio.identify(token); // token is a string minted by your serverThe widget installs window.Reqio.identify before it mounts, so you can call it before or after the widget has finished loading. Calls made before the widget is ready are queued and replayed once it initialises.