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

Support widget

MedianSupport props, the user object, the dashboard settings that change it, and its console messages.

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

<MedianSupport apiKey="median_pk_..." />;

With an identity route, pass its path instead:

<MedianSupport identity="/api/median/identity" />;

Mount it once, near the root of your app. It places itself in the bottom right corner. Do not wrap it in dynamic(). The reply renderer already loads on its own when the pointer reaches the launcher, when the panel opens, or when the page is idle with an answered thread behind the button.

What a visitor sees in a thread is on Conversations. Opening the panel from your own code is on Control from code.

Props

The props type is MedianSupportProps. launcher takes a SupportLauncherKind, "bubble" | "hidden".

PropType
apiKey?string

The median_pk_ id from publicKeyFromMedianKey(MEDIAN_KEY). Required unless identity is set.

Typestring
identity?string

Path to a route built with medianIdentity. It returns the key and the signed user. Required unless apiKey is set. Pass both to show the launcher before the route answers.

Typestring
user?MedianUser

The signed in person. Replaces the user the identity route returns. See MedianUser below.

TypeMedianUser
telemetry?boolean

Send time zone, language, browser, OS, device, screen and page with each message. False reads none of it.

Typeboolean
Defaulttrue
screenshot?boolean

Let the agent ask to see the visitor's screen. False tells the agent no picture is coming.

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

Record uncaught errors, and optionally failed requests and console.error calls, and send them with the next message.

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

Your own values for bug reports, such as a build or a route. Read when a message is sent. Sent only while diagnostics is on.

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

Show a Sign in to chat panel instead of the composer until user names somebody.

Typeboolean
Defaultfalse
onSignIn?() => void

Runs when the visitor presses Sign in on that panel. Without it, the panel has no button.

Type() => void
requireEmail?boolean

Ask for an email before the first message. Skipped when user.email is set or this browser already answered. The Ask for an email switch in the dashboard can also turn it off.

Typeboolean
Defaulttrue
messagePreview?boolean

Show the newest unread reply as a card over the closed launcher. The count shows either way. The Preview replies switch in the dashboard can also turn it off.

Typeboolean
Defaulttrue
titleCount?boolean

Put the unread count in the tab title, as (1) Your page. False never touches the title.

Typeboolean
Defaulttrue
launcher?"bubble" | "hidden"

Hidden renders no button. The panel still opens in the corner when your code opens it.

Type"bubble" | "hidden"
Default"bubble"
open?boolean

Control the open state yourself. While it is set, open(), close() and toggle() from useMedianSupport and medianSupport do nothing.

Typeboolean
onOpenChange?(open: boolean) => void

Called when the launcher, the unread card, the close button, Escape, a press on the modal's backdrop, or a destination press on a phone or in the modal opens or closes the panel. Not called for your own open(), close() and toggle() calls.

Type(open: boolean) => void
onNavigate?(path: string) => void

Called with a path on this site when the visitor presses Take me there on a page one of your tools offered. Without it, no button is shown.

Type(path: string) => void

Diagnostics, screenshots, telemetry and appState limits are on Context and diagnostics. Destinations are on Pages and highlights.

MedianUser

Every field is optional.

Field Type Effect Limit
id string Your id for this person. With a matching hash, their conversations follow them across devices Up to 128 characters, checked exactly as sent. Longer fails the signature check
hash string id signed from MEDIAN_KEY on your server. Without it, id is ignored
name string Shown in the queue and above the thread Trimmed, cut at 80 characters
email string Shown on the customer panel. Skips the email question Trimmed, cut at 320 characters
avatarUrl string Shown on the conversation and their messages https only, up to 512 characters. Anything else is dropped
metadata Record<string, string> Extra rows for your team, such as plan or seats 16 entries. Keys and values cut at 200 characters
const { data: session } = useSession();

<MedianSupport
  apiKey="median_pk_..."
  user={
    session
      ? {
          name: session.user.name,
          email: session.user.email,
          avatarUrl: session.user.image,
          metadata: { plan: session.org.plan, seats: String(session.org.seats) },
        }
      : undefined
  }
/>;
  • Nothing is stored until the visitor sends a message. The first message saves user, and later changes sync as they happen. Pass user on every render, as soon as your session has it.
  • A new name, email or avatarUrl replaces the stored one. A missing field or an empty string leaves the stored value alone.
  • metadata is replaced as a whole. Keys missing from the newest object are removed. An empty object is ignored.
  • Metadata keys display as labels. signed_up_at and signedUpAt both show as Signed up at. Values show exactly as sent, so format dates and numbers yourself.
  • Invalid values are cut or dropped. They never block a message.
  • Without a name or an email, your team sees the visitor as Customer #12.

