npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mantajs/dashboard

v0.1.17

Published

B2B Admin Dashboard for Medusa - Fork of @medusajs/dashboard

Readme

@mantajs/dashboard

A customizable fork of @medusajs/dashboard that lets you override components, override routes, and define your own sidebar menu — without modifying the original dashboard source code.

Drop-in replacement for @medusajs/dashboard via package manager resolutions/overrides.

Installation

# Yarn
yarn add @mantajs/dashboard

# npm
npm install @mantajs/dashboard

# pnpm
pnpm add @mantajs/dashboard

Using as a dashboard replacement

In your Medusa backend's package.json, add a resolution/override to swap @medusajs/dashboard with @mantajs/dashboard. The syntax depends on your package manager:

Yarn (v1 & v4+)

{
  "resolutions": {
    "@medusajs/dashboard": "npm:@mantajs/dashboard@^0.1.13"
  }
}

npm (v8.3+)

{
  "overrides": {
    "@medusajs/dashboard": "npm:@mantajs/dashboard@^0.1.13"
  }
}

pnpm

{
  "pnpm": {
    "overrides": {
      "@medusajs/dashboard": "npm:@mantajs/dashboard@^0.1.13"
    }
  }
}

Then register the Vite plugin in your medusa-config.ts:

import { defineConfig } from "@medusajs/framework/config"
import { customDashboardPlugin } from "@mantajs/dashboard/vite-plugin"

export default defineConfig({
  // ...
  admin: {
    vite: () => ({
      plugins: [customDashboardPlugin()],
    }),
  },
})

Run medusa build (or medusa develop) and the custom dashboard will be compiled in place of the stock one.

Features

1. Component Overrides

Replace any dashboard component by placing a file with the same name in your project's src/admin/components/ directory. The plugin recursively scans the entire components/ tree, so you can organize overrides in subdirectories.

your-project/
└── src/
    └── admin/
        └── components/
            ├── product-general-section.tsx          ← flat override (works)
            ├── orders/
            │   └── order-activity-section.tsx        ← nested override (works too)
            └── forms/
                └── shipping-address-form.tsx         ← deeply nested (works too)

How it works:

During Vite's pre-bundling phase, the plugin redirects the dashboard's dist/app.mjs entry to source files. It then intercepts individual source file loads and swaps any component whose filename matches one of your overrides.

Matching is done by file name (without extension), regardless of subdirectory depth. For example:

| Your file | Overrides | |-----------|-----------| | product-general-section.tsx | src/routes/products/.../product-general-section.tsx | | orders/order-list.tsx | src/routes/orders/order-list/order-list.tsx | | layout/main-layout.tsx | src/components/layout/main-layout/main-layout.tsx |

Your override component must export a default export:

// src/admin/components/orders/order-activity-section.tsx
const OrderActivitySection = () => {
  return <div>My custom order activity section</div>
}

export default OrderActivitySection

Developer experience (HMR):

| Action | Behavior | Details | |--------|----------|---------| | Modify an override | HMR (Hot Module Replacement) | The component is swapped in-place — no page reload, no React state loss. Instant feedback. | | Create a new override | Automatic full reload (~2-3s) | The Vite server restarts to rebuild the pre-bundle, then the browser reloads automatically. No manual refresh needed. | | Delete an override | Automatic full reload (~2-3s) | Same as creation — the original dashboard component is restored automatically. |

Under the hood, override files are kept as separate Vite modules (not inlined into the pre-bundled chunk). This allows React Fast Refresh to handle modifications via standard HMR. Creation and deletion require a server restart because the esbuild chunk structure changes.

Important notes:

  • Override files are discovered recursively in src/admin/components/ and all its subdirectories.
  • Matching is based on file name only — the subdirectory structure is for your own organization and does not affect matching.
  • If two files in different subdirectories share the same name, the plugin logs a warning in development and uses the one that comes last alphabetically by full path.
  • Index/barrel files (index.ts) are never overridden to preserve re-exports.
  • The plugin forces Vite to re-optimize dependencies when overrides are present, so changes are always picked up.

2. Route Overrides

Add new pages or replace existing ones using Medusa's standard admin extension system.

your-project/
└── src/
    └── admin/
        └── routes/
            └── custom-page/
                └── page.tsx          ← adds /custom-page to the dashboard
            └── orders/
                └── page.tsx          ← overrides the /orders page

How merging works:

  • If your extension route has a path that doesn't exist in the dashboard, it's added as a new route.
  • If your extension route has the same path as a built-in route, your component replaces the original.
  • Children of the original route that you don't redefine are preserved. For example, overriding /orders keeps /orders/:id intact.

This uses Medusa's @medusajs/admin-vite-plugin to discover routes in src/admin/routes/, combined with a custom merge function that ensures backward compatibility.

Route page example:

// src/admin/routes/orders/page.tsx
import { defineRouteConfig } from "@medusajs/admin-sdk"
import { ShoppingCart } from "@medusajs/icons"
import { Container, Heading } from "@medusajs/ui"

const OrdersPage = () => {
  return (
    <Container>
      <Heading level="h1">My Custom Orders Page</Heading>
      {/* Your custom orders list */}
    </Container>
  )
}

export const config = defineRouteConfig({
  label: "Orders",
  icon: ShoppingCart,
})

export default OrdersPage

Developer experience:

Route overrides use Medusa's standard admin extension system (@medusajs/admin-vite-plugin), which provides its own HMR. Modifying a route page triggers a standard Vite HMR update — the page refreshes instantly without a full reload.

Component overrides vs Route overrides — when to use which:

