$npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-core-databaseUse when performing database operations in ERPNext/Frappe v14-v16. Covers frappe.db methods, ORM patterns (frappe.get_doc, frappe.get_list), raw SQL, caching patterns, and performance optimization. Prevents common mistakes with database transactions and query building. Keywords:
| 1 | # Frappe Database Operations |
| 2 | |
| 3 | ## Quick Reference |
| 4 | |
| 5 | | Action | Method | Permissions | |
| 6 | |--------|--------|-------------| |
| 7 | | Get document | `frappe.get_doc(doctype, name)` | Yes | |
| 8 | | Cached document | `frappe.get_cached_doc(doctype, name)` | No | |
| 9 | | New document | `frappe.new_doc(doctype)` | — | |
| 10 | | Insert | `doc.insert()` | Yes | |
| 11 | | Save | `doc.save()` | Yes | |
| 12 | | Delete document | `frappe.delete_doc(doctype, name)` | Yes | |
| 13 | | List (with perms) | `frappe.db.get_list(doctype, ...)` | Yes | |
| 14 | | List (no perms) | `frappe.get_all(doctype, ...)` | No | |
| 15 | | Single field | `frappe.db.get_value(doctype, name, field)` | No | |
| 16 | | Single DocType | `frappe.db.get_single_value(doctype, field)` | No | |
| 17 | | Cached value | `frappe.db.get_value(..., cache=True)` | No | |
| 18 | | Direct update | `frappe.db.set_value(doctype, name, field, val)` | No | |
| 19 | | Direct update | `doc.db_set(field, value)` | No | |
| 20 | | Exists check | `frappe.db.exists(doctype, name)` | No | |
| 21 | | Count | `frappe.db.count(doctype, filters)` | No | |
| 22 | | Delete rows | `frappe.db.delete(doctype, filters)` | No | |
| 23 | | Raw SQL | `frappe.db.sql(query, values, as_dict)` | No | |
| 24 | | Query Builder | `frappe.qb.from_(doctype).select(...)` | No | |
| 25 | |
| 26 | > **"Permissions" = Yes** means user permission filters are applied automatically. |
| 27 | |
| 28 | --- |
| 29 | |
| 30 | ## Decision Tree |
| 31 | |
| 32 | ``` |
| 33 | What do you need? |
| 34 | │ |
| 35 | ├─ Create / Update / Delete a document? |
| 36 | │ ├─ With validations + hooks → frappe.get_doc() + .insert()/.save()/.delete() |
| 37 | │ └─ Direct DB (no hooks) → frappe.db.set_value() or doc.db_set() |
| 38 | │ |
| 39 | ├─ Read a single document? |
| 40 | │ ├─ Need full object with methods → frappe.get_doc() |
| 41 | │ ├─ Read-only, rarely changes → frappe.get_cached_doc() |
| 42 | │ └─ Only need 1-2 fields → frappe.db.get_value() |
| 43 | │ |
| 44 | ├─ List of documents? |
| 45 | │ ├─ Respect user permissions → frappe.db.get_list() |
| 46 | │ └─ System/admin context → frappe.get_all() |
| 47 | │ |
| 48 | ├─ Single DocType value? |
| 49 | │ └─ frappe.db.get_single_value('Settings', 'field') |
| 50 | │ |
| 51 | ├─ Check existence? |
| 52 | │ └─ frappe.db.exists() — NEVER use get_doc in try/except |
| 53 | │ |
| 54 | ├─ Complex query (JOINs, aggregates)? |
| 55 | │ ├─ Cross-DB compatible → frappe.qb (Query Builder) |
| 56 | │ └─ DB-specific SQL → frappe.db.sql() with parameters |
| 57 | │ |
| 58 | └─ DB-specific logic? |
| 59 | └─ frappe.db.multisql({'mariadb': q1, 'postgres': q2}) |
| 60 | ``` |
| 61 | |
| 62 | **RULE**: ALWAYS use the highest abstraction level: ORM > Database API > Query Builder > Raw SQL. |
| 63 | |
| 64 | --- |
| 65 | |
| 66 | ## ORM: Document Operations |
| 67 | |
| 68 | ### Get Document |
| 69 | ```python |
| 70 | doc = frappe.get_doc('Sales Invoice', 'SINV-00001') |
| 71 | |
| 72 | # Single DocType (no name needed) |
| 73 | settings = frappe.get_doc('System Settings') |
| 74 | |
| 75 | # Cached (read-only, for rarely-changing docs) |
| 76 | company = frappe.get_cached_doc('Company', 'My Company') |
| 77 | |
| 78 | # Last created |
| 79 | last_task = frappe.get_last_doc('Task', filters={'status': 'Open'}) |
| 80 | ``` |
| 81 | |
| 82 | ### Create Document |
| 83 | ```python |
| 84 | doc = frappe.get_doc({ |
| 85 | 'doctype': 'Task', |
| 86 | 'subject': 'Review report', |
| 87 | 'status': 'Open' |
| 88 | }) |
| 89 | doc.insert() |
| 90 | |
| 91 | # Alternative |
| 92 | doc = frappe.new_doc('Task') |
| 93 | doc.subject = 'Review report' |
| 94 | doc.insert() |
| 95 | ``` |
| 96 | |
| 97 | ### Update Document |
| 98 | ```python |
| 99 | # Via ORM — triggers validate, on_update, etc. |
| 100 | doc = frappe.get_doc('Task', 'TASK-001') |
| 101 | doc.status = 'Completed' |
| 102 | doc.save() |
| 103 | |
| 104 | # Direct DB — SKIPS all validations and hooks |
| 105 | frappe.db.set_value('Task', 'TASK-001', 'status', 'Completed') |
| 106 | |
| 107 | # Direct DB on loaded doc |
| 108 | doc.db_set('status', 'Completed') |
| 109 | doc.db_set('status', 'Completed', update_modified=False) |
| 110 | doc.db_set({'status': 'Completed', 'priority': 'High'}) |
| 111 | ``` |
| 112 | |
| 113 | ### Delete Document |
| 114 | ```python |
| 115 | frappe.delete_doc('Task', 'TASK-001') |
| 116 | # Also removes linked Communications, Comments, etc. |
| 117 | ``` |
| 118 | |
| 119 | ### Insert Flags |
| 120 | ```python |
| 121 | doc.insert( |
| 122 | ignore_permissions=True, # Bypass permission check |
| 123 | ignore_links=True, # Skip link validation |
| 124 | ignore_if_duplicate=True, # No error on duplicate |
| 125 | ignore_mandatory=True # Skip required field check |
| 126 | ) |
| 127 | ``` |
| 128 | |
| 129 | > **RULE**: NEVER use multiple ignore flags together unless you have a documented reason. Each flag you add weakens data integrity. |
| 130 | |
| 131 | --- |
| 132 | |
| 133 | ## Database API: Reading |
| 134 | |
| 135 | ### get_value |
| 136 | ```python |
| 137 | # Single field → scalar |
| 138 | status = frappe.db.get_value('Task', 'TASK-001', 'status') |
| 139 | |
| 140 | # Multiple fields → tuple |
| 141 | subject, status = frappe.db.get_value('Task', 'TASK-001', ['subject', 'status']) |
| 142 | |
| 143 | # As dict |
| 144 | data = frappe.db.get_value('Task', 'TASK-001', ['subject', 'status'], as_dict=True) |
| 145 | |
| 146 | # With filters instead of name |
| 147 | status = frappe.db.get_value('Task', {'project': ' |