JSON View - Admin Components

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?
Edit this page
Ask Anything
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