| Use case | Approach | |----------|----------| | Replace a full page (e.g., the orders list) | Route override (src/admin/routes/orders/page.tsx) | | Replace a section inside a page (e.g., the customer info block in order detail) | Component override (src/admin/components/order-customer-section.tsx) | | Add a new page that doesn't exist in the dashboard | Route override (src/admin/routes/my-page/page.tsx) | | Tweak a reusable UI element used across multiple pages | Component override |

3. Custom Menu Configuration

Define your own sidebar menu by creating a src/admin/menu.config.tsx file:

your-project/
└── src/
    └── admin/
        └── menu.config.tsx
// src/admin/menu.config.tsx
import { ShoppingCart, Users, BuildingStorefront } from "@medusajs/icons"
import type { MenuConfig } from "@mantajs/dashboard/vite-plugin"

const config: MenuConfig = {
  items: [
    {
      icon: <ShoppingCart />,
      label: "orders.domain",
      to: "/orders",
      useTranslation: true,
    },
    {
      icon: <Users />,
      label: "customers.domain",
      to: "/customers",
      useTranslation: true,
    },
    {
      icon: <BuildingStorefront />,
      label: "My Custom Page",
      to: "/custom-page",
      items: [
        { label: "Sub Page", to: "/custom-page/sub" },
      ],
    },
  ],
}

export default config

Menu item properties:

| Property | Type | Description | |----------|------|-------------| | icon | ReactNode | Icon component (use @medusajs/icons) | | label | string | Display label or i18n translation key | | to | string | Route path | | useTranslation | boolean | If true, label is treated as an i18n key | | items | MenuNestedItem[] | Optional nested items |

Types:

import type { MenuConfig, MenuItem, MenuNestedItem } from "@mantajs/dashboard/vite-plugin"

When no menu.config.ts is found, the dashboard falls back to its built-in sidebar menu.

Menu, Nested Routes, and Modules: How They Interact

Understanding how the sidebar menu is built is critical to avoid duplicate or missing entries. There are three sources that can add items to the sidebar:

  1. Your menu.config.tsx — the custom menu you define
  2. Route configs with nested — pages that declare nested: "/parent" in defineRouteConfig()
  3. Plugin modules — modules like @medusajs/draft-order that register their own routes and menu entries

How nested works

When a route page exports a config with nested, Medusa automatically injects it as a sub-item under the specified parent in the sidebar:

// src/admin/routes/draft-orders/page.tsx
export const config = defineRouteConfig({
  label: "Drafts",
  nested: "/orders",   // ← auto-injected under Orders in the sidebar
})

This happens regardless of your menu.config.tsx. Even if you define a custom menu, any route with nested will still be injected as a child of its parent entry.

To prevent a route from appearing in the sidebar, remove the nested property:

export const config = defineRouteConfig({
  label: "Drafts Test",
  // no `nested` → not auto-injected in the menu
})

The page remains accessible via its URL (/app/draft-orders) but won't appear in the sidebar unless you explicitly add it to your menu config.

Controlling sub-items via menu.config.tsx

If you want full control over which sub-items appear under a menu entry, define them explicitly in items:

{
  icon: <ShoppingCart />,
  label: "orders.domain",
  useTranslation: true,
  to: "/orders",
  items: [
    { label: "Draft Orders", to: "/draft-orders" },
  ],
}

Important: Nested routes (nested: "/orders") are still injected even if you define items manually. To avoid duplicates, either:

  • Remove nested from the route config, or
  • Don't list the route in items (let nested handle it)

Never do both — you'll get a duplicate entry.

Plugin modules and the Extensions section

Medusa plugin modules (e.g., @medusajs/draft-order) register their own sidebar entries. By default, these appear in the Extensions section at the bottom of the sidebar.

When you include a module's route in your menu.config.tsx, the module's entry is absorbed into your custom menu and no longer appears separately in Extensions:

// Including /draft-orders in the custom menu prevents it from
// appearing again under Extensions
{
  icon: <ShoppingCart />,
  label: "Orders",
  to: "/orders",
  items: [
    { label: "Draft Orders", to: "/draft-orders" },  // ← module route
  ],
}

If you don't include a module's route in your menu config, it will appear in the Extensions section as usual.

Summary

| Scenario | Result | |----------|--------| | Route has nested: "/orders" | Auto-injected under Orders in sidebar | | Route has no nested | Not in sidebar (unless in menu.config.tsx) | | Module route listed in menu.config.tsx | Appears in your menu, not in Extensions | | Module route not in menu.config.tsx | Appears in Extensions section | | Route has nested and listed in items | Duplicate entry (avoid this!) |

Exports

| Import | Description | |--------|-------------| | @mantajs/dashboard | Main dashboard app (DashboardPlugin type, render function) | | @mantajs/dashboard/css | Dashboard stylesheet | | @mantajs/dashboard/vite-plugin | Vite plugin + menu types |

Development

# Install dependencies
yarn install

# Start dev server (standalone dashboard)
yarn dev

# Build for distribution
yarn build

# Run tests
yarn test

# Validate i18n translations
yarn i18n:validate

Environment Variables

| Variable | Default | Description | |----------|---------|-------------| | VITE_MEDUSA_BACKEND_URL | http://localhost:9000 | Medusa backend URL | | VITE_MEDUSA_STOREFRONT_URL | http://localhost:8000 | Storefront URL | | VITE_MEDUSA_BASE | / | Base path for the dashboard | | VITE_MEDUSA_PROJECT | — | Path to a Medusa project to load admin extensions from |

Compatibility

  • Medusa v2 (2.13.x)
  • React 18
  • Vite 5
  • TypeScript 5.6+

License

MIT