| name | frappe-report-generator |
| description | Generate Frappe reports (query/script) with filters, charts, HTML templates, and JS customization. |
Frappe Report Generator
Create custom reports for data analysis, dashboards, and business intelligence in Frappe.
When to Use
- Creating custom reports (query/script)
- Data analysis or aggregation
- Building dashboards
- Report formatting and filters
Report Types
Query Report: SQL-based, fast for large datasets
Script Report: Python-based, full flexibility
Report Builder: No-code, user-configurable
Core Patterns
1. Basic Query Report
JSON:
{
"name": "Sales Analysis",
"report_type": "Query Report",
"ref_doctype": "Sales Order",
"module": "Selling"
}
Python:
import frappe
from frappe import _
def execute(filters=None):
return get_columns(), get_data(filters)
def get_columns():
return [
{"fieldname": "customer", "label": _("Customer"), "fieldtype": "Link", "options": "Customer", "width": 150},
{"fieldname": "grand_total", "label": _("Total"), "fieldtype": "Currency", "width": 120}
]
def get_data(filters):
return frappe.db.sql("""
SELECT customer, grand_total
FROM `tabSales Order`
WHERE docstatus = 1
AND posting_date BETWEEN %(from_date)s AND %(to_date)s
""", filters, as_dict=1)
2. Script Report with Chart & Summary
def execute(filters=None):
columns = get_columns()
data = get_data(filters)
chart = get_chart_data(data)
summary = get_report_summary(data)
return columns, data, None, chart, summary
def get_chart_data(data):
return {
"data": {"labels": [...], "datasets": [{"name": "Sales", "values": [...]}]},
"type": "bar"
}
def get_report_summary(data):
return [
{"label": "Total", "value": sum(...), "indicator": "Green"},
{"label": "Count", "value": len(data), "indicator": "Blue"}
]
3. Filters
{
"filters": [
{"fieldname": "from_date", "fieldtype": "Date", "label": "From Date", "reqd": 1},
{"fieldname": "to_date", "fieldtype": "Date", "label": "To Date", "reqd": 1},
{"fieldname": "customer", "fieldtype": "Link", "options": "Customer"}
]
}
4. HTML Template
Pattern: Use Jinja2-like syntax for custom layouts
Reference: See projectnext/report/project_cost_and_time_report/project_cost_and_time_report.html
def execute(filters=None):
html = None
return columns, data, None, chart, summary, html
Key HTML patterns:
{{ data[0].field }} - Access data
{{ filters.field }} - Access filters
{% for row in data %} - Iterate
{% var blocks = {} %} - Grouping
5. JavaScript Customization
Pattern: Client-side formatting and interactions
Reference: See projectnext/report/project_cost_and_time_report/project_cost_and_time_report.js
frappe.query_reports["Report Name"] = {
"formatter": function(value, row, column, data, default_formatter) {
if (column.fieldname === "delay" && value > 5) {
return `<span style="color: red;">${value}</span>`;
}
return default_formatter(value, row, column, data);
}
};
6. Axis-Mixed Chart
Pattern: Combine bars and lines for multi-metric visualization
Reference: See projectnext/report/project_cost_and_time_report/project_cost_and_time_report.py:120-196
chart = {
"data": {
"labels": labels,
"datasets": [
{"name": "Cost", "values": costs, "chartType": "bar"},
{"name": "Progress", "values": progress, "chartType": "line"}
]
},
"type": "axis-mixed",
"colors": ["#7cd6fd", "#5cb85c"]
}
7. Report Summary with Indicators
Pattern: Color-coded indicators based on values
Reference: See projectnext/report/project_cost_and_time_report/project_cost_and_time_report.py:67-118
summary = [
{
"label": "Completion",
"value": f"{percentage:.1f}%",
"indicator": "Red" if percentage < 30 else "Orange" if percentage < 70 else "Green"
}
]
8. Custom Query Functions
Pattern: Organize complex queries in controller modules
from projectnext.controllers.queries.reports.costandtimereport import get_project_report
def get_report_data(filters):
return get_project_report("Project", "project", "", 0, 200, filters)
9. Filter Validation
def validate_filters(filters):
if not filters.get("project"):
frappe.throw(_("Project is required"))
if filters.get("start") > filters.get("end"):
frappe.throw(_("Start Date cannot be after End Date"))
10. Data Grouping
blocks = {}
for row in data:
block = row.get("block_name") or "Unassigned"
if block not in blocks:
blocks[block] = []
blocks[block].append(row)
File Structure
apps/<app>/<module>/report/<report_name>/
โโโ __init__.py
โโโ <report_name>.json
โโโ <report_name>.py
โโโ <report_name>.js (optional)
โโโ <report_name>.html (optional)
Advanced Patterns
Complex Joins: Use INNER JOIN with GROUP BY for aggregations
Dynamic Columns: Build columns list programmatically
Caching: Use frappe.cache().get_value() for expensive queries
Permissions: Check with frappe.has_permission() before data access
Performance: Add indexes, use LIMIT, filter early in WHERE clause
Complete Examples
Simple Report: See ERPNext erpnext/selling/report/sales_analysis/
Complex Report: See projectnext/report/project_cost_and_time_report/
- Python: Lines 1-196 (structure, validation, charts, summary)
- HTML: Custom template with grouping
- JS: Client-side formatting
- JSON: Filter configuration
Best Practices
- Optimize queries (indexes, LIMIT)
- Filter early (WHERE clause, not Python)
- Use parameterized queries
- Validate filters
- Check permissions
- Cache expensive calculations
- Use HTML templates for complex layouts
- Use JS for client-side formatting
- Group data for better presentation
- Use indicators for quick status
Key Takeaways
- Query Reports: Fast SQL-based reports
- Script Reports: Flexible Python-based reports
- HTML Templates: Custom layouts and grouping
- JavaScript: Client-side formatting
- Charts: Bar, line, axis-mixed types
- Summary: Indicators for status
- Validation: Always validate filters
- Performance: Index, cache, limit
Remember: This skill is model-invoked. Claude will use it autonomously when detecting report development tasks.
Decision Tree & Reference
Source: consolidated from frappe-syntax-reports and frappe-impl-reports (Frappe Claude Skill Package).
Which report type?
Need a report?
โโ Simple list / group by on one DocType โ Report Builder (UI-only; Group By: Count/Sum/Avg)
โโ Direct SQL, no Python logic โ Query Report (legacy column aliases in SQL)
โโ Complex logic, charts, summaries, trees โ Script Report (standard: .py + .js; needs Developer Mode)
โโ Quick Python without deploying an app โ Script Report โ Custom (Python in Report UI; System Manager)
Additional signals (Desk / product):
End user builds their own report? โ Report Builder
Realtime KPI tile on workspace? โ Number Card or Dashboard Chart (not a report substitute)
Huge dataset (>~100k rows) or timeouts? โ enable Prepared Report (background job)
Script Report execute() return shape
What to return?
โโ Data only โ columns, data
โโ + HTML message above grid โ columns, data, message
โโ + chart โ columns, data, None, chart
โโ + summary cards โ columns, data, None, None, report_summary
โโ Full โ columns, data, message, chart, report_summary, skip_total_row
Positional order must stay: columns, data, message, chart, report_summary, skip_total_row / skip_total_rows (Frappe expects this sequence).
Report types (quick glance)
| Type | Code | Typical use | Access notes |
|---|
| Report Builder | None | Single DocType listing, filters, group by | Broader user access |
| Query Report | SQL | Legacy SQL reports | Often System Managerโlevel |
| Script Report (standard) | Python + JS | Charts, summaries, complex logic | Administrator + Developer Mode |
| Script Report (custom) | Python in UI | One-offs without shipping code | System Manager |
| Prepared Report | flag on report | Slow / huge result sets | Background generation, cached |
Supported filter fieldtypes (.js filters)
| Fieldtype | Options | Behavior |
|---|
Link | DocType | Autocomplete |
Select | newline-separated values | Fixed dropdown |
Date | โ | Date picker |
DateRange | โ | [from_date, to_date] |
Check | โ | Boolean |
Dynamic Link | fieldname of driving filter | Depends on another filter |
Data | โ | Free text |
Int | โ | Integer |
MultiSelectList | DocType | Multi-select |
Chart types & summary rows
- Chart
type (standard): bar, line, pie, donut, percentage โ plus mixed/axis setups when using per-dataset chartType.
chart.data: labels length must match each datasetโs values length (otherwise rendering breaks).
- Chart dict may include
fieldtype, options, currency, colors, height, barOptions (e.g. stacked), etc., as needed for formatting.
report_summary entries: value, label, datatype (e.g. Currency, Int), optional currency, indicator (Green, Blue, Orange, Red, Grey).
ALWAYS / NEVER (report code & data)
- ALWAYS return
columns and data as lists โ use [], not None, when empty.
- ALWAYS define Script Report columns as dicts with
fieldname, label, fieldtype (and width). Query Reports only: use legacy "Label:Fieldtype/Options:Width" in SQL SELECT aliases โ not the dict format.
- ALWAYS use
_(...) / translatable helpers for user-visible labels in columns and summaries.
- ALWAYS bind SQL parameters safely โ pass filter values as parameters to
frappe.db.sql / query builder; never interpolate untrusted filter input into the SQL string.
- ALWAYS set Reference DocType on the Report document so permissions line up with the underlying data.
- NEVER use
SELECT * or load full documents inside tight loops for report rows โ select columns in SQL or light APIs.
- NEVER skip
width on column dicts if you care about readable layout in the grid.
- For heavy reports, ALWAYS consider indexes on filtered/grouped columns and Prepared Report when runtime or row count is high.