medusa-analytics
v0.0.53
Published
A starter for Medusa plugins.
Downloads
4,303
Maintainers
Readme
medusa-analytics
Medusa v2 admin analytics extension that adds orders and customers dashboards with custom admin APIs.
Plugin Overview
medusa-analytics adds an Analytics extension page to the Medusa Admin with:
- Orders KPIs (total orders, revenue, AOV, pending/cancelled/delivered, returns, exchanges, fulfillment time)
- Orders time-series charts (7 days, 30 days, 12 months)
- Customer KPIs (guest, registered, total, deleted accounts)
- Customer time-series charts (registered vs guest over time)
- POS / Stores KPIs (net/gross sales, AOV, velocity, payment mix, staff, inventory performance, LTV, CSV export)
Aggregation uses paginated date-filtered Query API calls (no silent 50k cap). Day buckets respect plugin timezone (default Asia/Kolkata). Revenue prefers metadata.pos_charged_total for POS sales.
Medusa Compatibility
- Built for Medusa v2
- Package versions indicate Medusa
2.12.4compatibility (@medusajs/framework,@medusajs/medusa)
Installation & Setup
1) Install
npm install medusa-analyticsyarn add medusa-analytics2) Register plugin in medusa-config.ts / medusa-config.js
import { defineConfig } from "@medusajs/framework"
export default defineConfig({
plugins: [
{
resolve: "medusa-analytics",
options: {},
},
],
})3) Build and run
npx medusa plugin:build
npx medusa develop4) Migrations
This plugin does not define custom models or migrations. No plugin-specific migration step is required.
Configuration (config.ts / plugin options)
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| timezone | string | No | Asia/Kolkata | IANA timezone for day/hour buckets |
| guestEmail | string | No | [email protected] | Excluded from LTV / repeat metrics |
Example registration:
{
resolve: "medusa-analytics",
options: {
timezone: "Asia/Kolkata",
guestEmail: "[email protected]",
}
}Environment Variables
No runtime environment variables are consumed by the implemented analytics code.
| Variable | Required | Purpose | Example | |---|---|---|---| | None in implementation | - | - | - |
⚠️ Note:
src/modules/README.mdcontains generic starter examples usingprocess.env, but those are documentation snippets, not active plugin logic.
REST APIs / Routes
All implemented custom APIs are admin routes under /admin/analytics/*.
1) GET /admin/analytics/orders-summary
Returns orders KPI cards data and status counts (pending, cancelled, delivered) for a selected day range.
Auth requirement: Admin authenticated request (admin route context)
Query Params
| Name | Type | Required | Description |
|---|---|---|---|
| days | string \| number | No | Supports all (default), 0 (today), or integer day ranges (clamped between 1 and 365). |
Response Schema
| Field | Type | Description |
|---|---|---|
| totalOrders | number | Total orders in range. |
| totalRevenue | number | Revenue sum excluding cancelled orders. |
| aov | number | Average order value excluding cancelled orders. |
| ordersToday | number | Orders created today. |
| pendingOrders | number | Pending orders count. |
| cancelledOrders | number | Cancelled orders count. |
| deliveredOrders | number | Delivered orders count (based on fulfillments.delivered_at). |
| exchangeOrders | number | Exchange order changes in range. |
| returnOrders | number | Returns in range. |
| avgFulfillmentTimeHours | number \| null | Average created_at -> first shipped_at duration. |
| statusCounts | { pending?: number; cancelled?: number; delivered?: number } | Status breakdown used by charts/cards. |
Example
curl -X GET "http://localhost:9000/admin/analytics/orders-summary?days=30" \
-H "Authorization: Bearer <ADMIN_TOKEN>"2) GET /admin/analytics/orders-over-time
Returns orders time-series for charting.
Auth requirement: Admin authenticated request (admin route context)
Query Params
| Name | Type | Required | Description |
|---|---|---|---|
| period | string | No | one_week (default), one_month, 12_months, or one_year. |
Response Schema
| Field | Type | Description |
|---|---|---|
| dailyOrders | Array<{ date: string; orders_count: number; label?: string }> | Time-series points; monthly modes include optional month label. |
Example
curl -X GET "http://localhost:9000/admin/analytics/orders-over-time?period=one_year" \
-H "Authorization: Bearer <ADMIN_TOKEN>"3) GET /admin/analytics/customers-summary
Returns customer summary cards (guest, registered, total, deleted).
Auth requirement: Admin authenticated request (admin route context)
Query Params
| Name | Type | Required | Description |
|---|---|---|---|
| days | string \| number | No | Supports all (default), 0, or integer day ranges (clamped between 1 and 365). |
Response Schema
| Field | Type | Description |
|---|---|---|
| guestCount | number | Customers without account in range. |
| registeredCount | number | Customers with account in range. |
| totalCount | number | Total customers in range. |
| deletedAccountsCount | number | Count of soft-deleted customer accounts (withDeleted: true). |
Example
curl -X GET "http://localhost:9000/admin/analytics/customers-summary?days=90" \
-H "Authorization: Bearer <ADMIN_TOKEN>"4) GET /admin/analytics/customers-over-time
Returns customer time-series split by registered vs guest.
Auth requirement: Admin authenticated request (admin route context)
Query Params
| Name | Type | Required | Description |
|---|---|---|---|
| period | string | No | one_week (default), one_month, 12_months, or one_year. |
Response Schema
| Field | Type | Description |
|---|---|---|
| series | Array<{ date: string; label?: string; registered_count: number; guest_count: number }> | Time-series rows for dual-line chart rendering. |
Example
curl -X GET "http://localhost:9000/admin/analytics/customers-over-time?period=one_month" \
-H "Authorization: Bearer <ADMIN_TOKEN>"5) POS analytics (/admin/analytics/pos/*)
Shared query params: from, to (ISO), location_id, sales_channel_id, source=pos|all|web.
| Endpoint | Purpose |
|---|---|
| GET /admin/analytics/pos/sales-summary | Gross/net/tax/AOV/tickets/returns/margin |
| GET /admin/analytics/pos/sales-over-time | Velocity series (granularity=hour\|day\|week), including gross_margin per bucket |
| GET /admin/analytics/pos/sales-by-category | Revenue and margin by one product category per line |
| GET /admin/analytics/pos/sales-by-channel | Revenue by sales channel. Ignores sales_channel_id so every channel in range is included |
| GET /admin/analytics/pos/promotion-performance | Line revenue and discount grouped by adjustment code |
| GET /admin/analytics/pos/stored-value | Gift card and wallet totals. { available: false } when wallet tables are absent |
| GET /admin/analytics/pos/payment-mix | Tender breakdown |
| GET /admin/analytics/pos/product-sales | Top SKUs + ABC bucket |
| GET /admin/analytics/pos/inventory-performance | Sell-through approx + on-hand |
| GET /admin/analytics/pos/customers-summary | New/repeat/LTV (excludes guest email) |
| GET /admin/analytics/pos/basket-pairs | Market-basket pairs |
| GET /admin/analytics/pos/staff-summary | Sales / items-per-min / discounts / returns by cashier |
| GET /admin/analytics/pos/export?format=csv | CSV export of POS orders |
Services
No custom service classes are implemented in this plugin.
The routes query Medusa data via Query graph pagination helpers in src/utils/analytics-helpers.ts and core modules (Modules.ORDER, Modules.CUSTOMER, Modules.USER).
Workflows & Steps (Medusa v2)
No custom workflows or steps are implemented in source code.
Subscribers / Event Hooks
No event subscribers are implemented in source code.
Admin UI / Widgets
This plugin ships a custom Admin route extension (not widget-zone injection).
Route: Analytics page
- Location: Extensions sidebar as
Analytics(ChartBar icon) - File:
src/admin/routes/analytics/page.tsx - What it renders: Module switcher with three dashboards: Orders, Customers, and POS / Stores
- Interactions: Tab/button switch between modules
- Data consumed: Ecommerce analytics APIs +
/admin/analytics/pos/*
Orders dashboard
- File:
src/admin/routes/analytics/components/OrdersDashboard.tsx - Renders:
- KPI cards (orders, revenue, AOV, orders today, pending, cancelled, delivered, exchange, return)
- Pie chart for order status
- Pie chart for returns vs exchanges
- Line chart for orders over time
- Interactions:
- Summary period selector (
all,0,7,30,90) - Over-time period selector (
one_week,one_month,one_year) - Clear filter action
- Summary period selector (
- Data consumed:
/admin/analytics/orders-summary/admin/analytics/orders-over-time
Customers dashboard
- File:
src/admin/routes/analytics/components/CustomersDashboard.tsx - Renders:
- KPI cards (guest, registered, overall, deleted accounts)
- Dual-line chart (registered vs guest over time)
- Interactions:
- Count period selector (
all,0,7,30,90) - Graph period selector (
one_week,one_month,one_year) - Clear filter action
- Count period selector (
- Data consumed:
/admin/analytics/customers-summary/admin/analytics/customers-over-time
Models & Entities
No custom entities/models are defined by this plugin.
It reads existing Medusa entities through query/module APIs:
orderfulfillment(via order fulfillments fields)returnorder_changecustomer
Use Cases & Examples
Track order health in admin
- Use
/admin/analytics/orders-summary?days=30to monitor pending, delivered, and cancelled distribution.
- Use
Visualize order demand trends
- Use
/admin/analytics/orders-over-time?period=one_yearfor monthly trendline analysis.
- Use
Monitor customer acquisition mix
- Use
/admin/analytics/customers-over-time?period=one_monthto compare guest vs registered growth.
- Use
Operational return/exchange visibility
- Use
exchangeOrdersandreturnOrdersfrom/admin/analytics/orders-summaryto track post-purchase operations.
- Use
Review account lifecycle signals
- Use
/admin/analytics/customers-summaryto compare total/registered/guest counts and deleted accounts.
- Use
Troubleshooting
Analytics page does not appear in Admin
- Ensure plugin is registered in
medusa-config.ts. - Rebuild plugin:
npx medusa plugin:build. - Restart backend and hard refresh admin.
401/403 on analytics APIs
- These are
/admin/*routes; call with valid admin auth token/session.
Empty charts or zero counts
- Confirm store has orders/customers in selected range.
- Try broader ranges (
days=all,period=one_year).
Returns count stuck at 0 / isInitialized in logs
Medusa 2.21 OrderModule.listAndCountReturns can throw when a return references a missing shipping_method (Cannot read properties of undefined (reading 'isInitialized')). Analytics loads returns via Query Graph instead (loadReturnsForOrderIds) so the KPI still works.
Slow response with large datasets
- Aggregation is paginated (500 rows per page) with
created_atfilters — there is no silent 50k cap. - Prefer narrower date ranges for POS inventory / basket endpoints on very large catalogs.
"Failed to fetch ..." API errors
- Check backend logs for the route-specific error messages.
- Verify module availability and that core services (
ORDER,CUSTOMER) resolve correctly.
