Creating Custom Statistics Providers
May 1, 2026 · 5 min read
Statistics providers are the foundation of medusa-stats. This guide will walk you through creating your own custom provider to calculate business-specific metrics.
#Understanding Providers
A statistics provider is a class that extends AbstractStatisticsProvider and defines:
- Available Statistics - What metrics your provider exposes
- Calculation Logic - How each statistic is calculated
#Basic Provider Structure
Here's the structure of a statistics provider:
import { ModuleProvider } from "@medusajs/framework/utils"
import {
AbstractStatisticsProvider,
StatBuilder,
createTimeSeries,
sum,
type AvailableStatistic,
type CalculateStatisticInput,
type StatisticResult,
} from "medusa-stats"
class MyStatisticsProvider extends AbstractStatisticsProvider {
static identifier = "my-statistics"
static displayName = "My Statistics Provider"
async getAvailableStatistics(): Promise<AvailableStatistic[]> {
// Define your statistics here
return []
}
async calculateStatistic(input: CalculateStatisticInput): Promise<StatisticResult> {
// Implement calculation logic
}
}
export default ModuleProvider("statistics", {
services: [MyStatisticsProvider],
})
#Defining a Statistic
Use the StatBuilder to define a statistic with its configuration:
new StatBuilder("total_cart_value", "Total Cart Value")
.description("Total value of all carts over time")
.field({
name: "currency_code",
label: "Currency",
description: "Filter by currency code",
schema: z.string().optional(),
fieldType: "text",
placeholder: "USD"
})
.chart("line")
.dimension("time")
.build()
#Field Types
- text: Simple text input
- number: Numeric input
- stat: Composite statistic dependency
#Chart Types
- line
- bar
- area
- And more...
#Complete Example: Total Cart Value
Here's a complete provider implementation:
import { ModuleProvider } from "@medusajs/framework/utils"
import {
AbstractStatisticsProvider,
StatBuilder,
createTimeSeries,
sum,
type AvailableStatistic,
type CalculateStatisticInput,
type StatisticResult,
} from "medusa-stats"
import { z } from "zod"
class CartStatisticsProvider extends AbstractStatisticsProvider {
static identifier = "cart-statistics"
static displayName = "Cart Statistics Provider"
async getAvailableStatistics(): Promise<AvailableStatistic[]> {
return [
new StatBuilder("total_cart_value", "Total Cart Value")
.description("Total value of all carts over time")
.field({
name: "currency_code",
label: "Currency",
description: "Filter by currency code",
schema: z.string().optional(),
fieldType: "text",
placeholder: "USD"
})
.chart("line")
.dimension("time")
.build(),
]
}
async calculateStatistic(input: CalculateStatisticInput): Promise<StatisticResult> {
const { id, parameters, periodStart, periodEnd, interval } = input;
switch(id) {
case "total_cart_value": {
const currencyCode = parameters.currency_code;
const filters: any = {
created_at: { $gte: periodStart, $lte: periodEnd }
};
if (currencyCode) {
filters.currency_code = currencyCode;
}
// Query cart data
const { data: carts } = await this.query.graph({
entity: "cart",
fields: ["id", "created_at", "total"],
filters
});
// Create time series from cart data
const timeSeries = createTimeSeries(
carts,
periodStart,
periodEnd,
interval,
sum('total')
);
return {
value: timeSeries,
metadata: { totalCarts: carts.length }
};
}
}
}
}
export default ModuleProvider("statistics", {
services: [CartStatisticsProvider],
})
#Registering Your Provider
Add your custom provider to medusa-config.ts:
modules: [
{
resolve: "medusa-stats/modules/statistics",
options: {
providers: [
{ resolve: "medusa-stats/providers/common" },
{ resolve: "medusa-stats/providers/composite" },
{ resolve: "./src/providers/statistics/cart-statistics" }, // Your provider
],
},
},
]
#Creating Composite-Enabled Statistics
To make your statistics work with the composite provider, define fields with fieldType: "stat":
new StatBuilder("moving_average", "Moving Average")
.description("Smooth a time series by averaging values over a rolling window")
.field({
name: "input_series",
label: "Input Series",
description: "Dependency result to analyze",
fieldType: "stat",
schema: TimeSeriesSchema
})
.field({
name: "window_size",
label: "Window Size",
fieldType: "number",
schema: z.number().int().min(2).max(365).default(7),
})
.build()
At runtime, composite fields automatically receive dependency output data.
#Helper Functions
#createTimeSeries()
Groups data into time buckets and applies an accumulator function.
#sum(), count(), avg()
Common accumulator functions for time series creation.