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:

  1. Available Statistics - What metrics your provider exposes
  2. 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.