Skip to content

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>
  1. Find your project ID

    1. Open the Reqio dashboard.
    2. Select your project.
    3. Go to Settings → Widget.
    4. Copy the project ID from the embed snippet.
  2. Paste the script tag before </body>

    Replace YOUR_PROJECT_ID with 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.

  3. 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.

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.

featuretarget
Make a request (public feature)
bugtarget
Something's broken (bug report)
unexpectedtarget
Something was off
feedbacktarget
Share feedback, with a rating
questiontarget
Ask a question
updatestarget
The visitor's activity / updates feed
(omitted or unrecognised)target
The normal chooser

If 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 server

The 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.