You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Need a report?
├─ Simple list/group of one DocType → Report Builder
│ (no code, UI-only, supports Group By with Count/Sum/Avg)
├─ Direct SQL query, no Python logic needed → Query Report
│ (SQL in Report doc, column format in aliases)
├─ Complex logic, calculations, charts → Script Report (Standard)
│ (Python .py + JS .js files, requires Developer Mode)
└─ Quick one-off with Python but no app deploy → Script Report (Custom)
(Python in Report doc UI, System Manager can create)
Script Report returns what?
├─ Just data → return columns, data
├─ Data + chart → return columns, data, None, chart
├─ Data + summary → return columns, data, None, None, report_summary
├─ Data + message → return columns, data, message
└─ Everything → return columns, data, message, chart, report_summary, skip_total_row
ALWAYS define columns as list of dicts with fieldname, label, fieldtype. The legacy string format is ONLY for Query Report SQL aliases.
NEVER return None for columns or data in execute() — ALWAYS return empty lists [].
ALWAYS use _(...) for translatable labels in columns and report_summary.
NEVER use frappe.db.sql with user-supplied filter values directly in f-strings — ALWAYS pass as parameters: frappe.db.sql(query, filters, as_dict=True).
ALWAYS set Reference DocType on the Report document — it controls user access permissions.
NEVER omit width in column definitions — columns without width render poorly.
ALWAYS match datasets[].values length to labels length in chart data — mismatched lengths cause chart rendering errors.