Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    medusajs

    building-admin-dashboard-customizations

    medusajs/building-admin-dashboard-customizations
    Design
    74

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Load automatically when planning, researching, or implementing Medusa Admin dashboard UI (widgets, custom pages, forms, tables, data loading, navigation)...

    SKILL.md

    Medusa Admin Dashboard Customizations

    Build custom UI extensions for the Medusa Admin dashboard using the Admin SDK and Medusa UI components.

    Note: "UI Routes" are custom admin pages, different from backend API routes (which use building-with-medusa skill).

    When to Apply

    Load this skill for ANY admin UI development task, including:

    • Creating widgets for product/order/customer pages
    • Building custom admin pages
    • Implementing forms and modals
    • Displaying data with tables or lists
    • Adding navigation between pages

    Also load these skills when:

    • building-with-medusa: Building backend API routes that the admin UI calls
    • building-storefronts: If working on storefront instead of admin dashboard

    CRITICAL: Load Reference Files When Needed

    The quick reference below is NOT sufficient for implementation. You MUST load relevant reference files before writing code for that component.

    Load these references based on what you're implementing:

    • Creating widgets? → MUST load references/data-loading.md first
    • Building forms/modals? → MUST load references/forms.md first
    • Displaying data in tables/lists? → MUST load references/display-patterns.md first
    • Selecting from large datasets? → MUST load references/table-selection.md first
    • Adding navigation? → MUST load references/navigation.md first
    • Styling components? → MUST load references/typography.md first

    Minimum requirement: Load at least 1-2 reference files relevant to your specific task before implementing.

    When to Use This Skill vs MedusaDocs MCP Server

    ⚠️ CRITICAL: This skill should be consulted FIRST for planning and implementation.

    Use this skill for (PRIMARY SOURCE):

    • Planning - Understanding how to structure admin UI features
    • Component patterns - Widgets, pages, forms, tables, modals
    • Design system - Typography, colors, spacing, semantic classes
    • Data loading - Critical separate query pattern, cache invalidation
    • Best practices - Correct vs incorrect patterns (e.g., display queries on mount)
    • Critical rules - What NOT to do (common mistakes like conditional display queries)

    Use MedusaDocs MCP server for (SECONDARY SOURCE):

    • Specific component prop signatures after you know which component to use
    • Available widget zones list
    • JS SDK method details
    • Configuration options reference

    Why skills come first:

    • Skills contain critical patterns like separate display/modal queries that MCP doesn't emphasize
    • Skills show correct vs incorrect patterns; MCP shows what's possible
    • Planning requires understanding patterns, not just API reference

    Critical Setup Rules

    SDK Client Configuration

    CRITICAL: Always use exact configuration - different values cause errors:

    // src/admin/lib/client.ts
    import Medusa from "@medusajs/js-sdk"
    
    export const sdk = new Medusa({
      baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
      debug: import.meta.env.DEV,
      auth: {
        type: "session",
      },
    })
    

    pnpm Users ONLY

    CRITICAL: Install peer dependencies BEFORE writing any code:

    # Find exact version from dashboard
    pnpm list @tanstack/react-query --depth=10 | grep @medusajs/dashboard
    # Install that exact version
    pnpm add @tanstack/react-query@[exact-version]
    
    # If using navigation (Link component)
    pnpm list react-router-dom --depth=10 | grep @medusajs/dashboard
    pnpm add react-router-dom@[exact-version]
    

    npm/yarn users: DO NOT install these packages - already available.

    Rule Categories by Priority

    Priority Category Impact Prefix
    1 Data Loading CRITICAL data-
    2 Design System CRITICAL design-
    3 Data Display HIGH (includes CRITICAL price rule) display-
    4 Typography HIGH typo-
    5 Forms & Modals MEDIUM form-
    6 Selection Patterns MEDIUM select-

    Quick Reference

    1. Data Loading (CRITICAL)

    • data-sdk-always - ALWAYS use Medusa JS SDK for ALL API requests - NEVER use regular fetch() (missing auth headers causes errors)
    • data-sdk-method-choice - Use existing SDK methods for built-in endpoints (sdk.admin.product.list()), use sdk.client.fetch() for custom routes
    • data-display-on-mount - Display queries MUST load on mount (no enabled condition based on UI state)
    • data-separate-queries - Separate display queries from modal/form queries
    • data-invalidate-display - Invalidate display queries after mutations, not just modal queries
    • data-loading-states - Always show loading states (Spinner), not empty states
    • data-pnpm-install-first - pnpm users MUST install @tanstack/react-query BEFORE coding

    2. Design System (CRITICAL)

    • design-semantic-colors - Always use semantic color classes (bg-ui-bg-base, text-ui-fg-subtle), never hardcoded
    • design-spacing - Use px-6 py-4 for section padding, gap-2 for lists, gap-3 for items
    • design-button-size - Always use size="small" for buttons in widgets and tables
    • design-medusa-components - Always use Medusa UI components (Container, Button, Text), not raw HTML

    3. Data Display (HIGH)

    • display-price-format - CRITICAL: Prices from Medusa are stored as-is ($49.99 = 49.99, NOT in cents). Display them directly - NEVER divide by 100

    4. Typography (HIGH)

    • typo-text-component - Always use Text component from @medusajs/ui, never plain span/p tags
    • typo-labels - Use <Text size="small" leading="compact" weight="plus"> for labels/headings
    • typo-descriptions - Use <Text size="small" leading="compact" className="text-ui-fg-subtle"> for descriptions
    • typo-no-heading-widgets - Never use Heading for small sections in widgets (use Text instead)

    5. Forms & Modals (MEDIUM)

    • form-focusmodal-create - Use FocusModal for creating new entities
    • form-drawer-edit - Use Drawer for editing existing entities
    • form-disable-pending - Always disable actions during mutations (disabled={mutation.isPending})
    • form-show-loading - Show loading state on submit button (isLoading={mutation.isPending})

    6. Selection Patterns (MEDIUM)

    • select-small-datasets - Use Select component for 2-10 options (statuses, types, etc.)
    • select-large-datasets - Use DataTable with FocusModal for large datasets (products, categories, etc.)
    • select-search-config - Must pass search configuration to useDataTable to avoid "search not enabled" error

    Critical Data Loading Pattern

    ALWAYS follow this pattern - never load display data conditionally:

    // ✅ CORRECT - Separate queries with proper responsibilities
    const RelatedProductsWidget = ({ data: product }) => {
      const [modalOpen, setModalOpen] = useState(false)
    
      // Display query - loads on mount
      const { data: displayProducts } = useQuery({
        queryFn: () => fetchSelectedProducts(selectedIds),
        queryKey: ["related-products-display", product.id],
        // No 'enabled' condition - loads immediately
      })
    
      // Modal query - loads when needed
      const { data: modalProducts } = useQuery({
        queryFn: () => sdk.admin.product.list({ limit: 10, offset: 0 }),
        queryKey: ["products-selection"],
        enabled: modalOpen, // OK for modal-only data
      })
    
      // Mutation with proper invalidation
      const updateProduct = useMutation({
        mutationFn: updateFunction,
        onSuccess: () => {
          // Invalidate display data query to refresh UI
          queryClient.invalidateQueries({ queryKey: ["related-products-display", product.id] })
          // Also invalidate the entity query
          queryClient.invalidateQueries({ queryKey: ["product", product.id] })
          // Note: No need to invalidate modal selection query
        },
      })
    
      return (
        <Container>
          {/* Display uses displayProducts */}
          {displayProducts?.map(p => <div key={p.id}>{p.title}</div>)}
    
          <FocusModal open={modalOpen} onOpenChange={setModalOpen}>
            {/* Modal uses modalProducts */}
          </FocusModal>
        </Container>
      )
    }
    
    // ❌ WRONG - Single query with conditional loading
    const BrokenWidget = ({ data: product }) => {
      const [modalOpen, setModalOpen] = useState(false)
    
      const { data } = useQuery({
        queryFn: () => sdk.admin.product.list(),
        enabled: modalOpen, // ❌ Display breaks on page refresh!
      })
    
      // Trying to display from modal query
      const displayItems = data?.filter(item => ids.includes(item.id)) // No data until modal opens
    
      return <div>{displayItems?.map(...)}</div> // Empty on mount!
    }
    

    Why this matters:

    • On page refresh, modal is closed, so conditional query doesn't run
    • User sees empty state instead of their data
    • Display depends on modal interaction (broken UX)

    Common Mistakes Checklist

    Before implementing, verify you're NOT doing these:

    Data Loading:

    • Using regular fetch() instead of Medusa JS SDK (causes missing auth header errors)
    • Not using existing SDK methods for built-in endpoints (e.g., using sdk.client.fetch("/admin/products") instead of sdk.admin.product.list())
    • Loading display data conditionally based on modal/UI state
    • Using a single query for both display and modal
    • Forgetting to invalidate display queries after mutations
    • Not handling loading states (showing empty instead of spinner)
    • pnpm users: Not installing @tanstack/react-query before coding

    Design System:

    • Using hardcoded colors instead of semantic classes
    • Forgetting size="small" on buttons in widgets
    • Not using px-6 py-4 for section padding
    • Using raw HTML elements instead of Medusa UI components

    Data Display:

    • CRITICAL: Dividing prices by 100 when displaying (prices are stored as-is: $49.99 = 49.99, NOT in cents)

    Typography:

    • Using plain span/p tags instead of Text component
    • Not using weight="plus" for labels
    • Not using text-ui-fg-subtle for descriptions
    • Using Heading in small widget sections

    Forms:

    • Using Drawer for creating (should use FocusModal)
    • Using FocusModal for editing (should use Drawer)
    • Not disabling buttons during mutations
    • Not showing loading state on submit

    Selection:

    • Using DataTable for <10 items (overkill)
    • Using Select for >10 items (poor UX)
    • Not configuring search in useDataTable (causes error)

    Reference Files Available

    Load these for detailed patterns:

    references/data-loading.md       - useQuery/useMutation patterns, cache invalidation
    references/forms.md              - FocusModal/Drawer patterns, validation
    references/table-selection.md    - Complete DataTable selection pattern
    references/display-patterns.md   - Lists, tables, cards for entities
    references/typography.md         - Text component patterns
    references/navigation.md         - Link, useNavigate, useParams patterns
    

    Each reference contains:

    • Step-by-step implementation guides
    • Correct vs incorrect code examples
    • Common mistakes and solutions
    • Complete working examples

    Integration with Backend

    ⚠️ CRITICAL: ALWAYS use the Medusa JS SDK for ALL API requests - NEVER use regular fetch()

    Admin UI connects to backend API routes using the SDK:

    import { sdk } from "[LOCATE SDK INSTANCE IN PROJECT]"
    
    // ✅ CORRECT - Built-in endpoint: Use existing SDK method
    const { data: product } = useQuery({
      queryKey: ["product", productId],
      queryFn: () => sdk.admin.product.retrieve(productId),
    })
    
    // ✅ CORRECT - Custom endpoint: Use sdk.client.fetch()
    const { data: reviews } = useQuery({
      queryKey: ["reviews", product.id],
      queryFn: () => sdk.client.fetch(`/admin/products/${product.id}/reviews`),
    })
    
    // ❌ WRONG - Using regular fetch
    const { data } = useQuery({
      queryKey: ["reviews", product.id],
      queryFn: () => fetch(`http://localhost:9000/admin/products/${product.id}/reviews`),
      // ❌ Error: Missing Authorization header!
    })
    
    // Mutation to custom backend route
    const createReview = useMutation({
      mutationFn: (data) => sdk.client.fetch("/admin/reviews", {
        method: "POST",
        body: data
      }),
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ["reviews", product.id] })
        toast.success("Review created")
      },
    })
    

    Why the SDK is required:

    • Admin routes need Authorization and session cookie headers
    • Store routes need x-publishable-api-key header
    • SDK handles all required headers automatically
    • Regular fetch() without headers → authentication/authorization errors
    • Using existing SDK methods provides better type safety

    When to use what:

    • Built-in endpoints: Use existing SDK methods (sdk.admin.product.list(), sdk.store.product.list())
    • Custom endpoints: Use sdk.client.fetch() for your custom API routes

    For implementing backend API routes, load the building-with-medusa skill.

    Widget vs UI Route

    Widgets extend existing admin pages:

    // src/admin/widgets/custom-widget.tsx
    import { defineWidgetConfig } from "@medusajs/admin-sdk"
    import { DetailWidgetProps } from "@medusajs/framework/types"
    
    const MyWidget = ({ data }: DetailWidgetProps<HttpTypes.AdminProduct>) => {
      return <Container>Widget content</Container>
    }
    
    export const config = defineWidgetConfig({
      zone: "product.details.after",
    })
    
    export default MyWidget
    

    UI Routes create new admin pages:

    // src/admin/routes/custom-page/page.tsx
    import { defineRouteConfig } from "@medusajs/admin-sdk"
    
    const CustomPage = () => {
      return <div>Page content</div>
    }
    
    export const config = defineRouteConfig({
      label: "Custom Page",
    })
    
    export default CustomPage
    

    Common Issues & Solutions

    "Cannot find module" errors (pnpm users):

    • Install peer dependencies BEFORE coding
    • Use exact versions from dashboard

    "No QueryClient set" error:

    • pnpm: Install @tanstack/react-query
    • npm/yarn: Remove incorrectly installed package

    "DataTable.Search not enabled":

    • Must pass search configuration to useDataTable

    Widget not refreshing:

    • Invalidate display queries, not just modal queries
    • Include all dependencies in query keys

    Display empty on refresh:

    • Display query has conditional enabled based on UI state
    • Remove condition - display data must load on mount

    Next Steps - Testing Your Implementation

    After successfully implementing a feature, always provide these next steps to the user:

    1. Start the Development Server

    If the server isn't already running, start it:

    npm run dev      # or pnpm dev / yarn dev
    

    2. Access the Admin Dashboard

    Open your browser and navigate to:

    • Admin Dashboard: http://localhost:9000/app

    Log in with your admin credentials.

    3. Navigate to Your Custom UI

    For Widgets: Navigate to the page where your widget is displayed. Common widget zones:

    • Product widgets: Go to Products → Select a product → Your widget appears in the zone you configured (e.g., product.details.after)
    • Order widgets: Go to Orders → Select an order → Your widget appears in the configured zone
    • Customer widgets: Go to Customers → Select a customer → Your widget appears in the configured zone

    For UI Routes (Custom Pages):

    • Look for your custom page in the admin sidebar/navigation (based on the label you configured)
    • Or navigate directly to: http://localhost:9000/app/[your-route-path]

    4. Test Functionality

    Depending on what was implemented, test:

    • Forms: Try creating/editing entities, verify validation and error messages
    • Tables: Test pagination, search, sorting, and row selection
    • Data display: Verify data loads correctly and refreshes after mutations
    • Modals: Open FocusModal/Drawer, test form submission, verify data updates
    • Navigation: Click links and verify routing works correctly

    Format for Presenting Next Steps

    Always present next steps in a clear, actionable format after implementation:

    ## Implementation Complete
    
    The [feature name] has been successfully implemented. Here's how to see it:
    
    ### Start the Development Server
    [command based on package manager]
    
    ### Access the Admin Dashboard
    Open http://localhost:9000/app in your browser and log in.
    
    ### View Your Custom UI
    
    **For Widgets:**
    1. Navigate to [specific admin page, e.g., "Products"]
    2. Select [an entity, e.g., "any product"]
    3. Scroll to [zone location, e.g., "the bottom of the page"]
    4. You'll see your "[widget name]" widget
    
    **For UI Routes:**
    1. Look for "[page label]" in the admin navigation
    2. Or navigate directly to http://localhost:9000/app/[route-path]
    
    ### What to Test
    1. [Specific test case 1]
    2. [Specific test case 2]
    3. [Specific test case 3]
    
    Recommended Servers
    Mixpanel
    Mixpanel
    Browser tool
    Browser tool
    Airtable
    Airtable
    Repository
    medusajs/medusa-agent-skills
    Files