← Back to list

erpnext-database
by OpenAEC-Foundation
28 deterministic Claude AI skills for flawless ERPNext/Frappe v14-16 development. Agent Skills standard compliant.
⭐ 1🍴 1📅 Jan 23, 2026
SKILL.md
name: erpnext-database version: 1.0.0 description: Database operations and ORM patterns for ERPNext/Frappe v14-v16 author: OpenAEC-Foundation triggers:
- frappe database
- frappe.db
- frappe.get_doc
- database query
- SQL frappe
- ORM frappe
- caching frappe
- database performance
ERPNext Database Operations
Quick Overview
Frappe provides three abstraction levels for database operations:
| Level | API | Usage |
|---|---|---|
| High-level ORM | frappe.get_doc, frappe.new_doc | Document CRUD with validations |
| Mid-level Query | frappe.db.get_list, frappe.db.get_value | Reading with filters |
| Low-level SQL | frappe.db.sql, frappe.qb | Complex queries, reports |
RULE: Always use the highest abstraction level appropriate for your use case.
Decision Tree
What do you want to do?
│
├─ Create/modify/delete document?
│ └─ frappe.get_doc() + .insert()/.save()/.delete()
│
├─ Get single document?
│ ├─ Changes frequently? → frappe.get_doc()
│ └─ Changes rarely? → frappe.get_cached_doc()
│
├─ List of documents?
│ ├─ With user permissions? → frappe.db.get_list()
│ └─ Without permissions? → frappe.get_all()
│
├─ Single field value?
│ ├─ Regular DocType → frappe.db.get_value()
│ └─ Single DocType → frappe.db.get_single_value()
│
├─ Direct update without triggers?
│ └─ frappe.db.set_value() or doc.db_set()
│
└─ Complex query with JOINs?
└─ frappe.qb (Query Builder) or frappe.db.sql()
Most Used Patterns
Get Document
# With ORM (triggers validations)
doc = frappe.get_doc('Sales Invoice', 'SINV-00001')
# Cached (faster for frequently accessed docs)
doc = frappe.get_cached_doc('Company', 'My Company')
List Query
# With user permissions
tasks = frappe.db.get_list('Task',
filters={'status': 'Open'},
fields=['name', 'subject'],
order_by='creation desc',
page_length=50
)
# Without permissions
all_tasks = frappe.get_all('Task', filters={'status': 'Open'})
Single Value
# Single field
status = frappe.db.get_value('Task', 'TASK001', 'status')
# Multiple fields
subject, status = frappe.db.get_value('Task', 'TASK001', ['subject', 'status'])
# As dict
data = frappe.db.get_value('Task', 'TASK001', ['subject', 'status'], as_dict=True)
Create Document
doc = frappe.get_doc({
'doctype': 'Task',
'subject': 'New Task',
'status': 'Open'
})
doc.insert()
Update Document
# Via ORM (with validations)
doc = frappe.get_doc('Task', 'TASK001')
doc.status = 'Completed'
doc.save()
# Direct (without validations) - use carefully!
frappe.db.set_value('Task', 'TASK001', 'status', 'Completed')
Filter Operators
{'status': 'Open'} # =
{'status': ['!=', 'Cancelled']} # !=
{'amount': ['>', 1000]} # >
{'amount': ['>=', 1000]} # >=
{'status': ['in', ['Open', 'Working']]} # IN
{'date': ['between', ['2024-01-01', '2024-12-31']]} # BETWEEN
{'subject': ['like', '%urgent%']} # LIKE
{'description': ['is', 'set']} # IS NOT NULL
{'description': ['is', 'not set']} # IS NULL
Query Builder (frappe.qb)
Task = frappe.qb.DocType('Task')
results = (
frappe.qb.from_(Task)
.select(Task.name, Task.subject)
.where(Task.status == 'Open')
.orderby(Task.creation, order='desc')
.limit(10)
).run(as_dict=True)
With JOIN
SI = frappe.qb.DocType('Sales Invoice')
Customer = frappe.qb.DocType('Customer')
results = (
frappe.qb.from_(SI)
.inner_join(Customer)
.on(SI.customer == Customer.name)
.select(SI.name, Customer.customer_name)
.where(SI.docstatus == 1)
).run(as_dict=True)
Caching
Basics
# Set/Get
frappe.cache.set_value('key', 'value')
value = frappe.cache.get_value('key')
# With expiry
frappe.cache.set_value('key', 'value', expires_in_sec=3600)
# Delete
frappe.cache.delete_value('key')
@redis_cache Decorator
from frappe.utils.caching import redis_cache
@redis_cache(ttl=300) # 5 minutes
def get_dashboard_data(user):
return expensive_calculation(user)
# Invalidate cache
get_dashboard_data.clear_cache()
Transactions
Framework manages transactions automatically:
| Context | Commit | Rollback |
|---|---|---|
| POST/PUT request | After success | On exception |
| Background job | After success | On exception |
Manual (rarely needed)
frappe.db.savepoint('my_savepoint')
try:
# operations
frappe.db.commit()
except:
frappe.db.rollback(save_point='my_savepoint')
Critical Rules
1. NEVER Use String Formatting in SQL
# ❌ SQL Injection risk!
frappe.db.sql(f"SELECT * FROM `tabUser` WHERE name = '{user_input}'")
# ✅ Parameterized
frappe.db.sql("SELECT * FROM `tabUser` WHERE name = %(name)s", {'name': user_input})
2. NEVER Commit in Controller Hooks
# ❌ WRONG
def validate(self):
frappe.db.commit() # Never do this!
# ✅ Framework handles commits
3. ALWAYS Paginate
# ✅ Always limit
docs = frappe.get_all('Sales Invoice', page_length=100)
4. Avoid N+1 Queries
# ❌ N+1 problem
for name in names:
doc = frappe.get_doc('Customer', name)
# ✅ Batch fetch
docs = frappe.get_all('Customer', filters={'name': ['in', names]})
Version Differences
| Feature | v14 | v15 | v16 |
|---|---|---|---|
| Transaction hooks | ❌ | ✅ | ✅ |
| bulk_update | ❌ | ✅ | ✅ |
| Aggregate syntax | String | String | Dict |
v16 Aggregate Syntax
# v14/v15
fields=['count(name) as count']
# v16
fields=[{'COUNT': 'name', 'as': 'count'}]
Reference Files
See the references/ folder for detailed documentation:
- methods-reference.md - All Database and Document API methods
- query-patterns.md - Filter operators and Query Builder syntax
- caching-patterns.md - Redis cache patterns and @redis_cache
- examples.md - Complete working examples
- anti-patterns.md - Common mistakes and how to avoid them
Quick Reference
| Action | Method |
|---|---|
| Get document | frappe.get_doc(doctype, name) |
| Cached document | frappe.get_cached_doc(doctype, name) |
| New document | frappe.new_doc(doctype) or frappe.get_doc({...}) |
| Save document | doc.save() |
| Insert document | doc.insert() |
| Delete document | doc.delete() or frappe.delete_doc() |
| Get list | frappe.db.get_list() / frappe.get_all() |
| Single value | frappe.db.get_value() |
| Single value | frappe.db.get_single_value() |
| Direct update | frappe.db.set_value() / doc.db_set() |
| Exists check | frappe.db.exists() |
| Count records | frappe.db.count() |
| Raw SQL | frappe.db.sql() |
| Query Builder | frappe.qb.from_() |
Score
Total Score
75/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
✓説明文
100文字以上の説明がある
+10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon

