Skip to content
Median
Esc
↑↓navigate↵open⌘Jpreview
On this page

Feedback panel

MedianFeedback props, how a note is filed as a bug or a suggestion, and the API routes for notes collected elsewhere.

Live preview. Type in it.
import { MedianFeedback } from "@mediansh/widget";

<MedianFeedback apiKey="median_pk_...">
  <button>Share feedback</button>
</MedianFeedback>;

A visitor writes a note and presses Send. A model files it as a bug or a suggestion in Signal, or drops it as spam. Mounted alongside MedianSupport, the two share a visitor, so a person who writes in and leaves a note is one customer.

Props

PropType
apiKey?string

The browser-safe median_pk_ id, derived from MEDIAN_KEY with publicKeyFromMedianKey. Required unless identity is set.

Typestring
identity?string

Path to a route built with medianIdentity. The same prop MedianSupport takes. See Identity.

Typestring
children?ReactNode

Your trigger. Rendered where you put it, inside an inline span that takes the click. A press opens the panel, a second press closes it. With identity and no apiKey, the trigger renders once the identity route answers, and not at all if the route fails. Pass apiKey too to show it at once.

TypeReactNode
anchor?string

A CSS selector for an element to place the panel against, when children cannot be used. Ignored when children are passed.

Typestring
user?MedianUser

The signed in user, if any. Without it, the note belongs to this browser's visitor.

TypeMedianUser
telemetry?boolean

Send browser context with the note: time zone, language, browser, OS, device, screen, and the page it was written on.

Typeboolean
Defaulttrue
screenshot?boolean

Attach a picture of the page when the note is sent. The panel and anything else Median renders are left out.

Typeboolean
Defaulttrue
diagnostics?boolean | { errors?: boolean; network?: boolean; console?: boolean }

Attach uncaught errors, and optionally failed requests and console.error calls, to the note.

Typeboolean | { errors?: boolean; network?: boolean; console?: boolean }
Defaultfalse
appState?Record<string, string | number | boolean>

Facts about your app to send with the note. Read when the note is sent. Sent only when diagnostics is on.

TypeRecord<string, string | number | boolean>
open?boolean

Hold the open state yourself. See Control from code.

Typeboolean
onOpenChange?(open: boolean) => void

Called when the panel's own controls open or close it. Not called by medianFeedback.open() and the other control functions.

Type(open: boolean) => void
width?string

Any CSS length. The panel never grows wider than the window minus 2rem.

Typestring
Default"20rem"
title?string
Typestring
Default"Share feedback"
description?string
Typestring
Default"Bugs, ideas, anything."
placeholder?string
Typestring
Default"Tell us what could be better."

MedianUser, telemetry, screenshot, diagnostics and appState work as they do on MedianSupport. See Context and diagnostics. To open the panel from a menu or a shortcut, see Control from code.

Where a note goes

The panel says thanks as soon as the note is saved. A model reads it afterwards.

Verdict What happens
Bug Filed on the bug list with a title, a write-up and a priority
Suggestion Filed on the suggestion list the same way
Spam Dropped. Nothing is filed and nobody is notified
  • A note about something already on the list joins that signal instead of adding a row. Ten people with the same complaint make one signal with ten reporters. Each person’s own words stay on their report.
  • Spam is only what nobody could act on: adverts, links to unrelated products, keyboard mashing, and tests of whether the box sends. A rude note is not spam. Neither is a one word complaint.
  • Priority is urgent, high, medium or low.
  • Reading a note uses your workspace’s AI credits. See Plans.
  • If the model cannot read the note, nothing is filed. The visitor has already been thanked.

Turn filing on or off

Notes are filed only while Track bugs and suggestions is on in Agent → Behavior. It is on by default. The same switch controls what the agent files from conversations.

Switch What the panel does
On Takes the note and files it
Off Shows “Feedback is currently unavailable. Try again later.” and keeps the draft
Turned off after a note was sent, before it was read The note is not filed

Pictures

A visitor adds pictures three ways:

  • The Add a picture button, which opens the file picker for images.
  • Dragging an image onto the panel. The box reads “Drop to attach” while it is over the panel.
  • Pasting an image into the panel.
Rule Limit
Pictures per note 6, counting the automatic screenshot
Size 25 MB each
Types Images only. SVG is refused

A picture that breaks a rule stays in the panel with its reason, and Send stays off until it is removed.

Reason on the picture Cause
Larger than the 25 MB limit The file is over 25 MB
Only pictures can go here The file is not an image
This kind of file cannot be sent The file is an SVG or another type browsers can run
Only 6 at a time A seventh picture was added

Automatic screenshot

When the visitor presses Send, the panel takes a picture of the page and adds it to the note.

