Skip to content

PRISM - Interface

Accessing server api Through browser url

We can test the custom api directly without calling them in client / server script through browser

Pre-requisite A Server Script of type API should be used, with a return value set via frappe.response[‘message’] (since it’s being accessed from the browser, frappe.flags won’t be effective). ex: frappe.response['message'] = "pong

Basic Syntax in browser(chrome/ Firefox/ etc)

https://<sitename>/api/method/api_method?<parameter_name>="<your value>"

Common Use Cases Without Argument

https://lenscx.docker.localhost/api/method/ping

Sample Output

{
"message": "pong"
}

With Argument : if you have used frappe.form_dict.get("<variable>") ex script in ping API:

meta = frappe.form_dict.get("message")
frappe.response['message'] = meta```
https://lenscx.docker.localhost/api/method/ping?message="hello"

Sample Output

{
"message": "\"hello\""
}

Base64 Auth Key via Terminal

Use this command to create a base64-encoded string for HTTPs Basic Authentication through terminal. This is commonly used when interacting with APIs that require credentials passed in an Authorization header.

Basic Syntax

Terminal window
echo -n "api_key:api_secret" | base64

Example:

Terminal window
echo -n "bc0a2c2d2acb0f9:20ebfe67fbf273b" | base64

Output:

Terminal window
YmMwYTJjMmQyYWNiMGY5OjIwZWJmZTY3ZmJmMjczYg==

Common Use Cases When calling an API endpoint that requires Basic Authentication:

Terminal window
curl -H "Authorization: Basic YmMwYTJjMmQyYWNiMGY5OjIwZWJmZTY3ZmJmMjczYg==" https://api.example.com/data

Frappe Monkey Patching Technique

Monkey patching is a technique that allows you to dynamically modify or extend existing code at runtime without changing the original source files. In Python, this is possible because functions and methods are first-class objects that can be reassigned.

Basic Syntax

# In your custom app's custom file (e.g., monkey_patch.py)
# Step 1: Import the original function you want to change
import frappe
from frappe.utils import getdate #core utitle function
# Step 2: Create your own version of that function
def custom_getdate(string_date):
# Your custom logic
print("Patched getdate called!")
# Step 3: Use the original function if needed
return original_getdate(string_date)
# Step 4: Save the original function (optional but useful)
original_getdate = getdate
# Step 5: Replace the original function with your version at custom app's hook.py file
from frappe.utils import getdate
from custom_app.<file_name> import custom_getdate
frappe.utils.getdate = custom_getdate

Common Use Cases

Use CaseExample
Override core utility functionsfrappe.utils.getdate, frappe.sendmail
Modify controller methodsSalesOrder.validate, Quotation.on_submit
Customize API behaviorPatch whitelisted method like frappe.desk.form.load.getdoc
Patch background job functionsModify logic inside long jobs

Example Your company wants to prevent users from backdating Sales Invoices — they should only be allowed to select today’s date or future dates. However, by default, Frappe allows any posting date.

Solution We’ll override the default validate_posting_time method in accounts.doctype.sales_invoice.sales_invoice to add our custom date rule. path: your_app/your_app/monkey_patch.py

# Import the original Sales Invoice class
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice
import frappe
from frappe.utils import nowdate
# Save the original method (optional, in case you want fallback)
original_validate_posting_time = SalesInvoice.validate_posting_time
# Create a new method
def custom_validate_posting_time(self):
if self.posting_date < nowdate():
frappe.throw("Backdated Sales Invoices are not allowed. Please choose today or a future date.")
# You can still call the original method if needed
# original_validate_posting_time(self)

path: your_app/your_app/hook.py

from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice
from your_app.monkey_patch import custom_validate_posting_time
# Replace the method in the class with your version
SalesInvoice.validate_posting_time = custom_validate_posting_time

Output Now, if a user tries to submit a Sales Invoice with a past posting date, they’ll see this:

Backdated Sales Invoices are not allowed. Please choose today or a future date.

Customizations via hooks.py

The hooks.py file in your custom app is where you tell Frappe what to do and when — like plug-in points to modify behavior without touching the core.

1. doc_events

doc_events allows you to run custom Python functions during specific events of a DocType (like validate, on_submit, on_cancel, etc.).

Basic Syntax

doc_events = {
"{doctype_name}": {
"{event}": "path.to.your_method"
}
}

Common Use Case Run custom validation before saving a Quotation

doc_events = {
"Quotation": {
"validate": "my_app.events.quotation.validate_discount_limit" #custon python definition by default doc and event will be passed
}
}
# the file look like
def validate_discount_limit(doc, method):
if doc.additional_discount_percentage > 10:
frappe.throw("Discount cannot exceed 10%.")

Common DocType Event

EventWhen it runs
before_insertBefore a new document is inserted
after_insertAfter a new document is inserted
validateDuring document validation
before_saveBefore the document is saved
on_updateAfter the document is updated
before_submitBefore submission
on_submitAfter submission
before_cancelBefore cancellation
on_cancelAfter cancellation
on_trashBefore deletion

2. override_doctype_class

This allows you to completely replace the backend class of a standard DocType with your own custom class.

Basic Syntax

override_doctype_class = {
"{doctype_name}": "your_app.overrides.custom_class_path"
}

Common Use Case Override the Sales Invoice class to change on_submit behavior:

override_doctype_class = {
"Sales Invoice": "my_app.overrides.custom_sales_invoice.CustomSalesInvoice"
}
# the file look like
class CustomSalesInvoice(SalesInvoice):
def on_submit(self):
super().on_submit()
# Your custom logic here
print("Custom on_submit called!")

Important: When overriding standard functionality, call the original implementation using super() unless there is a specific reason not to.

3. extend_doctype_class

Used to extend the standard DocType class by adding custom methods, properties, or behavior without replacing the original controller.

Basic Syntax

extend_doctype_class = {
"{doctype_name}": [
"your_app.extensions.custom_class.CustomClass"
]
}

Common Usecase Add custom behavior to the standard Address DocType while keeping the original class functionality.

hooks.py
extend_doctype_class = {
"Address": [
"my_app.extensions.address.CustomAddress"
]
}
# my_app/extensions/address.py
from frappe.contacts.doctype.address.address import Address
class CustomAddress(Address):
def validate(self):
super().validate()
# Your custom logic
pass

Best Practice: Prefer extend_doctype_class when you only need to add or extend functionality. Use override_doctype_class when the standard controller needs to be replaced.

4. override_whitelisted_methods

Used to replace any Frappe or ERPNext whitelisted API method with your own implementation. Basic Syntax

override_whitelisted_methods = {
"path.to.original_method": "your_app.api.custom_method"
}

Common Use Case Customize how link field search works:

override_whitelisted_methods = {
"frappe.desk.search.search_link": "my_app.api.custom_search_link"
}
# the file look like
@frappe.whitelist()
def custom_search_link(doctype, txt, query=None, filters=None, page_length=20):
print("🔍 Using custom search link!")
# your logic
return search_widget(doctype, txt, query, filters, page_length)

5. override_report_class

Used to replace the Python class that powers a standard report. Basic Syntax

override_report_class = {
"{report_name}": "your_app.reports.custom_report.CustomReportClass"
}

Common Use Case Change how data is fetched in Accounts Receivable report:

override_report_class = {
"Accounts Receivable": "my_app.reports.accounts_receivable.CustomARReport"
}
# the file look like
class CustomARReport(AccountsReceivable):
def run(self, filters=None):
# Call parent method to get default data
columns, data = super().run(filters)
# Example: Add a new column
columns.append({"label": "Overdue Alert", "fieldname": "overdue_alert", "fieldtype": "Data", "width": 120})
# Example: Add logic to tag overdue invoices
for row in data:
row["overdue_alert"] = "⚠️ Overdue" if row.get("outstanding_amount", 0) > 0 and row.get("due_date") < self.today else ""
return columns, data

6. scheduler_events

Run background jobs automatically at specific time intervals (daily, hourly, etc.) Basic Syntax

scheduler_events = {
"{interval}": [
"your_app.path.to.function"
]
}

Common Use Case Send a summary report every day

scheduler_events = {
"daily": [
"my_app.tasks.send_daily_sales_summary"
]
}

Common Schedules

ScheduleUse
allRuns frequently
hourlyRuns every hour
dailyRuns daily
weeklyRuns weekly
monthlyRuns monthly

7. before_install / after_install

Used to run setup scripts when the app is installed. Basic Syntax

before_install = "your_app.install.before_install"
after_install = "your_app.install.after_install"

Common Use Case Create default records after app installation:

after_install = "my_app.install.setup_default_roles"

8. Fixtures

Fixtures are used to migrate specific data and settings from the custom app during its installation, so they could be available after installation

Basic Syntax In the app’s hook.py file

fixtures = [
{
"doctype": {doctype_name},
"filters": [
["name", "in", [{document_name}, {document_name}]]
]
}]

Note : refer https://lmnaslimited.github.io/lens-docs/04-developer-cheat-sheet/01-infrastructure/03-bench-commands/#bench-fixtures-command for executing fixture Common Use Cases Migrating a custom field using fixture

fixtures = [
{
"doctype": "Custom Field",
"filters": [
["name", "in", ["CRM Lead-custom_prompt", "CRM Lead-custom_question_type"]]
]
}]

9. page_js

page_js allows you to include custom JavaScript files specifically for standard Frappe pages like print, query-report, dashboard, etc.

Basic Syntax

page_js = {
"{page_name}": "public/js/<filename>"
}

Common Use Case Override the Print page’s set_title function to hide the Print button for a restricted document like “Quotation” in Draft.

hooks.py
page_js = {
"print": "public/js/print_restrict.js"
}
custom_app/public/js/print_restrict.js
(function () {
const fnOriginalSetTitle = frappe.ui.form.PrintView.prototype.set_title;
frappe.ui.form.PrintView.prototype.set_title = function () {
// Hide PDF button
if(this.frm.doctype === "Quotation" && this.frm.doc.status === "Draft"){
$(".btn.btn-primary.btn-sm.primary-action").hide();
}
fnOriginalSetTitle.call(this);
};
})

Debugging Background Job Functions in Frappe

Background jobs (scheduler / queue workers) are asynchronous, which makes debugging difficult.

This method lets you run them directly in VS Code debugger using bench execute.

Purpose

Run background job functions synchronously so:

  • Breakpoints work instantly
  • No scheduler required
  • No worker setup needed
  • Faster debugging

Concept

Instead of:

Scheduler → Queue Worker → Job Execution

We do:

VS Code Debugger → bench execute → Function runs immediately

VS Code Launch Configuration

Add this in .vscode/launch.json

{
"name": "Frappe: Debug Background Job",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/apps/frappe/frappe/utils/bench_helper.py",
"args": [
"frappe",
"--site",
"your-site-name.localhost",
"execute",
"erpnext.stock.reorder_item.reorder_item"
],
"cwd": "${workspaceFolder}/sites",
"env": {
"DEV_SERVER": "1"
}
}

Equivalent Bench Command

bench --site your-site-name.localhost execute erpnext.stock.reorder_item.reorder_item

Common Use Cases

Scheduler Job

erpnext.stock.reorder_item.reorder_item
  • Put breakpoint in function
def reorder_item():
import pdb; pdb.set_trace()
  • Select VS Code debug config
  • Press F5
  • Function runs instantly → breakpoint hits

Note: Change function to debug any job “erpnext.stock.reorder_item.reorder_item” to “your_app.module.function_name”