frappe-dev-debugger
Frappe Dev Debugger
All commands run from the bench directory (/workspace/development/frappe-bench).
Default site: development.localhost.
1. Query documents (bench execute)
Query a single doc (returns JSON printed to stdout):
bench --site development.localhost execute frappe.get_doc \
--args '["DocType", "docname"]' | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('fieldname'))"
Query a list (frappe.get_all):
bench --site development.localhost execute frappe.get_all \
--args '["DocType", {"filters": {"status": "Active"}, "fields": ["name", "field1"], "limit": 5}]'
Limitation:
bench executeonly works with top-levelfrappe.*functions and simple positional args. For anything more complex (multi-step logic, child tables, file reads) use bench console instead.
2. Read stored .json.gz files (disk)
Frappe stores report/snapshot data as .json.gz files under sites/{site}/private/files/ or sites/{site}/public/files/.
Discover the file URL from the document first (step 1), then read it:
# Read any .json.gz from disk directly
import gzip, json
site = "sites/development.localhost"
path = site + "/private/files/your_file.json.gz" # swap private/public as needed
with gzip.open(path, "rb") as f:
content = json.loads(f.read())
print(list(content.keys())) # inspect top-level structure
Inspect top-level structure first, then drill in:
print(list(content.keys())) # e.g. ['result', 'columns'] or {'data': [...], 'meta': {...}}
rows = content.get("result") or content.get("data") or []
print(f"{len(rows)} rows")
print(rows[0] if rows else "empty")
Run this as a one-liner from terminal:
cd /workspace/development/frappe-bench && python3 - <<'EOF'
import gzip, json
with gzip.open("sites/development.localhost/private/files/YOUR_FILE.json.gz", "rb") as f:
c = json.loads(f.read())
print(json.dumps(c, indent=2, default=str)[:3000])
EOF
3. Run multi-step scripts (bench console)
Use bench console for anything needing full Frappe context: doc loads, multiple queries, method calls, file reads + processing.
bench --site development.localhost console << 'EOF'
# frappe is pre-imported; all installed apps are available
doc = frappe.get_doc("Purchase Order", "PUR-0001")
print(doc.status, doc.supplier)
# call any imported function
from myapp.module.utils import my_function
result = my_function(doc)
print(result)
EOF
Tip:
bench consoleruns with full app context — all doctypes, hooks, and installed-app modules are importable. Nofrappe.init()/frappe.connect()needed.
4. Call whitelisted methods
Whitelisted methods (@frappe.whitelist()) can be called from console the same way client code would call them:
bench --site development.localhost console << 'EOF'
# Call a whitelisted method directly — no HTTP needed
from myapp.mymodule.doctype.mydoctype.mydoctype import my_whitelisted_fn
doc = frappe.get_doc("MyDoctype", "DOC-0001")
my_whitelisted_fn(doc)
frappe.db.commit()
EOF
Or trigger a method defined directly on the document:
bench --site development.localhost console << 'EOF'
doc = frappe.get_doc("MyDoctype", "DOC-0001")
doc.my_method()
frappe.db.commit()
EOF
5. Raw SQL queries
bench --site development.localhost console << 'EOF'
rows = frappe.db.sql("""
SELECT name, status, owner
FROM `tabPurchase Order`
WHERE supplier = 'Supplier A'
ORDER BY modified DESC
LIMIT 10
""", as_dict=True)
for r in rows:
print(r)
EOF
Or use the query builder:
bench --site development.localhost console << 'EOF'
from frappe.query_builder import DocType
T = DocType("Purchase Order")
rows = frappe.qb.from_(T).select(T.name, T.status).where(T.supplier == "Supplier A").run(as_dict=True)
print(rows)
EOF
6. Locate files on disk
# Find all files attached to a specific document
bench --site development.localhost console << 'EOF'
files = frappe.get_all("File", filters={
"attached_to_doctype": "Purchase Order",
"attached_to_name": "PUR-0001",
}, fields=["name", "file_name", "file_url", "attached_to_field"])
for f in files:
print(f.attached_to_field, "->", f.file_url)
EOF
Map file_url → disk path:
/private/files/foo.gz→sites/development.localhost/private/files/foo.gz/files/foo.gz→sites/development.localhost/public/files/foo.gz
7. Debugging checklist
- What document? — get the
name(ID) first. - Which fields matter? —
bench execute frappe.get_docand inspect field values. - Any attached files? — list via
frappe.get_all("File", ...), then read from disk. - Is a method producing wrong output? — call it directly in
bench console,print()intermediate values. - Database state correct? — raw SQL or
frappe.db.get_valueto verify stored values. - Commit or rollback? — add
frappe.db.commit()explicitly when mutations must persist; omit to leave DB unchanged.
More from kehwar/skills
to-prd
Turn the current conversation context into a PRD and publish it to Beads Issue Tracker. Use when user wants to create a PRD from the current context.
11setup-workflow-skills
Sets up an `## Agent orientation` block in AGENTS.md/CLAUDE.md so the engineering skills know this repo uses Beads for issue tracking. Run before first use of `to-tasks`, `to-prd`, `tdd`, `improve-codebase-architecture`, or `zoom-out`.
11tdd
Test-driven development with red-green-refactor loop. Use when user wants to build features or fix bugs using TDD, mentions "red-green-refactor", wants integration tests, or asks for test-first development. Tracks progress in Beads Issue Tracker.
10write-a-skill
Create new agent skills with proper structure, progressive disclosure, and bundled resources. Use when user wants to create, write, or build a new skill.
9to-tasks
Break a plan, spec, or PRD into independently-grabbable tasks/issues on Beads Issue Tracker using tracer-bullet vertical slices. Use when user wants to convert a plan into tasks, create implementation tickets, or break down work into tasks.
9grill-me
Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me".
8