# How to Create Custom Dashboard Charts in ERPNext: A Complete Developer Guide

> Learn how to create custom dashboard charts in ERPNext with this complete developer guide. Understand the essential steps to visualize your ERP data effectively.

- Repository: [Frappe/erpnext](https://github.com/frappe/erpnext)
- Tags: how-to-guide
- Published: 2026-05-20

---

**Creating custom dashboard charts in ERPNext requires a Python or JavaScript source module that returns structured data, a Dashboard Chart document that references that source, and placement within a Dashboard document.**

ERPNext, the open-source ERP from Frappe, renders analytics dashboards by reading *Dashboard Chart* documents linked to executable data sources. Understanding the process for creating custom dashboard charts in ERPNext enables developers to surface real-time business metrics—from warehouse stock levels to sales order trends—directly within the native interface without modifying core framework files.

## Architecture Overview: The Four-Step Rendering Flow

ERPNext constructs dashboards through a strictly defined pipeline. According to the `frappe/erpnext` source code, the system executes four distinct phases when rendering a chart:

1. **Source Definition**: A Python file (or JavaScript module) queries the database and returns a JSON structure containing `labels`, `values`, and optional `options`.
2. **Document Configuration**: A *Dashboard Chart* document (stored in the database or shipped as a JSON fixture) specifies metadata including `chart_type`, `time_interval`, and the `source` path.
3. **Dashboard Assignment**: A *Dashboard* document maintains an array of chart references that determines which charts appear and their layout width (Half or Full).
4. **Client Rendering**: When a user loads the dashboard, ERPNext calls the source method via `frappe.get_attr` (Python) or `frappe.call` (JavaScript), then renders the visualization using ChartJS.

Key implementation files include the default dashboard definitions in [`erpnext/setup/setup_wizard/data/dashboard_charts.py`](https://github.com/frappe/erpnext/blob/main/erpnext/setup/setup_wizard/data/dashboard_charts.py) and example source modules like [`erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py`](https://github.com/frappe/erpnext/blob/main/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py).

## Step 1: Define the Chart Source Module

The **chart source** is the data layer. It lives in a folder named after your chart within the `dashboard_chart_source` directory of your app.

### Python Source Modules

Place your Python file at `{app}/{module}/dashboard_chart_source/{chart_name}/{chart_name}.py`. The module must expose a top-level `get` function that accepts a `filters` argument and returns a dictionary.

**Required return structure:**

- **`labels`**: List of strings for the X-axis or category names.
- **`values`**: List of integers or floats corresponding to the data points.
- **`options`**: Dictionary specifying ChartJS configuration (e.g., `type`, `color`).

**Example:** Warehouse-wise stock value (based on [`erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py`](https://github.com/frappe/erpnext/blob/main/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py)):

```python
import frappe

def get(filters=None):
    data = frappe.db.sql("""
        SELECT warehouse, SUM(actual_qty) as qty
        FROM `tabBin`
        WHERE posting_date BETWEEN %(from)s AND %(to)s
        GROUP BY warehouse
    """, filters, as_dict=True)

    return {
        "labels": [d.warehouse for d in data],
        "values": [d.qty for d in data],
        "options": {"type": "pie"}
    }

```

### JavaScript Source Modules

Alternatively, you can define the source in JavaScript at the same path with a `.js` extension. ERPNext invokes this via `frappe.call` to the corresponding Python method:

```javascript
// my_app/dashboard_chart_source/monthly_sales/monthly_sales.js
frappe.call({
    method: "my_app.dashboard_chart_source.monthly_sales.monthly_sales.get",
    args: { filters: filters },
    callback: function(r) {
        // r.message contains {labels, values, options}
    }
});

```

## Step 2: Create the Dashboard Chart Document

The **Dashboard Chart** document acts as the bridge between your data source and the UI. You can create this via the desk interface (**Setup → Dashboard Chart**) or ship it as a JSON fixture in your custom app.

**Critical fields:**

- **`source`**: Must match the folder name of your source module (e.g., `"Warehouse Wise Stock Value"`).
- **`chart_type`**: Set to `"Custom"` when using your own source.
- **`timespan`** and **`time_interval`**: Control default date filtering.
- **`filters_json`**: Stringified JSON object passed to your `get` function.

**Fixture example** (save as [`my_app/dashboard_chart/monthly_sales/monthly_sales.json`](https://github.com/frappe/erpnext/blob/main/my_app/dashboard_chart/monthly_sales/monthly_sales.json)):

```json
{
    "doctype": "Dashboard Chart",
    "chart_name": "Monthly Sales Orders",
    "time_interval": "Monthly",
    "timespan": "Last Year",
    "chart_type": "Custom",
    "source": "Monthly Sales Orders",
    "filters_json": "{}",
    "type": "Line",
    "width": "Half"
}

```

When installed, this document tells ERPNext to execute the `get` function from the `monthly_sales_orders` source module.

## Step 3: Add the Chart to a Dashboard

**Dashboard** documents contain an ordered list of charts. To display your custom chart, append a reference object to the `charts` array in the target dashboard definition.

**Dashboard definition example** (from [`erpnext/setup/setup_wizard/data/dashboard_charts.py`](https://github.com/frappe/erpnext/blob/main/erpnext/setup/setup_wizard/data/dashboard_charts.py)):

```python
{
    "doctype": "Dashboard",
    "dashboard_name": "Sales",
    "charts": [
        {"chart": "Monthly Sales Orders", "width": "Full"},
        {"chart": "Top Customers", "width": "Half"}
    ]
}

```

You can also assign charts dynamically via the UI by navigating to **Setup → Dashboard**, selecting the target dashboard, and dragging the new chart into the layout.

## Complete Implementation Example: Monthly Sales Orders

The following example demonstrates an end-to-end custom chart that displays monthly sales order counts for the last 12 months.

**1. Create the source module** at [`my_app/dashboard_chart_source/monthly_sales_orders/monthly_sales_orders.py`](https://github.com/frappe/erpnext/blob/main/my_app/dashboard_chart_source/monthly_sales_orders/monthly_sales_orders.py):

```python
import frappe
from frappe.utils import getdate, add_months

def get(filters=None):
    end_date = getdate()
    start_date = add_months(end_date, -12)

    data = frappe.db.sql("""
        SELECT DATE_FORMAT(posting_date, '%Y-%m') as month, COUNT(*) as cnt
        FROM `tabSales Order`
        WHERE posting_date BETWEEN %(start)s AND %(end)s
        GROUP BY month
        ORDER BY month
    """, {"start": start_date, "end": end_date}, as_dict=True)

    return {
        "labels": [d.month for d in data],
        "values": [d.cnt for d in data],
        "options": {"type": "line", "color": "#5e72e4"}
    }

```

**2. Create the fixture** at [`my_app/dashboard_chart/monthly_sales_orders.json`](https://github.com/frappe/erpnext/blob/main/my_app/dashboard_chart/monthly_sales_orders.json):

```json
{
    "doctype": "Dashboard Chart",
    "chart_name": "Monthly Sales Orders",
    "time_interval": "Monthly",
    "timespan": "Last Year",
    "chart_type": "Custom",
    "source": "Monthly Sales Orders",
    "filters_json": "{}",
    "type": "Line",
    "width": "Half"
}

```

**3. Add to a dashboard** by including it in your app's fixtures or manually in **Setup → Dashboard**.

## Summary

- **Source Module**: Build a Python file with a `get(filters)` function returning `labels`, `values`, and `options`, or use JavaScript for client-side data fetching.
- **Dashboard Chart Document**: Create a JSON fixture or UI document referencing the source folder name and specifying chart metadata.
- **Dashboard Assignment**: Insert the chart reference into a Dashboard document's `charts` array, setting the width to `Half` or `Full`.
- **Core References**: Default dashboards are defined in [`erpnext/setup/setup_wizard/data/dashboard_charts.py`](https://github.com/frappe/erpnext/blob/main/erpnext/setup/setup_wizard/data/dashboard_charts.py), while rendering utilities reside in [`erpnext/controllers/trends.py`](https://github.com/frappe/erpnext/blob/main/erpnext/controllers/trends.py).

## Frequently Asked Questions

### What is the difference between a Chart Source and a Dashboard Chart?

A **Chart Source** is the executable code (Python or JavaScript) that queries the database and returns raw data. A **Dashboard Chart** is the configuration document that references that source, stores user-defined filters, and determines visual properties like chart type and color scheme.

### Can I use JavaScript instead of Python for chart data sources?

Yes. While Python sources are preferred for database queries due to security and performance, JavaScript sources are supported for client-side logic. Place a `.js` file alongside your Python module, or use `frappe.call` to invoke a whitelisted Python method from the client.

### How do I filter data based on user input or date ranges?

The `get` function receives a `filters` dictionary parameter populated from the `filters_json` field of the Dashboard Chart document. Users can modify these filters via the chart's filter UI, and ERPNext passes the updated values to your source function on each refresh.

### Where are default ERPNext dashboard charts defined?

Default charts for standard modules (Accounts, Sales, Stock) are defined in [`erpnext/setup/setup_wizard/data/dashboard_charts.py`](https://github.com/frappe/erpnext/blob/main/erpnext/setup/setup_wizard/data/dashboard_charts.py). This file contains the initial Dashboard and Dashboard Chart documents created during the setup wizard, which you can reference as templates for your custom implementations.