frappe-api
Frappe API Reference
Complete reference for Frappe's Python and JavaScript APIs for document operations, database queries, utilities, and server communication.
When to Use This Skill
- Working with Document API (get_doc, new_doc, save)
- Database operations (frappe.db.*)
- Making API calls from client to server
- Using Frappe utilities (date, number formatting)
- Creating whitelisted API endpoints
- Working with REST API
Python API
Document Operations
Get Document
# Get existing document
doc = frappe.get_doc("Customer", "CUST-001")
# Get document with filters
doc = frappe.get_doc("Customer", {"customer_name": "John"})
# Get last document
doc = frappe.get_last_doc("Customer", filters={"status": "Active"})
# Get cached document (read-only, faster)
doc = frappe.get_cached_doc("Customer", "CUST-001")
# Check if document exists
if frappe.db.exists("Customer", "CUST-001"):
doc = frappe.get_doc("Customer", "CUST-001")
Create Document
# Create new document
doc = frappe.new_doc("Customer")
doc.customer_name = "New Customer"
doc.customer_type = "Company"
doc.insert()
# Create with dict
doc = frappe.get_doc({
"doctype": "Customer",
"customer_name": "New Customer",
"customer_type": "Company"
})
doc.insert()
# Create and insert in one step
doc = frappe.get_doc({
"doctype": "Customer",
"customer_name": "New Customer"
}).insert()
# Insert ignoring permissions
doc.insert(ignore_permissions=True)
# Insert ignoring mandatory fields
doc.insert(ignore_mandatory=True)
Update Document
# Update and save
doc = frappe.get_doc("Customer", "CUST-001")
doc.customer_name = "Updated Name"
doc.save()
# Save ignoring permissions
doc.save(ignore_permissions=True)
# Update single value
frappe.db.set_value("Customer", "CUST-001", "customer_name", "New Name")
# Update multiple values
frappe.db.set_value("Customer", "CUST-001", {
"customer_name": "New Name",
"status": "Active"
})
# Bulk update
frappe.db.set_value("Customer", {"status": "Inactive"}, "status", "Active")
Delete Document
# Delete document
frappe.delete_doc("Customer", "CUST-001")
# Delete ignoring permissions
frappe.delete_doc("Customer", "CUST-001", ignore_permissions=True)
# Delete with linked documents
frappe.delete_doc("Customer", "CUST-001", force=True)
# Delete from controller
doc.delete()
Database API (frappe.db)
Select Queries
# Get single value
value = frappe.db.get_value("Customer", "CUST-001", "customer_name")
# Get multiple fields
values = frappe.db.get_value("Customer", "CUST-001",
["customer_name", "status"], as_dict=True)
# Get with filters
value = frappe.db.get_value("Customer",
{"customer_type": "Company"}, "customer_name")
# Get list of values
names = frappe.db.get_all("Customer",
filters={"status": "Active"},
fields=["name", "customer_name"],
order_by="creation desc",
limit=10
)
# Get list with pluck (single field as list)
names = frappe.db.get_all("Customer",
filters={"status": "Active"},
pluck="name"
)
# Complex filters
docs = frappe.db.get_all("Sales Invoice",
filters={
"status": ["in", ["Paid", "Unpaid"]],
"grand_total": [">", 1000],
"posting_date": ["between", ["2024-01-01", "2024-12-31"]],
"customer": ["like", "%Corp%"]
},
fields=["name", "customer", "grand_total"]
)
# Filter operators
# =, !=, <, >, <=, >=
# in, not in
# like, not like
# between
# is, is not (for None)
# descendants of, ancestors of (for tree doctypes)
# Get count
count = frappe.db.count("Customer", {"status": "Active"})
# Check existence
exists = frappe.db.exists("Customer", "CUST-001")
exists = frappe.db.exists("Customer", {"customer_name": "John"})
Raw SQL
# Execute SQL query
result = frappe.db.sql("""
SELECT name, customer_name, grand_total
FROM `tabSales Invoice`
WHERE status = %s AND grand_total > %s
ORDER BY creation DESC
LIMIT 10
""", ("Paid", 1000), as_dict=True)
# Single value
total = frappe.db.sql("""
SELECT SUM(grand_total) FROM `tabSales Invoice`
WHERE status = 'Paid'
""")[0][0]
# With named parameters
result = frappe.db.sql("""
SELECT * FROM `tabCustomer`
WHERE name = %(name)s
""", {"name": "CUST-001"}, as_dict=True)
Insert/Update
# Insert raw
frappe.db.sql("""
INSERT INTO `tabCustomer` (name, customer_name)
VALUES (%s, %s)
""", ("CUST-002", "New Customer"))
# Commit transaction
frappe.db.commit()
# Rollback
frappe.db.rollback()
Whitelisted API
# Create API endpoint
@frappe.whitelist()
def get_customer_details(customer):
"""Get customer details
Args:
customer: Customer ID
Returns:
dict: Customer details
"""
doc = frappe.get_doc("Customer", customer)
return {
"name": doc.name,
"customer_name": doc.customer_name,
"outstanding_amount": get_outstanding(customer)
}
# Allow guest access (no login required)
@frappe.whitelist(allow_guest=True)
def public_api():
return {"status": "ok"}
# With specific methods
@frappe.whitelist(methods=["POST"])
def create_record(data):
doc = frappe.get_doc(data)
doc.insert()
return doc.name
Utilities
Date/Time
from frappe.utils import (
now, nowdate, nowtime, now_datetime,
today, getdate, get_datetime,
add_days, add_months, add_years,
date_diff, time_diff, time_diff_in_seconds,
get_first_day, get_last_day,
formatdate, format_datetime
)
# Current date/time
current = nowdate() # "2024-01-15"
current_dt = now_datetime() # datetime object
timestamp = now() # "2024-01-15 10:30:00"
# Date arithmetic
next_week = add_days(nowdate(), 7)
next_month = add_months(nowdate(), 1)
last_year = add_years(nowdate(), -1)
# Date difference
days = date_diff(end_date, start_date)
seconds = time_diff_in_seconds(end_time, start_time)
# First/last day of month
first = get_first_day(nowdate())
last = get_last_day(nowdate())
# Parse dates
date_obj = getdate("2024-01-15")
dt_obj = get_datetime("2024-01-15 10:30:00")
# Format dates
formatted = formatdate("2024-01-15", "dd-MM-yyyy")
Numbers
from frappe.utils import (
flt, cint, cstr,
fmt_money, rounded,
money_in_words
)
# Type conversion with defaults
num = flt(value) # float, None -> 0.0
num = flt(value, 2) # with precision
integer = cint(value) # int, None -> 0
string = cstr(value) # string, None -> ""
# Formatting
formatted = fmt_money(1234.56, currency="USD") # "$1,234.56"
rounded_val = rounded(1234.567, 2) # 1234.57
words = money_in_words(1234.56, "USD") # "One Thousand..."
Strings
from frappe.utils import (
strip_html, strip_html_tags,
escape_html, sanitize_html,
scrub, unscrub
)
# HTML handling
plain = strip_html("<p>Hello</p>") # "Hello"
safe = escape_html("<script>bad</script>")
# Field name conversion
field = scrub("My Field Name") # "my_field_name"
label = unscrub("my_field_name") # "My Field Name"
Messaging & Notifications
# Show message (appears as toast)
frappe.msgprint("Document saved successfully")
# With indicator
frappe.msgprint("Error occurred", indicator="red", title="Error")
# Throw error (stops execution)
frappe.throw("Invalid data provided")
# With exception type
from frappe.exceptions import ValidationError
frappe.throw("Validation failed", exc=ValidationError)
# Send email
frappe.sendmail(
recipients=["user@example.com"],
subject="Hello",
message="Email body",
template="email_template",
args={"name": "John"}
)
# Create system notification
frappe.publish_realtime(
"msgprint",
{"message": "Task completed"},
user="user@example.com"
)
Background Jobs
# Enqueue background job
frappe.enqueue(
"myapp.tasks.heavy_task",
queue="long",
timeout=600,
job_name="Heavy Task",
customer="CUST-001"
)
# In tasks.py
def heavy_task(customer):
# Long running task
process_customer(customer)
# Enqueue with callback
frappe.enqueue(
method=process_data,
queue="default",
on_success=on_complete,
on_failure=on_error
)
# Scheduled jobs (in hooks.py)
scheduler_events = {
"daily": [
"myapp.tasks.daily_task"
],
"hourly": [
"myapp.tasks.hourly_task"
],
"cron": {
"0 0 * * *": [ # Midnight
"myapp.tasks.midnight_task"
]
}
}
Session & User
# Current user
user = frappe.session.user
# Check if logged in
if frappe.session.user != "Guest":
pass
# Check permissions
if frappe.has_permission("Customer", "write"):
pass
# Get user info
user_doc = frappe.get_doc("User", frappe.session.user)
full_name = frappe.utils.get_fullname(frappe.session.user)
# Check roles
if "System Manager" in frappe.get_roles():
pass
# Run as different user
frappe.set_user("Administrator")
# ... do operations
frappe.set_user(original_user)
JavaScript API
Document Operations
// Get document
frappe.call({
method: 'frappe.client.get',
args: {
doctype: 'Customer',
name: 'CUST-001'
},
callback: function(r) {
console.log(r.message);
}
});
// Create document
frappe.call({
method: 'frappe.client.insert',
args: {
doc: {
doctype: 'Customer',
customer_name: 'New Customer'
}
},
callback: function(r) {
console.log('Created:', r.message.name);
}
});
// Save document
frappe.call({
method: 'frappe.client.save',
args: {
doc: cur_frm.doc
}
});
// Delete document
frappe.call({
method: 'frappe.client.delete',
args: {
doctype: 'Customer',
name: 'CUST-001'
}
});
frappe.call (API Calls)
// Call whitelisted method
frappe.call({
method: 'myapp.api.get_customer_details',
args: {
customer: 'CUST-001'
},
freeze: true,
freeze_message: 'Loading...',
callback: function(r) {
if (r.message) {
console.log(r.message);
}
},
error: function(r) {
frappe.msgprint('Error occurred');
}
});
// Async/await pattern
async function getCustomer(name) {
const response = await frappe.call({
method: 'myapp.api.get_customer',
args: { name }
});
return response.message;
}
// Call with promise
frappe.call({
method: 'myapp.api.process',
args: { data: 'test' }
}).then(r => {
console.log(r.message);
});
Form API (cur_frm)
frappe.ui.form.on('Sales Invoice', {
refresh: function(frm) {
// Add custom button
frm.add_custom_button('Process', function() {
// Button action
}, 'Actions');
// Set field properties
frm.set_df_property('field_name', 'read_only', 1);
frm.set_df_property('field_name', 'hidden', 1);
frm.set_df_property('field_name', 'reqd', 1);
// Set query for link field
frm.set_query('customer', function() {
return {
filters: {
status: 'Active'
}
};
});
// Toggle fields
frm.toggle_display('field_name', frm.doc.show_field);
frm.toggle_reqd('field_name', frm.doc.is_required);
},
customer: function(frm) {
// Field change handler
if (frm.doc.customer) {
frappe.call({
method: 'myapp.api.get_customer_details',
args: { customer: frm.doc.customer },
callback: function(r) {
frm.set_value('customer_name', r.message.name);
}
});
}
},
validate: function(frm) {
// Validate before save
if (!frm.doc.customer) {
frappe.throw('Customer is required');
return false;
}
},
before_save: function(frm) {
// Before save actions
frm.doc.modified_by_script = 1;
},
after_save: function(frm) {
// After save actions
frappe.show_alert('Document saved!');
}
});
// Child table events
frappe.ui.form.on('Sales Invoice Item', {
qty: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
row.amount = row.qty * row.rate;
frm.refresh_field('items');
},
items_add: function(frm, cdt, cdn) {
// New row added
let row = locals[cdt][cdn];
row.warehouse = frm.doc.default_warehouse;
},
items_remove: function(frm) {
// Row removed - recalculate totals
calculate_totals(frm);
}
});
Dialogs
// Simple prompt
frappe.prompt(
{fieldname: 'name', fieldtype: 'Data', label: 'Name', reqd: 1},
function(values) {
console.log(values.name);
},
'Enter Name'
);
// Multiple fields
frappe.prompt([
{fieldname: 'name', fieldtype: 'Data', label: 'Name', reqd: 1},
{fieldname: 'email', fieldtype: 'Data', label: 'Email', options: 'Email'},
{fieldname: 'date', fieldtype: 'Date', label: 'Date', default: frappe.datetime.nowdate()}
], function(values) {
console.log(values);
}, 'Enter Details', 'Submit');
// Custom dialog
let dialog = new frappe.ui.Dialog({
title: 'My Dialog',
fields: [
{fieldname: 'customer', fieldtype: 'Link', options: 'Customer', label: 'Customer'},
{fieldname: 'amount', fieldtype: 'Currency', label: 'Amount'}
],
primary_action_label: 'Submit',
primary_action: function(values) {
console.log(values);
dialog.hide();
}
});
dialog.show();
// Confirmation
frappe.confirm(
'Are you sure you want to proceed?',
function() {
// Yes
process_action();
},
function() {
// No
}
);
Utilities
// Messages
frappe.msgprint('Hello World');
frappe.msgprint({
title: 'Success',
message: 'Operation completed',
indicator: 'green'
});
frappe.throw('Error message'); // Stops execution
frappe.show_alert('Quick notification', 5); // 5 seconds
// Date/time
frappe.datetime.nowdate(); // "2024-01-15"
frappe.datetime.now_datetime(); // "2024-01-15 10:30:00"
frappe.datetime.add_days('2024-01-15', 7);
frappe.datetime.str_to_obj('2024-01-15');
// Format
frappe.format(1234.56, {fieldtype: 'Currency'});
frappe.format('2024-01-15', {fieldtype: 'Date'});
// Routing
frappe.set_route('Form', 'Customer', 'CUST-001');
frappe.set_route('List', 'Customer');
frappe.set_route('query-report', 'Sales Report');
// Current route
let route = frappe.get_route();
REST API
Authentication
# Token-based
curl -X GET "https://site.com/api/resource/Customer" \
-H "Authorization: token api_key:api_secret"
# Session-based (login first)
curl -X POST "https://site.com/api/method/login" \
-d "usr=user&pwd=password"
CRUD Operations
# List
GET /api/resource/Customer?filters=[["status","=","Active"]]&fields=["name","customer_name"]&limit_page_length=10
# Get single
GET /api/resource/Customer/CUST-001
# Create
POST /api/resource/Customer
Content-Type: application/json
{"customer_name": "New Customer", "customer_type": "Company"}
# Update
PUT /api/resource/Customer/CUST-001
Content-Type: application/json
{"customer_name": "Updated Name"}
# Delete
DELETE /api/resource/Customer/CUST-001
Call Methods
# Call whitelisted method
POST /api/method/myapp.api.get_customer_details
Content-Type: application/json
{"customer": "CUST-001"}
JavaScript Fetch
// Using fetch API
async function getCustomers() {
const response = await fetch('/api/resource/Customer?limit_page_length=10', {
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
return data.data;
}
// POST request
async function createCustomer(customerData) {
const response = await fetch('/api/resource/Customer', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(customerData)
});
return response.json();
}
More from unityappsuite/frappe-claude
bench-commands
Frappe Bench CLI command reference for site management, app management, development, and production operations. Use when running bench commands, managing sites, migrations, builds, or deployments.
23client-scripts
Frappe client-side JavaScript patterns for form events, field manipulation, dialogs, and UI customization. Use when writing form scripts, handling field changes, creating dialogs, or customizing the Frappe desk interface.
11doctype-patterns
Frappe DocType creation patterns, field types, controller hooks, and data modeling best practices. Use when creating DocTypes, designing data models, adding fields, or setting up document relationships in Frappe/ERPNext.
10server-scripts
Frappe server-side Python patterns for controllers, document events, whitelisted APIs, background jobs, and database operations. Use when writing controller logic, creating APIs, handling document events, or processing data on the server.
10react-spa-patterns
React SPA patterns for Frappe-backed applications. Reference for TanStack Query, Axios, Jotai, shadcn/ui, Tailwind, Refine v4, Mantine v5, React Router, Vite config, and Frappe API integration in the Unity Parent App and Walsh Admin Portal.
2react-native-patterns
React Native / Expo patterns for Frappe-backed mobile apps. Reference for Axios API calls, React Query v4, Redux Toolkit, React Navigation, expo-location, NetworkContext, transport journey lifecycle, attendance flow, and EAS builds.
2