> ## Documentation Index
> Fetch the complete documentation index at: https://formbricks.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Docs Feedback

> A step-by-step guide to getting feedback on your Documentation with Formbricks

Docs Feedback allows you to measure how clear your documentation is.

## Purpose

Your users don’t spend as much time thinking about your product as you do. To fight the "Curse of Knowledge" you have to measure how clear your docs are.

## Installation

To get this running, you'll need a bit of time. Here are the steps we're going through:

* Set up Formbricks Cloud

* Build the frontend

* Connect to API

* Test

## 1. Setting up Formbricks Cloud

Create a [Formbricks Cloud](https://app.formbricks.com/auth/signup) account, then a survey from the **Docs Feedback** template — **New Survey → Choose a template**, then search for it.

### Make it a link survey

Open **Settings → Survey Type** and make sure **Link survey** is selected. A new survey inherits its type
from the workspace channel chosen during onboarding, so a workspace set up for a website or app creates
**app** surveys by default — check it rather than assuming it.

This matters more than it looks. You are going to post responses to the client API from your own frontend, so nothing here uses the Formbricks widget. An **app** survey cannot be activated without a trigger — `survey-menu-bar.tsx` refuses to save one with no triggers — which is why older versions of this guide told you to invent an action that never fires. A link survey has no such requirement, so the workaround is unnecessary.

### Set the question IDs

The frontend below refers to questions by ID, so give them names you will recognise. Open a question, click **Show Question settings** at the foot of its card, and set the **Question ID**:

| Question | ID |
| - | - |
| Is this page helpful? | `isHelpful` |
| Please elaborate | `additionalFeedback` |
| Page URL | `pageUrl` |

> [!NOTE]
>
> The **answers** must match the frontend too. If you replace "Yes 👍" and "No 👎" with your own wording, change the choices in the code below to match — they are compared as strings.

> [!WARNING]
>
> Question IDs can only be changed **before** a survey is activated. Afterwards they are fixed, because the responses already collected refer to them. Set them now.

Activate the survey.

## 2. Build the frontend

> [!NOTE]
>
> Your frontend might work differently Your frontend likely looks and works
> differently. This is an example specific to our tech stack. We want to
> illustrate what you should consider building yours.

Before we start, lets talk about the widget. It works like this:

* Once the user selects yes/no, a partial response is sent to the Formbricks API. It includes the feedback and the current page url.

* Then the user is presented with an additional open text field to further explain their choice. Once it's submitted, the previous response is updated with the additional feedback.

This allows us to capture and analyze partial feedback where the user is not willing to provide additional information.

**Let's do this 👇**

* Open the code editor where you handle your docs page.

* Likely, you have a template file or similar which renders the navigation at the bottom of the page:

![doc navigation](https://mintcdn.com/formbricks/HVXK9qhSV5tjWeAg/images/surveys/best-practices/docs-feedback/docs-navi.webp?fit=max\&auto=format\&n=HVXK9qhSV5tjWeAg\&q=85\&s=4f0fb1f90d2ff578fd40700a675d0232)

Locate that file. We are using the [Tailwind Template “Syntax”](https://tailwindui.com/templates/syntax) in this case.

* Write the frontend code for the widget. Here is the full component (we break it down right below):

```tsx Entire Widget theme={null}
import { Button } from "@/modules/ui/components/Button";
import { Popover, PopoverContent, PopoverTrigger } from "@/modules/ui/popover";
import { useRouter } from "next/router";
import { useState } from "react";
import {
  handleFeedbackSubmit,
  updateFeedback,
} from "../../lib/handleFeedbackSubmit";

export const DocsFeedback = () => {
  const router = useRouter();
  const [isOpen, setIsOpen] = useState(false);
  const [sharedFeedback, setSharedFeedback] = useState(false);
  const [responseId, setResponseId] = useState(null);
  const [freeText, setFreeText] = useState("");

  if (
    !process.env.NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID ||
    !process.env.NEXT_PUBLIC_FORMBRICKS_COM_API_HOST ||
    !process.env.NEXT_PUBLIC_FORMBRICKS_COM_WORKSPACE_ID
  ) {
    return null;
  }

  return (
    <div className="mt-6 inline-flex cursor-default items-center rounded-md border border-slate-200 bg-white p-4 text-slate-800 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300">
      {!sharedFeedback ? (
        <div>
          Was this page helpful?
          <Popover open={isOpen} onOpenChange={setIsOpen}>
            <div className="ml-4 inline-flex space-x-3">
              {["Yes 👍", " No 👎"].map((option) => (
                <PopoverTrigger
                  className="rounded border border-slate-200 bg-slate-50 px-4 py-2 text-slate-900 hover:bg-slate-100 hover:text-slate-600 focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1 dark:border-slate-700 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600 dark:hover:text-slate-300"
                  onClick={async () => {
                    const id = await handleFeedbackSubmit(
                      option,
                      router.asPath
                    );
                    setResponseId(id);
                  }}
                >
                  {option}
                </PopoverTrigger>
              ))}
            </div>
            <PopoverContent className="border-slate-300 bg-white dark:border-slate-500 dark:bg-slate-700">
              <form>
                <textarea
                  value={freeText}
                  onChange={(e) => setFreeText(e.target.value)}
                  placeholder="Please explain why..."
                  className="focus:border-brand-dark focus:ring-brand-dark mb-2 w-full rounded-md bg-white text-sm text-slate-900 dark:bg-slate-600 dark:text-slate-200 dark:placeholder:text-slate-200"
                />
                <div className="text-right">
                  <Button
                    type="submit"
                    variant="primary"
                    onClick={(e) => {
                      e.preventDefault();
                      updateFeedback(freeText, responseId);
                      setIsOpen(false);
                      setFreeText("");
                      setSharedFeedback(true);
                    }}
                  >
                    Send
                  </Button>
                </div>
              </form>
            </PopoverContent>
          </Popover>
        </div>
      ) : (
        <div>Thanks a lot, boss! 🤝</div>
      )}
    </div>
  );
};
```

**Let’s break it down!**

Setting the local states and getting the current URL:

```tsx State Management theme={null}
const router = useRouter(); // to get the URL of the current docs page
const [isOpen, setIsOpen] = useState(false); // to close Popover after
const [sharedFeedback, setSharedFeedback] = useState(false); // to display Thank You message
const [responseId, setResponseId] = useState(null); // to store responseID (will explain more)
const [freeText, setFreeText] = useState(""); // to locally store the additional info provided by user
```

Disabling feedback if config environment variables are not set properly:

```tsx Disable feedback if incorrect config env vars theme={null}
if (
  !process.env.NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID ||
  !process.env.NEXT_PUBLIC_FORMBRICKS_COM_API_HOST ||
  !process.env.NEXT_PUBLIC_FORMBRICKS_COM_WORKSPACE_ID
) {
  return null;
}
```

The actual frontend (read comments):

```tsx Actual Frontend theme={null}
return (
    <div className="mt-6 inline-flex cursor-default items-center rounded-md border border-slate-200 bg-white p-4 text-slate-800 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300">
      {!sharedFeedback ? ( // displays Feedback buttons or Thank You message
        <div>
          Was this page helpful?
          <Popover open={isOpen} onOpenChange={setIsOpen}>
            <div className="ml-4 inline-flex space-x-3">
              {["Yes 👍", " No 👎"].map((option) => ( // Popup Trigger is a button as well. This is a workaround to open the same form but send a different response to the API
                <PopoverTrigger
                  className="rounded border border-slate-200 bg-slate-50 px-4 py-2 text-slate-900 hover:bg-slate-100 hover:text-slate-600 focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1 dark:border-slate-700 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600 dark:hover:text-slate-300"
                  onClick={async () => {
                    const id = await handleFeedbackSubmit(option, router.asPath); // handleFeedbackSubmit sends the Yes / No choice as well as the current URL to Formbricks and returns the responseId
                    setResponseId(id); // add responseId to local state so we can use it if user decides to add more feedback in free text field
                  }}>
                  {option} // "Yes 👍" or "No 👎" - they have to be identical with the choices in the survey on app.formbricks.com for it to work (!)
                </PopoverTrigger>
              ))}
            </div>
            <PopoverContent className="border-slate-300 bg-white dark:border-slate-500 dark:bg-slate-700">
              <form> // Form to handle additional feedback by user
                <textarea
                  value={freeText}
                  onChange={(e) => setFreeText(e.target.value)}
                  placeholder="Please explain why..."
                  className="focus:border-brand-dark focus:ring-brand-dark mb-2 w-full rounded-md bg-white text-sm text-slate-900 dark:bg-slate-600 dark:text-slate-200 dark:placeholder:text-slate-200"
                />
                <div className="text-right">
                  <Button
                    type="submit"
                    variant="primary"
                    onClick={(e) => {
                      e.preventDefault(); // prevent page from reloading (default HTML behaviour)
                      updateFeedback(freeText, responseId); // update initial Yes / No response with free text feedback
                      setIsOpen(false); // close Popover
                      setFreeText(""); // remove feedback from free text field local state
                      setSharedFeedback(true); // display Thank You message
                    }}>
                    Send
                  </Button>
                </div>
              </form>
            </PopoverContent>
          </Popover>
        </div>
      ) : (
        <div>Thanks a lot, boss! 🤝</div> // Thank You message
      )}
    </div>
  );
}
```

## 3. Connecting to the Formbricks API

The last step is to hook up your new frontend to the Formbricks API. To achieve that, we followed the “[Create Response](https://formbricks.com/docs/api-reference/client-api--response/create-response.md)” and “[Update Response](https://formbricks.com/docs/api-reference/client-api--response/update-response.md)” pages in our docs.

Here is the code for the `handleFeedbackSubmit` function with comments:

```tsx handleFeedbackSubmit() function definition theme={null}
export const handleFeedbackSubmit = async (YesNo, pageUrl) => {
  const response_data = {
    data: {
      isHelpful: YesNo, // the "Yes 👍" or "No 👎" response. Remember: They have to be identical with the choices in the survey on app.formbricks.com for it to work.
      pageUrl: pageUrl, // So you know which page the user gives feedback about.
    },
  };

  const payload = {
    response: response_data,
    surveyId: process.env.NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID, // For testing, replace this with the survey ID of your survey (more info below)
  };

  try {
    const res = await fetch(
      `${process.env.NEXT_PUBLIC_FORMBRICKS_COM_API_HOST}/api/v1/client/${process.env.NEXT_PUBLIC_FORMBRICKS_COM_WORKSPACE_ID}/responses`, // For testing, replace this with the API host and workspace ID of your Development workspace on app.formbricks.com
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(payload),
      }
    );

    if (res.ok) {
      const responseJson = await res.json();
      return responseJson.id; // Returns the response ID
    } else {
      console.error("Error submitting form");
    }
  } catch (error) {
    console.error("Error submitting form:", error);
  }
};
```

And this is the `updateFeedback` function with comments:

```tsx updateFeedback() function definition theme={null}
export const updateFeedback = async (freeText, responseId) => {
  if (!responseId) {
    console.error("No response ID available"); // If there is not response ID, no response can be updated.
    return;
  }

  const payload = {
    response: {
      data: {
        additionalFeedback: freeText,
      },
      finished: true, // Lets Formbricks calculate Completion Rate
    },
  };

  try {
    const res = await fetch(
      `${process.env.NEXT_PUBLIC_FORMBRICKS_COM_API_HOST}/api/v1/client/${process.env.NEXT_PUBLIC_FORMBRICKS_COM_WORKSPACE_ID}/responses/${responseId}`,
      {
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(payload),
      }
    );

    if (!res.ok) {
      console.error("Error updating response");
    }
  } catch (error) {
    console.error("Error updating response:", error);
  }
};
```

We're almosr there! 🤸

## 4. Setting it up for testing

Before you roll it out in production, you want to test it. To do so, you need two things:

* the **workspace ID**
* the **survey ID** of your test survey

Open the survey and read both out of the address bar — they are the two path segments:

```
https://app.formbricks.com/workspaces/<workspaceId>/surveys/<surveyId>/summary
```

Now, you have to replace the IDs and the API host accordingly in your `handleFeedbackSubmit`:

```tsx Replace the ID and API accordingly theme={null}
  const payload = {
    response: response_data,
    surveyId: clgwfv4a7002el50ihyuss38x, // This is an example, replace with yours
  };

  try {
    const res = await fetch(
			// Note that we also updated the API host to 'https://app.formbricks.com/'
      `https://app.formbricks.com/api/v1/client/clgwcwp4z000lpf0hur7uxbuv/responses`,
```

And lastly, in the `updateFeedback` function

```tsx Replace the ID and API here as well theme={null}
  try {
    const res = await fetch(
		// Note that we also updated the API host to 'https://app.formbricks.com/'
      `https://app.formbricks.com/api/v1/client/clgwcwp4z000lpf0hur7uxbuv/responses/${responseId}`, // Note that we also updated the API host to 'https://app.formbricks.com/'
```

### You’re good to go! 🎉

Something doesn’t work? Check your browser console for the error.

**Need help?** [Reach out in GitHub Discussions](https://github.com/formbricks/formbricks/discussions)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.