Skip to main content
The order detail page is a built-in panel screen you don’t own. To add a small piece of UI to it — a button, a badge, a note — you don’t copy the page. You drop a widget at one of its zones, and the SDK renders your component there while the rest of the page keeps working exactly as shipped. This tutorial adds a “Copy link” button to the order summary section that copies a link to the order.
Additive, not a replacement. A widget layers your component onto a built-in page at a documented zone. Reach for it first whenever you just want to add something to an existing screen.

Add the button

1

Create the widget file

Drop a file under src/widgets/. Export the component as the default and a config built with defineWidgetConfig. Target orders.detail.summary.after — your component renders in the order summary section footer and receives the loaded order as data.
apps/vendor/src/widgets/order-copy-link.tsx
import { defineWidgetConfig } from "@mercurjs/dashboard-sdk"
import type { HttpTypes } from "@medusajs/types"
import { Button, Container, toast } from "@medusajs/ui"

export const config = defineWidgetConfig({
  zone: "orders.detail.summary.after",
})

const OrderCopyLink = ({ data: order }: { data?: HttpTypes.AdminOrder }) => {
  if (!order) return null

  const orderLink = `${window.location.origin}/orders/${order.id}`

  const handleCopy = async () => {
    try {
      await navigator.clipboard.writeText(orderLink)
      toast.success("Link copied")
    } catch {
      toast.error("Couldn't copy the link")
    }
  }

  return (
    <Container className="flex items-center justify-end p-3">
      <Button size="small" variant="secondary" onClick={handleCopy}>
        Copy link
      </Button>
    </Container>
  )
}

export default OrderCopyLink
2

Understand the zone id

A zone id is <domain>.<view>.<slot>.<placement>. The last segment is the placement:
PlacementEffect
beforeRenders before the built-in content of the zone
afterRenders after the built-in content
Multiple before / after widgets on the same zone stack in registration order.
3

Reload the panel

Start the project (bun run dev) and open any order in the vendor portal. Widget files hot-reload — the “Copy link” button appears in the summary section footer, and the rest of the page is untouched.

Order detail zones

Zones mounted on the vendor order detail page:
ZoneWhere it renders
orders.detail.summary.before / .afterInside the order summary section (around its footer)
orders.detail.main.before / .afterAround the main column (summary, payment, fulfillment)
orders.detail.side.before / .afterAround the sidebar (customer, activity)
Each is passed the loaded order as data. The full, valid set is typed as WidgetZoneId and generated from the panel’s own zone hosts — let your editor autocomplete zone: to see every option. A zone no page renders can’t be targeted and won’t type-check.

Verify

  1. Open an order — the “Copy link” button renders in the summary section footer.
  2. Click it — the link is copied and a toast appears.
  3. Change the zone to orders.detail.side.before and reload — the button moves to the top of the sidebar.
  4. Set zone: "not.a.zone"tsc (bun run lint) fails with a “not assignable to WidgetZoneId” error.
  5. Delete the file — the button disappears; nothing else changed.

FAQ

The zone passes the loaded order as data (HttpTypes.AdminOrder) — id, display id, totals, items, payment_collections, customer, and more. Build the link (or any UI) from it.
Yes. Put the same file in a block’s vendor_ui entry under src/widgets/; installing the block adds the button with no wiring.
The zone renders any React component — a badge, an action menu, a whole section. You have the full order in data.

Next steps

Add a widget

The general widget model and the full list of zones.

Extend forms and tables

Add validated fields and columns with defineCustomFieldsConfig.