Claude Code Plugins

Community-maintained marketplace

Feedback

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name frappe-api
description 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.

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