mcpField
B
building-admin-dashboard-customizations

building-admin-dashboard-customizations

github.com/medusajs開発2344.4k 回インストール1 週間前に更新
リポジトリ

building-admin-dashboard-customizations について

Medusa AdminダッシュボードのカスタムUI拡張を開発するためのスキルです。ウィジェットや管理ページに加え、Medusa SDKによるデータ取得、React Queryのパターン、実装リファレンスを扱います。

主な機能

  1. 01ダッシュボードのウィジェットとページ作成
  2. 02フォームとモーダルの作成
  3. 03テーブルやリストでのデータ表示
  4. 04Medusa SDKによるAPIアクセスパターン
  5. 05デザインシステムとコンポーネントの指針

活用シーン

  1. 01商品または注文ページにウィジェットを追加
  2. 02カスタム管理ページを作成
  3. 03バックエンドのデータを読み書きするフォームを構築

MCP 対応

このスキルは実装パターンと計画を扱い、MedusaDocs MCP Serverはコンポーネントのプロパティ、ウィジェットゾーン、SDKメソッドの詳細確認に使うと説明されています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/medusajs/medusa-agent-skills --skill building-admin-dashboard-customizations

インストール後、Claude Code で/building-admin-dashboard-customizationsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
data-loading.md
display-patterns.md
forms.md
navigation.md
table-selection.md
typography.md

詳細

Medusa 管理ダッシュボードのカスタマイズ

Admin SDK と Medusa UI コンポーネントを使用して、Medusa Admin ダッシュボードのカスタム UI 拡張機能を作成します。

注:「UI ルート」はカスタムの管理ページであり、バックエンド API ルートとは異なります(バックエンド API ルートには building-with-medusa スキルを使用します)。

使用するタイミング

次のような管理 UI の開発タスクでは、必ずこのスキルを読み込んでください。

  • 商品/注文/顧客ページのウィジェットを作成する
  • カスタム管理ページを構築する
  • フォームやモーダルを実装する
  • テーブルやリストでデータを表示する
  • ページ間のナビゲーションを追加する

次の場合は、次のスキルも読み込んでください。

  • building-with-medusa: 管理 UI が呼び出すバックエンド API ルートを構築する場合
  • building-storefronts: 管理ダッシュボードではなく、ストアフロントを扱う場合

重要:必要に応じてリファレンスファイルを読み込む

以下のクイックリファレンスだけでは実装に不十分です。 該当するコンポーネントのコードを書く前に、関連するリファレンスファイルを必ず読み込んでください。

実装内容に応じて、次のリファレンスを読み込んでください。

  • ウィジェットを作成する場合は? → まず references/data-loading.md を必ず読み込む
  • フォーム/モーダルを構築する場合は? → まず references/forms.md を必ず読み込む
  • テーブル/リストでデータを表示する場合は? → まず references/display-patterns.md を必ず読み込む
  • 大規模なデータセットから選択する場合は? → まず references/table-selection.md を必ず読み込む
  • ナビゲーションを追加する場合は? → まず references/navigation.md を必ず読み込む
  • コンポーネントにスタイルを適用する場合は? → まず references/typography.md を必ず読み込む

最低要件: 実装前に、具体的なタスクに関連するリファレンスファイルを少なくとも 1~2 個読み込んでください。

このスキルと MedusaDocs MCP サーバーの使い分け

⚠️ 重要:計画と実装では、まずこのスキルを参照してください。

次の用途には、このスキルを使用します(主な情報源):

  • 計画 - 管理 UI 機能の構成方法を理解する
  • コンポーネントのパターン - ウィジェット、ページ、フォーム、テーブル、モーダル
  • デザインシステム - タイポグラフィ、色、間隔、セマンティッククラス
  • データ読み込み - 重要な個別クエリのパターン、キャッシュの無効化
  • ベストプラクティス - 正しいパターンと誤ったパターン(例:マウント時の表示クエリ)
  • 重要なルール - 避けるべきこと(条件付き表示クエリなど、よくある間違い)

次の用途には、MedusaDocs MCP サーバーを使用します(補助的な情報源):

  • 使用するコンポーネントが分かった後の、個別コンポーネントのプロパティのシグネチャ
  • 利用可能なウィジェットゾーンの一覧
  • JS SDK メソッドの詳細
  • 設定オプションのリファレンス

スキルを先に使う理由:

  • スキルには、MCP ではあまり強調されない、表示クエリとモーダルクエリを分けるなどの重要なパターンが含まれています
  • スキルには正しいパターンと誤ったパターンが示されていますが、MCP では可能なことが示されています
  • 計画には API リファレンスだけでなく、パターンの理解が必要です

