frappe-api

UnityAppSuite's avatarfrom UnityAppSuite

Frappe Python and JavaScript API reference including document operations, database queries, utilities, and REST API patterns. Use when working with frappe.get_doc, frappe.db, frappe.call, or any Frappe API methods.

⭐0stars🔀0forks📁View on GitHub🕐Updated Dec 26, 2025

When & Why to Use This Skill

This Claude skill serves as a comprehensive technical reference for the Frappe Framework, covering both Python and JavaScript APIs. It streamlines ERPNext and Frappe app development by providing instant access to document operations (CRUD), complex database queries via frappe.db, REST API patterns, and essential utility functions for date, number, and string manipulation. It is designed to help developers write cleaner, more efficient code for server-side logic and client-side UI interactions.

Use Cases

  • Backend Logic Implementation: Efficiently manage database records using frappe.get_doc, insert, and save methods while handling permissions and mandatory fields.
  • Client-side UI Customization: Use the JavaScript API to add custom buttons, toggle field visibility, and create interactive dialogs within Frappe forms.
  • Database Query Optimization: Write advanced select queries and raw SQL using frappe.db.get_all and frappe.db.sql for reporting and data analysis.
  • Integration & API Development: Create and consume whitelisted API endpoints and RESTful services for connecting Frappe with external applications.
  • System Utilities & Automation: Implement background jobs, scheduled tasks, and use built-in utilities for complex date arithmetic and currency formatting.
namefrappe-api
descriptionFrappe Python and JavaScript API reference including document operations, database queries, utilities, and REST API patterns. Use when working with frappe.get_doc, frappe.db, frappe.call, or any Frappe API methods.

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();
}