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

Contact form

Props and behavior for MedianContactForm, plus useMedianContact and sendMedianContact for a form of your own.

Live preview. Press Send message with it empty.
import { MedianContactForm } from "@mediansh/widget";

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

The form sends the same message the support widget would. The agent answers first, and your team can take over in the inbox. Mounted alongside MedianSupport, the two share a visitor, so a person who writes here and later opens the widget finds the same thread.

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. The form shows but stays switched off until the route answers. See Identity.

Typestring
fields?MedianContactField[]

Fields to ask for besides the email and the message.

TypeMedianContactField[]
user?MedianUser

The signed in user, if any. Their email and name fill empty boxes. What the visitor types wins.

TypeMedianUser
telemetry?boolean

Send browser context with the message: time zone, language, browser, OS, device, screen, and the page it was sent from.

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

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

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

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

TypeRecord<string, string | number | boolean>
title?string
Typestring
Default"Contact us"
description?string
Typestring
Default"We'll reply by email."
onSent?() => void

Called after the message is delivered, before the thank-you shows. If it throws, the error goes to the console and the thank-you still shows.

Type() => void

MedianUser, telemetry, diagnostics and appState work as they do on MedianSupport. See Context and diagnostics.

Fields

Email and message are always on the form. Add the rest with fields:

<MedianContactForm
  apiKey="median_pk_..."
  fields={[
    { name: "name", label: "Name", required: true },
    { name: "company", label: "Company" },
    {
      name: "topic",
      label: "Topic",
      type: "select",
      options: ["Billing", "A bug", "Something else"],
    },
  ]}
/>;
PropType
namestring

The field's key. Three names are reserved: email, message and name.

Typestring
label?string

Shown above the box, and before the answer in the message. Made from name when left out. "order_number" and "orderNumber" both become "Order number".

Typestring
type?"text" | "textarea" | "select"

One line, a paragraph, or one choice from a list. Ignored on email and message.

Type"text" | "textarea" | "select"
Default"text"
options?string[]

The choices for a select. A select with no options is shown as a text box.

Typestring[]
placeholder?string

Shown in an empty box. On a select it labels the empty choice, "Choose one" by default.

Typestring
required?boolean

Must be filled in. Fields that are not required show "Optional" beside the label. Email and message are always required.

Typeboolean
Defaultfalse
Rule What happens
Order Fields show in the order given. Email goes first, or right after a name field. Message goes last. Name either one in fields to place it yourself
name: "email" or name: "message" Changes that field’s label and placeholder. Nothing else
name: "name" The answer goes on the customer record, not in the message
Any other name The answer goes above the message as its own line, such as Topic: Billing
An empty optional field Left out of the message
Two fields with one name Only the first is shown
Built-in field Label Placeholder
Email Email you@example.com
Message Message How can we help?

Validation

The form checks its fields when Send message is pressed, not while the visitor types. Each problem shows under its field, and the first one takes focus. A message clears as soon as the visitor edits that field.

Field Message
Email, empty Enter your email.
Email, not shaped like name@domain.tld Enter a valid email address.
Message, empty Write a message.
A required field, empty Required.

Enter in a one line field sends the form. Cmd+Enter or Ctrl+Enter sends from a paragraph field.

After sending

  • Send message shows a spinner while the message is on its way.
  • On success the card shows Message sent, a line such as “Replies go to ada@example.com.” with the address given, and a Send another button. The card keeps the form’s height.
  • Send another brings the form back with the email and name filled in and the other fields empty.
  • On failure the reason shows above the button and every answer stays.
  • The email is saved in the browser. MedianSupport on the same site will not ask for it again, and the form fills it in next time.

The form fills the width of its container and has no width of its own.

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

What your team sees

The form’s answers become one message:

Company: Acme
Topic: Billing

We were charged twice for May. Order 4821.
  • If this browser has an open widget conversation, the message joins it. Otherwise it starts a new Live chat conversation in the inbox. A resolved conversation is never reopened.
  • The email and the name go on the customer record. Typed values win over user.
  • With telemetry on, the page URL and browser context come with it. So do diagnostics and data queued with attach(), when you use them.
  • The page URL is kept only when the message starts a new conversation.
  • The agent and your team reply in the thread. The visitor sees the replies when they open MedianSupport in the same browser.

The copy goes out 2 minutes after a reply the visitor has not seen. If they later open the support panel on your site, the panel decides instead, the same as for any widget visitor.

Limits