Signing id and signing out are on Identity.

Dashboard settings

These change every installed widget without a redeploy.

Setting Where Effect
Name Settings → General Header title, the launcher’s label, and How can we help you with Acme? on a new conversation
Picture Agent → Profile The agent’s face in the header and on its replies
Name Agent → Profile The agent’s name on its replies
Ask for an email Agent → Behavior Off turns the email question off, whatever requireEmail says
Preview replies Agent → Behavior Off hides the unread card, whatever messagePreview says
Show Median branding Settings → Billing, under Add-ons Off hides the Powered by Median line under the composer and on the sign in panel. Needs the Pro plan. See Plans

Powered by Median has no prop. Only the dashboard switch hides it. The widget checks every 60 seconds and shows the line again if the check fails or the paid period ends.

Troubleshooting

Errors inside the widget never break your page. The widget hides itself and prints to the console.

Errors

Printed in every build.

Console message Fix
Median: the apiKey passed to <MedianSupport> is "…", which does not look like the browser-safe id from publicKeyFromMedianKey(MEDIAN_KEY). It should start with median_pk_. Pass the id from publicKeyFromMedianKey, not MEDIAN_KEY itself
Median did not recognize the apiKey passed to <MedianSupport>, so it has been hidden. Derive it from an active MEDIAN_KEY with publicKeyFromMedianKey. No organization has this id. It was deleted or mistyped. Derive it from a current key in Settings → API
Median: <MedianSupport> was given neither apiKey nor identity, so nothing is on the page. Pass the median_pk_ id from publicKeyFromMedianKey(MEDIAN_KEY), or the path of a route built with medianIdentity(). Pass apiKey or identity
Median: could not read the identity route. followed by /api/median/identity answered 500. It should return { apiKey, user } from medianIdentity(). Fix the route. With identity alone, nothing renders. Pass apiKey too to keep the widget up
Median: the user hash passed to <MedianSupport> did not match. Sign the same string you pass as `user.id` with the Median key that produced this widget id. Until it matches, this visitor gets a conversation of their own on every device. Sign the exact string you pass as user.id, with the key this widget id came from
Median: a user hash was passed to <MedianSupport>, but its widget id and hash came from different Median keys. Derive both from the same key. Derive apiKey and hash from one current MEDIAN_KEY
Median: refused to navigate to https://…, which is not a path on this site. A tool offered another origin. Return a path that starts with /
Median: a tool asked to highlight <selector>, which is not a valid CSS selector. A tool returned a highlight the browser cannot parse. Return a valid CSS selector
Median: mountMedianSupport() was called where there is no document, so nothing was mounted. Call it in the browser, after the page exists. Call it in the browser, after the page exists
Sending from the support widget failed followed by the error A send failed. A red line shows above the composer and the draft stays, so the visitor can send it again
Rating the conversation failed followed by the error The visitor’s rating did not save. The panel shows Could not submit your rating. Try again.
Median: <MedianSupport> hit an error and has been hidden. Your page is unaffected. Any other error. The original error is printed after it

Warnings

Printed only when your bundle sets process.env.NODE_ENV to development.

Console message Fix
Median: more than one <MedianSupport> is mounted. They share one open state and open together. Mount it once, near the root of your app. Mount one MedianSupport or MedianSupportModal, not two
Median: the widget was asked to open, but no <MedianSupport> is on the page. Mount it once, near the root of your app. open() ran with nothing mounted. It prints after one second, and a panel that mounts later still opens
Median: a tool offered to take this visitor to a page, but <MedianSupport> has no onNavigate, so no button is shown for it. Pass one, such as onNavigate={router.push}. Pass onNavigate, such as router.push

Median: support read status could not be synced and Median: the picture the agent asked for could not be uploaded print in every build and need no action.

No console message

Symptom Cause
The widget renders unstyled @mediansh/widget/styles.css is not imported
One person shows up twice in your inbox They used two devices without a signed user.id. See Identity
The sign in panel shows for a signed in person user is empty or arrives late. Pass it as soon as your session loads
The email question shows although you pass user user.email is missing or empty

Was this page helpful?