重要なセットアップルール

SDK クライアントの設定

重要: 設定は必ず正確に使用してください。値が異なるとエラーが発生します。

// 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 ユーザーのみ

重要: コードを書く前に、ピア依存関係をインストールしてください。

# 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 ユーザー: これらのパッケージはすでに利用可能なため、インストールしないでください。

優先度別のルールカテゴリ

優先度カテゴリ影響プレフィックス
1データ読み込み重大data-
2デザインシステム重大design-
3データ表示高(重大な価格ルールを含む)display-
4タイポグラフィ高typo-
5フォームとモーダル中form-
6選択パターン中select-

クイックリファレンス

1. データの読み込み(重要)
  • data-sdk-always - すべての API リクエストで、必ず Medusa JS SDK を使用します - 通常の fetch() は絶対に使用しないでください(認証ヘッダーがないためエラーになります)
  • data-sdk-method-choice - 組み込みエンドポイントには既存の SDK メソッド(sdk.admin.product.list())を使用し、カスタムルートには sdk.client.fetch() を使用します
  • data-display-on-mount - 表示用クエリはマウント時に必ず読み込みます(UI の状態に基づく enabled 条件は使用しません)
  • data-separate-queries - 表示用クエリとモーダル/フォーム用クエリは分けます
  • data-invalidate-display - ミューテーション後は、モーダル用クエリだけでなく表示用クエリも無効化します
  • data-loading-states - 空の状態ではなく、必ず読み込み状態(Spinner)を表示します
  • data-pnpm-install-first - pnpm ユーザーはコーディングを始める前に、必ず @tanstack/react-query をインストールします
2. デザインシステム(重要)
  • design-semantic-colors - 必ずセマンティックカラークラス(bg-ui-bg-base、text-ui-fg-subtle)を使用し、ハードコードした色は使用しません
  • design-spacing - セクションのパディングには px-6 py-4、リストには gap-2、項目には gap-3 を使用します
  • design-button-size - ウィジェットやテーブルのボタンには、必ず size="small" を使用します
  • design-medusa-components - 生の HTML ではなく、必ず Medusa UI コンポーネント(Container、Button、Text)を使用します
3. データ表示(高)
  • display-price-format - 重要:Medusa の価格はそのまま保存されます($49.99 = 49.99 であり、セント単位ではありません)。そのまま表示してください。100 で割るのは絶対にやめてください
4. タイポグラフィ(高)
  • typo-text-component - プレーンな span/p タグではなく、必ず @medusajs/ui の Text コンポーネントを使用します
  • typo-labels - ラベル/見出しには <Text size="small" leading="compact" weight="plus"> を使用します
  • typo-descriptions - 説明には <Text size="small" leading="compact" className="text-ui-fg-subtle"> を使用します
  • typo-no-heading-widgets - ウィジェットの小さなセクションに Heading は使用しません(代わりに Text を使用します)
5. フォームとモーダル(中)
  • form-focusmodal-create - 新しいエンティティの作成には FocusModal を使用します
  • form-drawer-edit - 既存のエンティティの編集には Drawer を使用します
  • form-disable-pending - ミューテーション中は必ずアクションを無効にします(disabled={mutation.isPending})
  • form-show-loading - 送信ボタンに読み込み状態を表示します(isLoading={mutation.isPending})
6. 選択パターン(中)
  • select-small-datasets - 選択肢が 2~10 個(ステータス、タイプなど)の場合は Select コンポーネントを使用します
  • select-large-datasets - 選択肢が多い場合(商品、カテゴリなど)は FocusModal と DataTable を使用します
  • select-search-config - 「search not enabled」エラーを避けるため、useDataTable に検索設定を必ず渡します

重要なデータ読み込みパターン

必ずこのパターンに従い、表示データを条件付きで読み込まないでください。

// ✅ 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!
}

重要な理由:

  • ページを更新するとモーダルが閉じているため、条件付きクエリは実行されません
  • ユーザーにはデータではなく空の状態が表示されます
  • 表示がモーダルの操作に依存してしまいます(UX が損なわれます)

よくある間違いのチェックリスト

実装する前に、以下のことをしていないか確認してください。

