Skip to content

ERPNext glossary

Script Report

Also called Script Reports, Frappe script report, ERPNext custom report

Definition

A Script Report is a Frappe report whose columns and rows are produced by Python, typically an execute(filters) function that returns column definitions and data, with its filters declared in a JavaScript file. It's the report type to use when the numbers need more logic than a single SQL query can express.

Frappe gives you several ways to build a report, and picking the heaviest one first is a common waste of an afternoon. The Report DocType’s type field lists Report Builder, Query Report, Script Report and Custom Report.

Choosing between the report types

Report Builder is a saved list view: pick a DocType, choose columns (child table fields included), add filters, group and sort. No code. If the question is “show me open Sales Orders by customer with these columns”, start here.

Query Report is a SQL select whose result becomes the report. It’s the right tool when one query answers the question.

Script Report runs Python. You can call several queries, loop, bucket values by date, merge data from different DocTypes and return rows in whatever shape the reader needs. You can also return a chart, a message and summary figures above the table.

Before writing any of them, check what ERPNext already ships. Accounts Receivable, for example, ages outstanding invoices with more options than most custom versions end up with.

Standard and non-standard

With developer mode on, a Script Report saved with Is Standard set to Yes and a Module chosen gets its own folder in that module, report/<report_name>/, containing three files: a .py with execute, a .js with filters, and a .json with the report record. That folder is part of a Frappe app and goes into Git like any other code.

A non-standard Script Report keeps its Python in the Report record itself. That code runs through the same safe_exec sandbox as a Server Script, so it needs server_script_enabled on the bench and inherits the same restrictions.

An example execute function

# acme_custom/acme_custom/report/overdue_by_customer/overdue_by_customer.py
import frappe
from frappe import _
from frappe.utils import date_diff, flt, getdate


def execute(filters=None):
    filters = frappe._dict(filters or {})
    as_of = getdate(filters.as_of_date)

    columns = [
        {"fieldname": "customer", "label": _("Customer"), "fieldtype": "Link", "options": "Customer", "width": 220},
        {"fieldname": "days_1_30", "label": _("1-30 Days"), "fieldtype": "Currency", "width": 130},
        {"fieldname": "days_31_60", "label": _("31-60 Days"), "fieldtype": "Currency", "width": 130},
        {"fieldname": "days_over_60", "label": _("Over 60 Days"), "fieldtype": "Currency", "width": 130},
    ]

    invoices = frappe.get_list(
        "Sales Invoice",
        filters={"docstatus": 1, "company": filters.company, "outstanding_amount": [">", 0], "due_date": ["<", as_of]},
        fields=["customer", "due_date", "outstanding_amount"],
    )

    rows = {}
    for inv in invoices:
        row = rows.setdefault(inv.customer, {"customer": inv.customer, "days_1_30": 0, "days_31_60": 0, "days_over_60": 0})
        days = date_diff(as_of, inv.due_date)
        bucket = "days_1_30" if days <= 30 else "days_31_60" if days <= 60 else "days_over_60"
        row[bucket] += flt(inv.outstanding_amount)

    return columns, list(rows.values())

The matching .js file registers company and as_of_date in frappe.query_reports["Overdue by Customer"].

Things that catch people out

  • Permissions. frappe.get_list applies the user’s permissions. frappe.get_all and raw frappe.db.sql skip them, so a report built on either shows every record to anyone who can open the report. Role access to the report is set separately on the Report record.
  • Return order. execute can return up to six values in a fixed order: columns, data, message, chart, report summary and a flag to skip the total row. Put a chart second and the table breaks.
  • Slow reports. If a Script Report runs longer than 15 seconds, Frappe switches it to a prepared report, which then runs as a background job, unless automation is disabled on the record.

erpfly writes reports as standard Script Reports inside the app, so each one arrives in the pull request with its filters and column definitions visible. The accounting module and ERPNext customization pages show the kinds of reports finance teams usually ask for.

Sources

The official documentation and source code this page was checked against.

  1. 01 Script Report, Frappe Framework documentation docs.frappe.io
  2. 02 report.json, Report DocType (version-15), frappe/frappe on GitHub github.com
  3. 03 report.py, execute_script_report (version-15), frappe/frappe on GitHub github.com

Where Script Report comes up

More ERPNext terms

Your next module is one paragraph away

Write it the way you’d explain it to a new hire. We’ll turn it into an app you can read, test and install.

ERPNext v16

Create your account

Free to start. No card needed.

By signing up you agree to our terms and privacy policy.