JSON View - Admin Components

In this guide, you'll learn how to create a JSON view section that matches the Medusa Admin's design conventions.

Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format.

Example of a JSON section in the admin

To create a component that shows a JSON section in your customizations, create the file src/admin/components/json-view-section.tsx with the following content:

src/admin/components/json-view-section.tsx
1import {2  ArrowUpRightOnBox,3  Check,4  SquareTwoStack,5  TriangleDownMini,6  XMarkMini,7} from "@medusajs/icons"8import {9  Badge,10  Container,11  Drawer,12  Heading,13  IconButton,14  Kbd,15} from "@medusajs/ui"16import Primitive from "@uiw/react-json-view"17import { CSSProperties, MouseEvent, Suspense, useState } from "react"18
19type JsonViewSectionProps = {20  data: object21  title?: string22}23
24export const JsonViewSection = ({ data }: JsonViewSectionProps) => {25  const numberOfKeys = Object.keys(data).length26
27  return (28    <Container className="flex items-center justify-between px-6 py-4">29      <div className="flex items-center gap-x-4">30        <Heading level="h2">JSON</Heading>31        <Badge size="2xsmall" rounded="full">32          {numberOfKeys} keys33        </Badge>34      </div>35      <Drawer>36        <Drawer.Trigger asChild>37          <IconButton38            size="small"39            variant="transparent"40            className="text-ui-fg-muted hover:text-ui-fg-subtle"41          >42            <ArrowUpRightOnBox />43          </IconButton>44        </Drawer.Trigger>45        <Drawer.Content className="bg-ui-contrast-bg-base text-ui-code-fg-subtle !shadow-elevation-commandbar overflow-hidden border border-none max-md:inset-x-2 max-md:max-w-[calc(100%-16px)]">46          <div className="bg-ui-code-bg-base flex items-center justify-between px-6 py-4">47            <div className="flex items-center gap-x-4">48              <Drawer.Title asChild>49                <Heading className="text-ui-contrast-fg-primary">50                <span className="text-ui-fg-subtle">51                  {numberOfKeys}52                </span>53                </Heading>54              </Drawer.Title>55            </div>56            <div className="flex items-center gap-x-2">57              <Kbd className="bg-ui-contrast-bg-subtle border-ui-contrast-border-base text-ui-contrast-fg-secondary">58                esc59              </Kbd>60              <Drawer.Close asChild>61                <IconButton62                  size="small"63                  variant="transparent"64                  className="text-ui-contrast-fg-secondary hover:text-ui-contrast-fg-primary hover:bg-ui-contrast-bg-base-hover active:bg-ui-contrast-bg-base-pressed focus-visible:bg-ui-contrast-bg-base-hover focus-visible:shadow-borders-interactive-with-active"65                >66                  <XMarkMini />67                </IconButton>68              </Drawer.Close>69            </div>70          </div>71          <Drawer.Body className="flex flex-1 flex-col overflow-hidden px-[5px] py-0 pb-[5px]">72            <div className="bg-ui-contrast-bg-subtle flex-1 overflow-auto rounded-b-[4px] rounded-t-lg p-3">73              <Suspense74                fallback={<div className="flex size-full flex-col"></div>}75              >76                <Primitive77                  value={data}78                  displayDataTypes={false}79                  style={80                    {81                      "--w-rjv-font-family": "Roboto Mono, monospace",82                      "--w-rjv-line-color": "var(--contrast-border-base)",83                      "--w-rjv-curlybraces-color":84                        "var(--contrast-fg-secondary)",85                      "--w-rjv-brackets-color": "var(--contrast-fg-secondary)",86                      "--w-rjv-key-string": "var(--contrast-fg-primary)",87                      "--w-rjv-info-color": "var(--contrast-fg-secondary)",88                      "--w-rjv-type-string-color": "var(--tag-green-icon)",89                      "--w-rjv-quotes-string-color": "var(--tag-green-icon)",90                      "--w-rjv-type-boolean-color": "var(--tag-orange-icon)",91                      "--w-rjv-type-int-color": "var(--tag-orange-icon)",92                      "--w-rjv-type-float-color": "var(--tag-orange-icon)",93                      "--w-rjv-type-bigint-color": "var(--tag-orange-icon)",94                      "--w-rjv-key-number": "var(--contrast-fg-secondary)",95                      "--w-rjv-arrow-color": "var(--contrast-fg-secondary)",96                      "--w-rjv-copied-color": "var(--contrast-fg-secondary)",97                      "--w-rjv-copied-success-color":98                        "var(--contrast-fg-primary)",99                      "--w-rjv-colon-color": "var(--contrast-fg-primary)",100                      "--w-rjv-ellipsis-color": "var(--contrast-fg-secondary)",101                    } as CSSProperties102                  }103                  collapsed={1}104                >105                  <Primitive.Quote render={() => <span />} />106                  <Primitive.Null107                    render={() => (108                      <span className="text-ui-tag-red-icon">null</span>109                    )}110                  />111                  <Primitive.Undefined112                    render={() => (113                      <span className="text-ui-tag-blue-icon">undefined</span>114                    )}115                  />116                  <Primitive.CountInfo117                    render={(_props, { value }) => {118                      return (119                        <span className="text-ui-contrast-fg-secondary ml-2">120                          {Object.keys(value as object).length} items121                        </span>122                      )123                    }}124                  />125                  <Primitive.Arrow>126                    <TriangleDownMini className="text-ui-contrast-fg-secondary -ml-[0.5px]" />127                  </Primitive.Arrow>128                  <Primitive.Colon>129                    <span className="mr-1">:</span>130                  </Primitive.Colon>131                  <Primitive.Copied132                    render={({ style }, { value }) => {133                      return <Copied style={style} value={value} />134                    }}135                  />136                </Primitive>137              </Suspense>138            </div>139          </Drawer.Body>140        </Drawer.Content>141      </Drawer>142    </Container>143  )144}145
146type CopiedProps = {147  style?: CSSProperties148  value: object | undefined149}150
151const Copied = ({ style, value }: CopiedProps) => {152  const [copied, setCopied] = useState(false)153
154  const handler = (e: MouseEvent<HTMLSpanElement>) => {155    e.stopPropagation()156    setCopied(true)157
158    if (typeof value === "string") {159      navigator.clipboard.writeText(value)160    } else {161      const json = JSON.stringify(value, null, 2)162      navigator.clipboard.writeText(json)163    }164
165    setTimeout(() => {166      setCopied(false)167    }, 2000)168  }169
170  const styl = { whiteSpace: "nowrap", width: "20px" }171
172  if (copied) {173    return (174      <span style={{ ...style, ...styl }}>175        <Check className="text-ui-contrast-fg-primary" />176      </span>177    )178  }179
180  return (181    <span style={{ ...style, ...styl }} onClick={handler}>182      <SquareTwoStack className="text-ui-contrast-fg-secondary" />183    </span>184  )185}

The JsonViewSection component shows a section with the "JSON" title and a button to show the data as JSON in a drawer or side window.

The JsonViewSection accepts a data prop, which is the data to show as a JSON object in the drawer.


Example#

Use the JsonViewSection component in any widget or UI route.

For example, create the widget src/admin/widgets/product-widget.tsx with the following content:

src/admin/widgets/product-widget.tsx
1import { defineWidgetConfig } from "@medusajs/admin-sdk"2import { JsonViewSection } from "../components/json-view-section"3
4const ProductWidget = () => {5  return <JsonViewSection data={{6    name: "John",7  }} />8}9
10export const config = defineWidgetConfig({11  zone: "product.details.before",12})13
14export default ProductWidget

This shows the JSON section at the top of the product page, passing it the object { name: "John" }.

Was this page helpful?
Ask Anything
Ask any questions about Medusa. Get help with your development.
You can also use the Medusa MCP server in Cursor, VSCode, etc...
FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break