Case Result
screenshot={false} No picture is taken
The visitor already added 6 pictures No picture is taken
The capture fails or takes over 6 seconds The note is sent without it
The upload fails The send stops, the reason shows, and the draft stays

Pictures show on the report in Signal, and on the Linear or GitHub issue when the signal is pushed to one.

Placement

Setup Where the panel opens
children Against your trigger
anchor, no children Against the element the selector matches, looked up each time the panel is placed
Neither, or anchor matches nothing The bottom right corner of the window

Against a trigger or an anchor:

  • The panel opens 8px below it, or above it when there is not enough room below and more room above.
  • Its left edge lines up with the trigger’s left edge. If that would run off the window, the right edges line up instead.
  • It stays 16px inside the window and moves with scrolling and resizing.

The anchor is only a position. Pressing it does not open or close the panel, and closes an open panel like any press outside. An invalid selector logs Median: the anchor passed to <MedianFeedback> is {selector}, which is not a valid CSS selector. and the panel opens in the corner.

From a dropdown

Choosing a menu item closes the menu and removes the item, so the panel has nothing to sit against. Mount the panel outside the menu and point anchor at the button the menu opens from:

import { MedianFeedback, medianFeedback } from "@mediansh/widget";

<MedianFeedback apiKey="median_pk_..." anchor="#account-button" />;

<DropdownMenuItem onSelect={medianFeedback.open}>
  Share feedback
</DropdownMenuItem>;

Sending

Key Does
Enter Adds a new line
Cmd+Enter or Ctrl+Enter Sends
Escape Closes the panel
  • Send is off while the box is empty or a picture has a problem.
  • After a send the panel shows “Thanks for your feedback.” and closes itself 1.6 seconds later. It opens empty next time.
  • Closing the panel without sending keeps the draft until the page reloads.
  • A failed send keeps the draft and the pictures and shows the reason under the box.
Notice under the box Cause
Feedback is currently unavailable. Try again later. Track bugs and suggestions is off
Enter your feedback before sending. The note reached the server empty
Still connecting. Try again in a moment. The panel had no session yet
Too many requests. Wait a moment and try again. A rate limit. See Rate limits

A “Powered by Median” line sits beside the buttons. Hide it with Show Median branding in Settings → Billing, on a plan that allows it. See Plans.

Limits

What Limit
Note 2,000 characters. The box stops there, and the server trims the note and cuts it at 2,000
Pictures 6 per note, 25 MB each, images only
Requests See Rate limits

Send notes from the CLI, API or MCP

Send notes collected outside the browser, such as app store reviews or survey answers, through the same reader. These routes wait for the verdict, usually a second or two, and return it.

median feedback submit --from user_8812 --body "The export button does nothing on my phone." --image https://files.acme.com/8812.png
curl https://api.median.sh/v1/feedback \
  -H "Authorization: Bearer median_key_..." \
  -H "content-type: application/json" \
  -d '{"session":"user_8812","body":"The export button does nothing on my phone.","images":["https://files.acme.com/8812.png"]}'
await median.feedback.submit({
  sessionId: "user_8812",
  body: "The export button on the billing page does nothing on my phone.",
  images: [
    { url: "https://files.acme.com/8812.png", name: "Billing page on iOS" },
  ],
});

The HTTP route is POST /v1/feedback. It takes a Median key or an OAuth token. See Authentication. Every CLI flag is in the CLI reference.

Field Type Rules
session string, required A stable id for the person, such as your user id. Notes with the same session come from one reporter. 8 to 128 characters after trimming. Otherwise the request fails with “Session tokens are 8 to 128 characters.”
body string, required The note. Trimmed and cut at 2,000 characters
user { name?, email?, avatarUrl? } Who wrote it
page { url?, title?, referrer? } Where they were. Only http and https URLs are kept, without the query string or fragment
images (string | { url, name? })[] Picture addresses. Addresses that are not http or https, or longer than 2,000 characters, are dropped. An entry that is not a string or an object with a string url fails the request, for example "images[0]" has to be a URL or an object with a "url". So does a name that is not a string. A repeated address counts once, and only the last 6 are kept

Over MCP, send sessionId in place of session, and pass each image as { url, name? }.

The response is { outcome, signalId }.

outcome Meaning signalId CLI prints
bug Filed on the bug list, or joined a signal already there The signal’s id Filed as a bug (<signal id>).
suggestion Filed on the suggestion list, or joined a signal already there The signal’s id Filed as a suggestion (<signal id>).
spam Dropped null Read as spam, so nothing was filed.
unread Nothing filed. The reader was unavailable, the note was empty, or Track bugs and suggestions is off null Nothing was filed: the reader was unavailable, or filing is off for this organization.

Was this page helpful?