What Limit
Whole message, field lines included 4,000 characters. Longer fails with “Messages have to be 4000 characters or fewer.”
One line field, including email and name 200 characters
Paragraph field 4,000 characters
Email on the customer record Trimmed and cut at 320 characters
Name on the customer record Trimmed and cut at 80 characters
New conversations and sends Rate limited. Over the limit fails with “Too many requests. Wait a moment and try again.” See Rate limits

Your own form

useMedianContact is the same send without the markup:

import { useMedianContact } from "@mediansh/widget";
import { useState } from "react";

function ContactPage() {
  const contact = useMedianContact({ apiKey: "median_pk_..." });
  const [email, setEmail] = useState("");
  const [message, setMessage] = useState("");

  return (
    <form
      onSubmit={async (event) => {
        event.preventDefault();
        await contact.send({ email, message }).catch(() => {});
      }}
    >
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      <textarea value={message} onChange={(e) => setMessage(e.target.value)} />
      {contact.error && <p>{contact.error}</p>}
      <button disabled={!contact.isReady || contact.isSending}>Send</button>
    </form>
  );
}

It takes apiKey or identity, plus user, telemetry, diagnostics and appState, as the component does. It needs no provider and no other component on the page.

PropType
send?(message: MedianContactMessage) => Promise<void>

Resolves once the message is delivered. Rejects with an Error whose message is fit to show, and puts the same text in error.

Type(message: MedianContactMessage) => Promise<void>
isSending?boolean

A send is in flight.

Typeboolean
error?string | null

Why the last send failed. Cleared when the next send starts.

Typestring | null
isReady?boolean

Whether a send can go. False on the server, and until an identity route has answered with the key.

Typeboolean
user?MedianUser | undefined

What the page knows about the visitor. That is the user you passed or the identity route returned, plus the email this browser already gave. Use it to fill your boxes.

TypeMedianUser | undefined
showBranding?boolean

Whether to show a "Powered by Median" line. False only when your plan allows hiding it and Show Median branding is off.

Typeboolean

send does not check the email or the message. Validate in your form. It fails before sending with these messages:

Message Cause
This form is not connected yet. Try again in a moment. No key yet. The identity route has not answered
Still connecting. Try again in a moment. No browser session yet

The message

send and sendMedianContact take a MedianContactMessage:

PropType
emailstring

Where replies go. Saved on the customer record.

Typestring
messagestring

What the visitor wrote.

Typestring
name?string

Saved on the customer record beside the email.

Typestring
fields?Record<string, string>

Other answers, as label to answer. Each goes above the message as its own line. { Topic: "Billing" } becomes "Topic: Billing". Empty answers are left out.

TypeRecord<string, string>

Without React

sendMedianContact is the same send as a plain function:

import { sendMedianContact } from "@mediansh/widget";

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  try {
    await sendMedianContact(
      { apiKey: "median_pk_..." },
      {
        email: email.value,
        message: message.value,
        fields: { Topic: topic.value },
      },
    );
  } catch (error) {
    notice.textContent = error.message;
  }
});
PropType
apiKeystring

The browser-safe median_pk_ id.

Typestring
user?MedianUser

The signed in user, if any. The email and name in the message win over it.

TypeMedianUser
telemetry?boolean

Send browser context with the message.

Typeboolean
Defaulttrue
  • It takes no identity, diagnostics or appState. Use the hook for those.
  • It does not check the email or the message.
  • Called on the server, it throws “sendMedianContact() was called where there is no browser, so nothing was sent. Call it from the page, after it has loaded.”

Both the hook and the function save the email in the browser after a successful send.

To render the ready-made form on a page without React, use mountMedianContactForm with a target. See Without React.

Troubleshooting

You see Cause
The form stays switched off. Console: Median: <MedianContactForm> was given neither apiKey nor identity, so it cannot send. From the hook, it names useMedianContact() Pass apiKey, or identity with the path of your identity route
The form stays switched off. Console: Median: could not read the identity route. The identity route failed and no apiKey was passed. See Identity
Console: Median: the apiKey passed to <MedianContactForm> is "...", which does not look like the browser-safe id from publicKeyFromMedianKey(MEDIAN_KEY). From the hook, it names useMedianContact() Derive apiKey from MEDIAN_KEY with publicKeyFromMedianKey. It starts with median_pk_
“That API key does not match any organization.” above the button The key was revoked, or is not a Median key. Derive it from your current MEDIAN_KEY
Console: Median: in the fields passed to <MedianContactForm>, ... A field has no name, shares a name with another, or is a select with no options. The message says which
A reason above the button The send failed. The answers stay, so the visitor can press Send message again

Was this page helpful?