mcpField
F
frappe-syntax-query-builder

frappe-syntax-query-builder

github.com/impertio-studioData & databases198113 installsUpdated 30w ago
Repository

About frappe-syntax-query-builder

A usage reference for Frappe Query Builder (frappe.qb), for developers constructing database queries in Frappe. It covers query composition, data operations, and cross-database patterns for complex query scenarios.

Key features

  1. 01Build SELECT queries
  2. 02Join and aggregate queries
  3. 03Insert, update, and delete data
  4. 04Filter and sort results
  5. 05Map functions across databases

Use cases

  1. 01Write queries with JOINs
  2. 02Group and summarize database records
  3. 03Perform parameterized data changes
  4. 04Adapt queries to MariaDB and PostgreSQL

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/impertio-studio/frappe_claude_skill_package --skill frappe-syntax-query-builder

After installing, type/frappe-syntax-query-builderin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
cross-db-patterns.md
functions-reference.md
migration-from-sql.md

Details

From the project README

Frappe Query Builder (frappe.qb)

Quick Reference

from frappe.query_builder import DocType, Field
from frappe.query_builder.functions import Count, Sum, IfNull
from frappe.query_builder.custom import ConstantColumn, GROUP_CONCAT
from frappe.query_builder.terms import SubQuery
from frappe.query_builder.utils import ImportMapper, db_type_is
from pypika.terms import Case, ValueWrapper
from pypika import CustomFunction, Order
ActionPattern
SELECTfrappe.qb.from_("DocType").select("field1", "field2")
WHERE.where(Field("status") == "Open")
ORDER.orderby("creation", order=Order.desc)
LIMIT.limit(10).offset(0)
JOIN.left_join(dt2).on(dt2.name == dt1.parent)
COUNT.select(Count("*"))
INSERTfrappe.qb.into("DocType").columns("f1", "f2").insert("v1", "v2")
UPDATEfrappe.qb.update("DocType").set("field", "value").where(...)
DELETEfrappe.qb.from_("DocType").delete().where(...)
RUN.run() (tuples) / .run(as_dict=True) (dicts)

Decision Tree

Need to query the database?
│
├─ Simple get/list → frappe.db.get_value(), frappe.get_all()
│  (See frappe-core-database skill)
│
├─ Complex query with joins/aggregates/subqueries → frappe.qb ✓
│
├─ Very complex SQL not expressible in qb → frappe.db.sql()
│  (ALWAYS use parameterized values: frappe.db.sql(query, values))
│
└─ Need cross-DB compatibility → frappe.qb + ImportMapper ✓

Using frappe.qb?
│
├─ Table reference → DocType("Sales Order") — NEVER use Table()
├─ Field reference → dt.field_name or Field("field_name")
├─ Execute → .run() for tuples, .run(as_dict=True) for dicts
├─ DB-specific function → ImportMapper({db_type_is.MARIADB: X, db_type_is.POSTGRES: Y})
└─ Get SQL string → query.get_sql() — NEVER pass to frappe.db.sql()

Core Patterns

SELECT with DocType
# ALWAYS use DocType() for table references — adds "tab" prefix
so = frappe.qb.DocType("Sales Order")
soi = frappe.qb.DocType("Sales Order Item")

orders = (
    frappe.qb.from_(so)
    .select(so.name, so.customer, so.grand_total)
    .where(so.status == "To Deliver and Bill")
    .where(so.docstatus == 1)
    .orderby(so.creation, order=Order.desc)
    .limit(20)
    .run(as_dict=True)
)
JOIN
result = (
    frappe.qb.from_(so)
    .left_join(soi).on(soi.parent == so.name)
    .select(so.name, so.customer, soi.item_code, soi.qty)
    .where(so.docstatus == 1)
    .where(soi.item_code.like("ITEM-%"))
    .run(as_dict=True)
)
Aggregation
from frappe.query_builder.functions import Count, Sum

gl = frappe.qb.DocType("GL Entry")
result = (
    frappe.qb.from_(gl)
    .select(gl.account, Sum(gl.debit).as_("total_debit"), Count("*").as_("entries"))
    .where(gl.docstatus == 1)
    .groupby(gl.account)
    .run(as_dict=True)
)

# Shortcut aggregation methods
total = frappe.qb.sum("GL Entry", "debit", filters={"account": "Sales"})
max_qty = frappe.qb.max("Stock Ledger Entry", "actual_qty", filters={"item_code": "ITEM-001"})
INSERT / UPDATE / DELETE
# INSERT
frappe.qb.into("Activity Log").columns("user", "action").insert("admin", "login").run()

# UPDATE
customer = frappe.qb.DocType("Customer")
(frappe.qb.update(customer)
    .set(customer.status, "Active")
    .where(customer.name == "CUST-001")
    .run())

# DELETE
frappe.qb.from_("Error Log").delete().where(Field("creation") < "2024-01-01").run()

Filtering

dt = frappe.qb.DocType("Sales Order")

# Equality
.where(dt.status == "Open")

# OR (pipe operator)
.where((dt.status == "Open") | (dt.status == "Draft"))

# AND (chain .where() calls)
.where(dt.status == "Open")
.where(dt.docstatus == 1)

# LIKE
.where(dt.customer.like("CUST-%"))

# IN
.where(dt.status.isin(["Open", "Draft"]))

# BETWEEN (bracket syntax)
.where(dt.creation[start_date:end_date])

# NULL checks
.where(dt.email.isnotnull())
.where(dt.phone.isnull())

# Comparison
.where(dt.grand_total > 1000)
.where(dt.grand_total >= 500)
.where(dt.status != "Cancelled")

Cross-DB Compatibility

from frappe.query_builder.utils import ImportMapper, db_type_is
from frappe.query_builder.custom import GROUP_CONCAT, STRING_AGG

# ImportMapper selects correct function per database
GroupConcat = ImportMapper({
    db_type_is.MARIADB: GROUP_CONCAT,
    db_type_is.POSTGRES: STRING_AGG,
})

dt = frappe.qb.DocType("Has Role")
result = (
    frappe.qb.from_(dt)
    .select(dt.parent, GroupConcat(dt.role))
    .groupby(dt.parent)
    .run(as_dict=True)
)
MariaDBPostgreSQLUse ImportMapper
GROUP_CONCATSTRING_AGGYes
MATCH...AGAINSTTO_TSVECTORYes
LocateStrposYes
TimestampExtract-basedAuto-handled

Anti-patterns

  1. NEVER pass qb query to frappe.db.sql() — bypasses parameterization
  2. NEVER use Table() for DocTypes — use DocType() (adds tab prefix)
  3. NEVER forget .run() — without it you get a query object, not results
  4. NEVER use raw SQL strings in frappe.get_all(fields=[...]) — use dict syntax
  5. ALWAYS use ImportMapper for DB-specific functions
  6. ALWAYS chain .run(as_dict=True) when you need dicts — default is tuples

Version Differences

Featurev14v15v16
Core qb APIIntroducedYesYes
ImportMapperYesYesYes
SQLite backend----Added
Masked fields----Added
Union queries (.walk)--AddedYes
Child query execution----Added

Reference Files

Comments

Markdown supported