> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/medusajs/medusa/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom UI Routes

> Learn how to add custom routes and pages to the Medusa admin dashboard

Custom routes allow you to add entirely new pages to the admin dashboard, complete with sidebar navigation and nested routing.

## Overview

Routes in the Medusa admin dashboard are powered by React Router 6. You can create custom routes to:

* Add new pages for custom functionality
* Create nested routes under existing sections
* Add items to the sidebar navigation
* Build multi-page workflows

## Creating a Custom Route

### Basic Route

Create a new route by defining a React component and exporting a route configuration:

<Steps>
  <Step title="Create Route File">
    Create a new file in your admin extensions directory:

    ```bash theme={null}
    src/admin/routes/analytics/page.tsx
    ```
  </Step>

  <Step title="Define the Component">
    ```tsx theme={null}
    // src/admin/routes/analytics/page.tsx
    import { Container, Heading } from "@medusajs/ui"

    const AnalyticsPage = () => {
      return (
        <Container>
          <Heading>Analytics Dashboard</Heading>
          <div className="mt-4">
            <p>Your custom analytics content here</p>
          </div>
        </Container>
      )
    }

    export default AnalyticsPage
    ```
  </Step>

  <Step title="Export Route Configuration">
    ```tsx theme={null}
    import { defineRouteConfig } from "@medusajs/admin-sdk"
    import { ChartBar } from "@medusajs/icons"

    export const config = defineRouteConfig({
      label: "Analytics",
      icon: ChartBar,
    })

    export default AnalyticsPage
    ```
  </Step>
</Steps>

The route will be automatically registered and accessible at `/analytics`.

### Route Configuration Options

The `defineRouteConfig` function accepts the following options:

```tsx theme={null}
interface RouteConfig {
  /**
   * Label to display in the sidebar
   */
  label?: string
  
  /**
   * Icon component to display next to the label
   */
  icon?: ComponentType
  
  /**
   * Nest this route under an existing route
   */
  nested?: NestedRoutePosition
  
  /**
   * Control the order in the sidebar (lower appears first)
   */
  rank?: number
  
  /**
   * i18n namespace for label translation
   */
  translationNs?: string
}
```

## Route File Structure

The file path determines the route path:

```
src/admin/routes/
├── custom-page/
│   └── page.tsx          → /custom-page
├── settings/
│   └── custom/
│       └── page.tsx      → /settings/custom
└── analytics/
    ├── page.tsx          → /analytics
    └── [id]/
        └── page.tsx      → /analytics/:id
```

### Dynamic Routes

Use `[param]` syntax for dynamic route segments:

```tsx theme={null}
// src/admin/routes/reports/[reportId]/page.tsx
import { useParams } from "react-router-dom"
import { Container, Heading } from "@medusajs/ui"

const ReportDetailPage = () => {
  const { reportId } = useParams()
  
  return (
    <Container>
      <Heading>Report {reportId}</Heading>
      {/* Fetch and display report data */}
    </Container>
  )
}

export default ReportDetailPage
```

## Nested Routes

Nest your custom routes under existing dashboard sections:

```tsx theme={null}
import { defineRouteConfig } from "@medusajs/admin-sdk"
import { Package } from "@medusajs/icons"

const BrandManagementPage = () => {
  return (
    <div>
      <h1>Brand Management</h1>
      {/* Your content */}
    </div>
  )
}

export const config = defineRouteConfig({
  label: "Brands",
  icon: Package,
  nested: "/products", // Nest under Products section
  rank: 1 // Appear first in the nested list
})

export default BrandManagementPage
```

Available nested positions (from `packages/admin/admin-shared/src/extensions/routes/constants.ts:1`):

* `/orders`
* `/products`
* `/inventory`
* `/customers`
* `/promotions`
* `/price-lists`

## Data Loading

Use React Router loaders for data fetching:

```tsx theme={null}
import { LoaderFunctionArgs } from "react-router-dom"
import { useLoaderData } from "react-router-dom"
import { Container, Heading } from "@medusajs/ui"

type Report = {
  id: string
  name: string
  data: any[]
}

// Loader function
export async function loader({ params }: LoaderFunctionArgs) {
  const response = await fetch(`/admin/reports/${params.reportId}`)
  const report: Report = await response.json()
  return { report }
}

// Component
const ReportDetailPage = () => {
  const { report } = useLoaderData() as { report: Report }
  
  return (
    <Container>
      <Heading>{report.name}</Heading>
      {/* Render report data */}
    </Container>
  )
}

export default ReportDetailPage
```

## Using the Admin SDK

Fetch data using the Medusa Admin SDK:

```tsx theme={null}
import { useMedusa } from "@medusajs/dashboard"
import { useQuery } from "@tanstack/react-query"
import { Container, Heading } from "@medusajs/ui"

const CustomProductsPage = () => {
  const { client } = useMedusa()
  
  const { data, isLoading } = useQuery({
    queryKey: ["custom-products"],
    queryFn: async () => {
      const response = await client.products.list()
      return response.products
    }
  })
  
  if (isLoading) {
    return <div>Loading...</div>
  }
  
  return (
    <Container>
      <Heading>Custom Products View</Heading>
      <div className="grid grid-cols-3 gap-4 mt-4">
        {data?.map(product => (
          <div key={product.id} className="border p-4 rounded">
            <h3>{product.title}</h3>
          </div>
        ))}
      </div>
    </Container>
  )
}

export default CustomProductsPage
```

## Navigation

### Programmatic Navigation

Use React Router hooks for navigation:

```tsx theme={null}
import { useNavigate } from "react-router-dom"
import { Button } from "@medusajs/ui"

const CustomPage = () => {
  const navigate = useNavigate()
  
  const handleNavigate = () => {
    navigate("/products")
  }
  
  return (
    <Button onClick={handleNavigate}>
      Go to Products
    </Button>
  )
}
```

### Links

```tsx theme={null}
import { Link } from "react-router-dom"

const CustomPage = () => {
  return (
    <div>
      <Link to="/orders" className="text-ui-fg-interactive hover:underline">
        View Orders
      </Link>
    </div>
  )
}
```

## Layouts

### Custom Layout

Create a layout for multiple related routes:

```tsx theme={null}
// src/admin/routes/analytics/layout.tsx
import { Outlet } from "react-router-dom"
import { Container } from "@medusajs/ui"

const AnalyticsLayout = () => {
  return (
    <Container>
      <nav className="flex gap-4 border-b pb-4 mb-4">
        <Link to="/analytics/sales">Sales</Link>
        <Link to="/analytics/customers">Customers</Link>
        <Link to="/analytics/products">Products</Link>
      </nav>
      <Outlet /> {/* Child routes render here */}
    </Container>
  )
}

export default AnalyticsLayout
```

## Settings Routes

Add custom settings pages:

```tsx theme={null}
// src/admin/routes/settings/custom-settings/page.tsx
import { defineRouteConfig } from "@medusajs/admin-sdk"
import { Cog } from "@medusajs/icons"
import { Container, Heading } from "@medusajs/ui"

const CustomSettingsPage = () => {
  return (
    <Container>
      <Heading>Custom Settings</Heading>
      {/* Settings form */}
    </Container>
  )
}

export const config = defineRouteConfig({
  label: "Custom Settings",
  icon: Cog,
})

export default CustomSettingsPage
```

Settings routes are automatically grouped in the Settings section.

## Route Guards

Protect routes with authentication or permissions:

```tsx theme={null}
import { useAuth } from "@medusajs/dashboard"
import { Navigate } from "react-router-dom"

const ProtectedRoute = () => {
  const { user } = useAuth()
  
  if (!user || user.role !== "admin") {
    return <Navigate to="/" replace />
  }
  
  return (
    <div>
      <h1>Admin Only Content</h1>
    </div>
  )
}

export default ProtectedRoute
```

## Complete Example

Here's a complete example with data fetching, forms, and navigation:

```tsx theme={null}
// src/admin/routes/brands/page.tsx
import { defineRouteConfig } from "@medusajs/admin-sdk"
import { Package } from "@medusajs/icons"
import { Container, Heading, Button, Table } from "@medusajs/ui"
import { useQuery } from "@tanstack/react-query"
import { useNavigate } from "react-router-dom"

type Brand = {
  id: string
  name: string
  description: string
}

const BrandsPage = () => {
  const navigate = useNavigate()
  
  const { data: brands, isLoading } = useQuery({
    queryKey: ["brands"],
    queryFn: async () => {
      const response = await fetch("/admin/brands")
      return response.json() as Promise<Brand[]>
    }
  })
  
  if (isLoading) {
    return <div>Loading...</div>
  }
  
  return (
    <Container>
      <div className="flex items-center justify-between mb-4">
        <Heading>Brand Management</Heading>
        <Button onClick={() => navigate("/brands/create")}>
          Create Brand
        </Button>
      </div>
      
      <Table>
        <Table.Header>
          <Table.Row>
            <Table.HeaderCell>Name</Table.HeaderCell>
            <Table.HeaderCell>Description</Table.HeaderCell>
            <Table.HeaderCell></Table.HeaderCell>
          </Table.Row>
        </Table.Header>
        <Table.Body>
          {brands?.map((brand) => (
            <Table.Row key={brand.id}>
              <Table.Cell>{brand.name}</Table.Cell>
              <Table.Cell>{brand.description}</Table.Cell>
              <Table.Cell>
                <Button 
                  variant="secondary" 
                  onClick={() => navigate(`/brands/${brand.id}`)}
                >
                  Edit
                </Button>
              </Table.Cell>
            </Table.Row>
          ))}
        </Table.Body>
      </Table>
    </Container>
  )
}

export const config = defineRouteConfig({
  label: "Brands",
  icon: Package,
  nested: "/products",
  rank: 10
})

export default BrandsPage
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use Meaningful Route Paths">
    Choose route paths that clearly describe the page content (e.g., `/reports/sales` instead of `/r/s`).
  </Accordion>

  <Accordion title="Implement Loading States">
    Always show loading indicators while fetching data to improve user experience.
  </Accordion>

  <Accordion title="Handle Errors Gracefully">
    Use error boundaries and show helpful error messages when data loading fails.
  </Accordion>

  <Accordion title="Leverage TanStack Query">
    Use TanStack Query for data fetching to get automatic caching, refetching, and optimistic updates.
  </Accordion>
</AccordionGroup>

## Next Steps

* Learn about [custom widgets](/admin/widgets)
* Explore [dashboard customization](/admin/customization)
* Review the [JavaScript SDK](/sdk/overview) for API integration