データの読み込み:

  • Medusa JS SDK ではなく通常の fetch() を使用している(認証ヘッダーがないというエラーの原因になります)
  • 組み込みエンドポイントに既存の SDK メソッドを使用していない(例:sdk.admin.product.list() ではなく sdk.client.fetch("/admin/products") を使用している)
  • モーダルや UI の状態に応じて表示データを条件付きで読み込んでいる
  • 表示用とモーダル用に単一のクエリを使用している
  • ミューテーション後に表示用クエリを無効化し忘れている
  • 読み込み状態を処理していない(スピナーではなく空の表示にしている)
  • pnpm を使用している場合:コーディング前に @tanstack/react-query をインストールしていない

デザインシステム:

  • セマンティッククラスではなく、色をハードコードしている
  • ウィジェット内のボタンに size="small" を指定し忘れている
  • セクションのパディングに px-6 py-4 を使用していない
  • Medusa UI コンポーネントではなく、生の HTML 要素を使用している

データ表示:

  • 重大:表示時に価格を 100 で割っている(価格はそのまま保存されています:$49.99 = 49.99 であり、セント単位ではありません)

タイポグラフィ:

  • Text コンポーネントではなく、通常の span/p タグを使用している
  • ラベルに weight="plus" を使用していない
  • 説明に text-ui-fg-subtle を使用していない
  • 小さなウィジェットセクションで Heading を使用している

フォーム:

  • 作成に Drawer を使用している(FocusModal を使用してください)
  • 編集に FocusModal を使用している(Drawer を使用してください)
  • ミューテーション中にボタンを無効化していない
  • 送信時に読み込み状態を表示していない

選択:

  • 10 件未満の項目に DataTable を使用している(過剰です)
  • 10 件を超える項目に Select を使用している(UX がよくありません)
  • useDataTable で検索を設定していない(エラーの原因になります)

利用可能なリファレンスファイル

詳しい実装パターンについては、以下を読み込んでください。

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

各リファレンスには以下が含まれます。

  • 実装手順のガイド
  • 正しいコード例と誤ったコード例
  • よくある間違いと解決策
  • 完全に動作するサンプル

バックエンドとの連携

⚠️ 重大:すべての API リクエストには必ず Medusa JS SDK を使用してください。通常の fetch() は絶対に使用しないでください。

管理 UI は SDK を使用してバックエンドの API ルートに接続します。

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")
  },
})

SDK が必要な理由:

  • 管理ルートでは Authorization ヘッダーとセッション Cookie ヘッダーが必要です
  • ストアルートでは x-publishable-api-key ヘッダーが必要です
  • SDK は必要なすべてのヘッダーを自動的に処理します
  • ヘッダーなしの通常の fetch() → 認証/認可エラー
  • 既存の SDK メソッドを使用すると、型安全性が向上します

用途に応じた使い分け:

  • 組み込みエンドポイント:既存の SDK メソッド(sdk.admin.product.list()、sdk.store.product.list())を使用します
  • カスタムエンドポイント:カスタム API ルートには sdk.client.fetch() を使用します

バックエンド API ルートを実装する場合は、building-with-medusa スキルを読み込んでください。

ウィジェットと UI ルート

ウィジェットは既存の管理ページを拡張します:

// 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",
})

export default MyWidget

⚠️ .before / .after は配置を制御しなくなりました(v2.17.2+):

Layout Composer の導入以降、管理者はダッシュボードのエディタービューでウィジェットなどのコンポーネントを配置し、その配置はデータベースに保存されます。.before と .after のゾーンサフィックスは非推奨です。product.details.before のウィジェットと product.details.after のウィジェットは同じ挿入ゾーンに配置され、最終的な順序はユーザーの設定によって決まります。

  • サフィックスを根拠に、特定の位置に表示されるとユーザーに約束しないでください。「ウィジェットは商品詳細ページに表示され、エディタービューで位置を変更できます」と伝えてください。
  • .side は引き続き意味があり、2 列のページレイアウトのサイド列を対象とします。
  • 新しいウィジェットでは、プロジェクトですでにサフィックスを標準化している場合を除き、サフィックスなしのゾーン(例:product.details)を優先してください。

v2.16.0 で追加された新しいゾーンは、下書き注文、ギフトカード、ストアクレジットアカウント(draft_order.*、gift_card.*、store_credit_account.* の details/list/side バリアント)を対象とします。ゾーン名を推測せず、正確なゾーン一覧については MedusaDocs MCP サーバーに問い合わせてください。

UI ルートは新しい管理ページを作成します:

// 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

ブラウザータブのタイトル(v2.17.2+): デフォルトでは、UI ルートのタブタイトルに label が使われます。seo リゾルバーを持つ handle をエクスポートすると、ルートの loader データを使って動的に上書きすることもできます:

