supabase
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
erpnext-impl-whitelisted
Implementation workflows and decision trees for Frappe Whitelisted Methods (REST APIs). Use when determining HOW to implement API endpoints: public vs authenticated, permission patterns, error handling, response formats, client integration. Triggers: how do I create API, build REST endpoint, frap...
Full skill instructions
This skill helps you determine HOW to implement REST API endpoints. For exact syntax, see erpnext-syntax-whitelisted.
Version: v14/v15/v16 compatible
┌───────────────────────────────────────────────────────────────────┐
│ WHAT ARE YOU BUILDING? │
├───────────────────────────────────────────────────────────────────┤
│ │
│ ► Public API (contact forms, status checks)? │
│ └── allow_guest=True + strict input validation │
│ │
│ ► Internal API for logged-in users? │
│ └── Default (no allow_guest) + permission checks │
│ │
│ ► Admin-only API? │
│ └── frappe.only_for("System Manager") │
│ │
│ ► Document-specific method (on a form)? │
│ └── Controller method + frm.call() │
│ │
│ ► Standalone utility API? │
│ └── Separate api.py + frappe.call() │
│ │
└───────────────────────────────────────────────────────────────────┘
→ See references/decision-tree.md for complete guide.
WHERE SHOULD YOUR API LIVE?
│
├─► Related to a specific DocType?
│ │
│ ├─► Called from that DocType's form?
│ │ └─► Controller method (doctype/xxx/xxx.py)
│ │ Client: frm.call('method_name', args)
│ │
│ └─► Standalone but DocType-related?
│ └─► Same file or doctype/xxx/xxx_api.py
│ Client: frappe.call('path.to.method', args)
│
├─► General utility API?
│ └─► myapp/api.py or myapp/api/module.py
│ Client: frappe.call('myapp.api.method', args)
│
└─► External integration?
└─► myapp/integrations/service_name.py
Often combined with webhooks
WHO CAN ACCESS THIS API?
│
├─► Anyone (public)?
│ └─► allow_guest=True
│ ⚠️ MUST validate all input
│ ⚠️ MUST rate limit if possible
│ ⚠️ NEVER expose sensitive data
│
├─► Any logged-in user?
│ └─► Default (no allow_guest)
│ Still check document permissions!
│
├─► Specific role(s)?
│ └─► frappe.only_for("Role") or frappe.only_for(["Role1", "Role2"])
│ Throws PermissionError if user lacks role
│
├─► Document-level permission?
│ └─► frappe.has_permission(doctype, ptype, doc)
│ Check before accessing each document
│
└─► Custom permission logic?
└─► Implement your own checks
Always deny by default
# myapp/api.py
import frappe
from frappe import _
@frappe.whitelist()
def get_customer_balance(customer):
"""Get customer's outstanding balance."""
# Permission check
if not frappe.has_permission("Customer", "read", customer):
frappe.throw(_("Not permitted"), frappe.PermissionError)
# Fetch data
balance = frappe.db.get_value("Customer", customer, "outstanding_amount")
return {"customer": customer, "balance": balance or 0}
// Client call
frappe.call({
method: 'myapp.api.get_customer_balance',
args: { customer: 'CUST-00001' }
}).then(r => {
console.log(r.message.balance);
});
@frappe.whitelist(allow_guest=True, methods=["POST"])
def submit_inquiry(name, email, message):
"""Public contact form - strict validation required."""
# Validate required fields
if not all([name, email, message]):
frappe.throw(_("All fields are required"))
# Validate email format
if not frappe.utils.validate_email_address(email):
frappe.throw(_("Invalid email address"))
# Sanitize input
name = frappe.utils.strip_html(name)[:100]
message = frappe.utils.strip_html(message)[:2000]
# Create record
doc = frappe.get_doc({
"doctype": "Lead",
"lead_name": name,
"email_id": email,
"notes": message,
"source": "Website"
})
doc.insert(ignore_permissions=True)
return {"success": True, "id": doc.name}
@frappe.whitelist()
def get_salary_data(employee):
"""HR-only endpoint."""
# Role check - throws if not HR
frappe.only_for(["HR Manager", "HR User"])
return frappe.get_doc("Employee", employee).as_dict()
# In doctype/sales_order/sales_order.py
class SalesOrder(Document):
@frappe.whitelist()
def calculate_shipping(self, carrier):
"""Called via frm.call() from form."""
# Permission already checked by Frappe for doc access
rate = get_shipping_rate(self.shipping_address, carrier)
return {"carrier": carrier, "rate": rate}
// Client (in sales_order.js)
frm.call('calculate_shipping', {
carrier: 'FedEx'
}).then(r => {
frm.set_value('shipping_amount', r.message.rate);
});
→ See references/workflows.md for 10+ complete workflows.
# ❌ WRONG - exposes all data
@frappe.whitelist()
def get_document(doctype, name):
return frappe.get_doc(doctype, name).as_dict()
# ✅ CORRECT
@frappe.whitelist()
def get_document(doctype, name):
if not frappe.has_permission(doctype, "read", name):
frappe.throw(_("Not permitted"), frappe.PermissionError)
return frappe.get_doc(doctype, name).as_dict()
# ❌ WRONG - SQL injection!
@frappe.whitelist()
def search(term):
return frappe.db.sql(f"SELECT * FROM tabItem WHERE name LIKE '%{term}%'")
# ✅ CORRECT - parameterized
@frappe.whitelist()
def search(term):
return frappe.db.sql("""
SELECT name, item_name FROM tabItem
WHERE name LIKE %(term)s
LIMIT 20
""", {"term": f"%{term}%"}, as_dict=True)
@frappe.whitelist(allow_guest=True)
def public_api(data):
# ❌ WRONG - trusts input
doc = frappe.get_doc(data)
doc.insert(ignore_permissions=True)
# ✅ CORRECT - validate everything
if not isinstance(data, dict):
frappe.throw(_("Invalid data format"))
allowed_fields = {"name", "email", "message"}
clean_data = {k: v for k, v in data.items() if k in allowed_fields}
# Validate each field...
# ❌ WRONG - leaks internal info
except Exception as e:
frappe.throw(str(e))
# ✅ CORRECT - generic message, log details
except Exception:
frappe.log_error(frappe.get_traceback(), "API Error")
frappe.throw(_("An error occurred. Please try again."))
# ❌ WRONG - bypasses all security
@frappe.whitelist()
def get_all_data():
return frappe.get_all("Salary Slip", ignore_permissions=True)
# ✅ CORRECT - check role first
@frappe.whitelist()
def get_all_data():
frappe.only_for("HR Manager") # Verify role first!
return frappe.get_all("Salary Slip", ignore_permissions=True)
@frappe.whitelist()
def robust_api(param):
"""API with proper error handling."""
try:
# Validate input
if not param:
frappe.throw(_("Parameter required"), frappe.ValidationError)
# Check permissions
if not frappe.has_permission("MyDocType", "read"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
# Process
result = do_something(param)
return {"success": True, "data": result}
except frappe.ValidationError:
raise # Let Frappe handle (417)
except frappe.PermissionError:
raise # Let Frappe handle (403)
except frappe.DoesNotExistError:
frappe.local.response["http_status_code"] = 404
return {"success": False, "error": "Not found"}
except Exception:
frappe.log_error(frappe.get_traceback(), "API Error")
frappe.local.response["http_status_code"] = 500
return {"success": False, "error": "Internal error"}
| Code | Exception | When to Use |
|---|---|---|
| 200 | - | Success |
| 201 | - | Created (set manually) |
| 400 | - | Bad request (set manually) |
| 401 | AuthenticationError | Not logged in |
| 403 | PermissionError | Access denied |
| 404 | DoesNotExistError | Not found |
| 417 | ValidationError | Validation failed |
| 409 | DuplicateEntryError | Duplicate |
| 500 | Exception | Server error |
@frappe.whitelist()
def get_data():
return {"key": "value"}
# Response: {"message": {"key": "value"}}
@frappe.whitelist()
def get_items():
return frappe.get_all("Item", fields=["name", "item_name"], limit=10)
# Response: {"message": [{"name": "...", "item_name": "..."}, ...]}
@frappe.whitelist()
def get_paged_data(page=1, page_size=20):
offset = (int(page) - 1) * int(page_size)
total = frappe.db.count("Item")
items = frappe.get_all("Item", limit=page_size, start=offset)
return {
"data": items,
"total": total,
"page": page,
"page_size": page_size,
"pages": (total + page_size - 1) // page_size
}
frappe.call({
method: 'myapp.api.my_method',
args: { param1: 'value' },
// UI Options
freeze: true, // Show loading overlay
freeze_message: __('Loading...'), // Custom message
// Callbacks
callback: function(r) {
if (r.message) { /* success */ }
},
error: function(r) {
// Handle error
},
always: function() {
// Always runs (finally)
},
// Other
async: true, // Default true
type: 'POST' // Default POST
});
async function fetchData() {
try {
const r = await frappe.call({
method: 'myapp.api.get_data',
args: { id: 123 }
});
return r.message;
} catch (e) {
frappe.msgprint(__('Error loading data'));
console.error(e);
}
}
| File | Contents |
|---|---|
| decision-tree.md | Complete API type selection guide |
| workflows.md | Step-by-step implementation patterns |
| examples.md | Complete working examples |
| anti-patterns.md | Common mistakes to avoid |
| Feature | v14 | v15 | v16 |
|---|---|---|---|
| @frappe.whitelist() | ✅ | ✅ | ✅ |
| allow_guest | ✅ | ✅ | ✅ |
| methods parameter | ✅ | ✅ | ✅ |
| Type annotation validation | ❌ | ✅ | ✅ |
| Rate limiting decorator | ❌ | ✅ | ✅ |
| API v2 endpoints | ❌ | ✅ | ✅ |
# v15+ validates types automatically
@frappe.whitelist()
def typed_api(customer: str, limit: int = 10) -> dict:
return {"customer": customer, "limit": limit}
from frappe.rate_limiter import rate_limit
@frappe.whitelist(allow_guest=True)
@rate_limit(limit=5, seconds=60) # 5 calls per minute
def rate_limited_api():
return {"status": "ok"}
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
Comprehensive guides and best practices for Neon Serverless Postgres, covering setup, connection methods, authentication, and platform APIs.
Guide for setting up and using Firebase Authentication. Use this skill when the user's app requires user sign-in, user management, or secure data access using auth rules.
A skill to evaluate how secure Firestore security rules are. Use this when Firestore security rules are updated to ensure that the generated rules are extremely secure and robust.
Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security.
Complete Better Auth server and client setup with database adapters, session management, plugins, and security configuration.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Complete Cloudflare platform integration with decision trees for compute, storage, AI, networking, security, and infrastructure-as-code.
Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.
Send, read, and manage Gmail messages, drafts, labels, and account settings.
Comprehensive website auditing across 230+ rules in 21 categories including SEO, performance, security, and accessibility.
Shared authentication, CLI syntax, and output formatting patterns for gws Google Workspace commands.
Discover your celebrity doppelgänger with StarByFace!
GeoSpy: Pricing, Features, FAQs, and Alternatives for AI Teams
Detect AI-generated voices to protect against audio fraud.
Ensure Your Content's Originality with AI Plagiarism Checker
Upscale images by 400% without quality loss
Protect your social media from copyright disputes
Accurately Detect AI-Generated Content with TheChecker.AI
Chrome extension that detects and flags AI-generated content