// src/admin/routes/brands/[id]/page.tsx
import { UIMatch } from "react-router-dom"

export const handle = {
  seo: (match: UIMatch<BrandResponse>) => ({
    title: match.loaderData?.brand.name || "Brand",
  }),
}

seo がタイトルを返さない場合、ダッシュボードはサイドバーのラベル、パンくずリスト、Medusa の順にフォールバックします。

カスタム挿入ゾーン(v2.16.0+): カスタムページ(特にプラグインで便利です)では、ページレイアウトに @medusajs/dashboard/components の LayoutComposer を使用することで、独自のウィジェット挿入ゾーンを公開できます:

import { LayoutComposer } from "@medusajs/dashboard/components"

const BrandDetailsPage = () => (
  <LayoutComposer
    widgetsZonePrefix="brand.details"   // exposes "brand.details" and "brand.details.side"
    preferredLayoutId="core:two-column"
    data={brand}                        // passed to widgets as their `data` prop
    sections={{ main: <GeneralSection brand={brand} />, side: <MediaSection brand={brand} /> }}
  />
)
  • ゾーン名は {resource}.{page-context}(例:brand.list、brand.details)とし、サイドセクションには .side を付けます。.before/.after は追加しないでください。
  • 型チェックと defineWidgetConfig での自動補完のために、ゾーンを InjectionZoneRegistry インターフェースに登録し、src/admin/tsconfig.json の include 配列に "../../.medusa/types/augmentation-refs.d.ts" を含めてください。
  • カスタムゾーンでは、不明なゾーンに関するビルド警告が出るのは想定どおりです。むやみに無視せず、ゾーン名のつづりが正しいことを確認してください。

よくある問題と解決策

「Cannot find module」エラー(pnpm ユーザー):

  • コーディングを始める前にピア依存関係をインストールする
  • ダッシュボードで使用されている正確なバージョンを使う

「No QueryClient set」エラー:

  • pnpm:@tanstack/react-query をインストールする
  • npm/yarn:誤ってインストールされたパッケージを削除する

「DataTable.Search not enabled」:

  • useDataTable に検索設定を渡す必要があります

ウィジェットが更新されない:

  • モーダルのクエリだけでなく、表示用クエリを無効化する
  • クエリキーにすべての依存関係を含める

更新後に表示が空になる:

  • UI の状態に応じて、表示用クエリの enabled が条件付きになっている
  • 条件を削除する。表示データはマウント時に読み込む必要があります

次のステップ - 実装のテスト

機能の実装が正常に完了したら、必ず次のステップをユーザーに案内してください:

1. 開発サーバーを起動する

サーバーがまだ起動していない場合は、起動します:

npm run dev      # or pnpm dev / yarn dev
2. 管理ダッシュボードにアクセスする

ブラウザーを開き、次の URL にアクセスします:

管理者の認証情報でログインします。

3. カスタム UI に移動する

ウィジェットの場合: ウィジェットが表示されるページに移動します。一般的なウィジェットゾーン:

  • 商品ウィジェット: Products → 商品を選択 → ページにウィジェットが表示されます
  • 注文ウィジェット: Orders → 注文を選択 → ページにウィジェットが表示されます
  • 顧客ウィジェット: Customers → 顧客を選択 → ページにウィジェットが表示されます

ページ内の正確な配置は、ゾーンの .before/.after サフィックスではなく、ダッシュボードの Editor ビュー(Layout Composer)でユーザーが設定します。ウィジェットは好きな位置にドラッグできることをユーザーに伝えてください。

UI ルート(カスタムページ)の場合:

  • 管理画面のサイドバー/ナビゲーションでカスタムページを探します(設定した label に基づきます)
  • または、次の URL に直接移動します:http://localhost:9000/app/[your-route-path]
4. 機能をテストする

実装した内容に応じて、次の項目をテストします:

  • フォーム: エンティティの作成/編集を試し、検証とエラーメッセージを確認します
  • テーブル: ページネーション、検索、並べ替え、行選択をテストします
  • データ表示: データが正しく読み込まれ、変更後に更新されることを確認します
  • モーダル: FocusModal/Drawer を開き、フォームの送信をテストして、データが更新されることを確認します
  • ナビゲーション: リンクをクリックし、ルーティングが正しく動作することを確認します
次のステップの提示形式

実装後は、次のステップを常に明確で実行しやすい形式で提示します:

## 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]

コメント