
Secure SQL Queries in Frappe That Respect Permissions
Secure SQL Queries in Frappe That Respect Permissions
Performance & security — without the tradeoff.
When making reports or fetching data in general, secure SQL queries in Frappe are often the first solution that comes to mind.
Why SQL?
Database engines use C/C++, which makes them highly optimized for filtering and joining data. That’s why joining and filtering is much faster in SQL than doing it in Python.
Combined with database indexing, this makes SQL an entirely different beast.
SQL is the universal language of data. Most developers already know SQL, making it simpler for existing devs to write the code and for new devs to understand it.
The Problem
In Frappe, there is a major issue in using SQL — it doesn’t know who is logged in.
SQL fetches all the data, irrespective of user/role permissions. It ignores:
✕ User Permissions — e.g., “User A can only see Territory B”
✕ Role Permissions — e.g., “Sales Users cannot read Purchase Receipts”
This makes it unreliable in many cases.
The Frappe Alternatives
Frappe provides tools for this — let’s look at them first.
1. get_list (docs)
Takes the name of a DocType, a list of filters, and fetches all matching entries. Works well for single-DocType fetches.
But when you need joins (e.g. customers connected to a department), it falls short — you must fetch data into Python, creating a bottleneck.
2. Query Builder
Although some sources claim it handles permissions, in practice it is often unreliable.
The Solution: Secure SQL Queries
We don’t need to choose between security and efficiency. Now we can have both.
We can add user permissions to the SQL query itself as filters using the function build_match_conditions from frappe.desk.reportview.
This is the same method Frappe uses internally in get_list.
How to Implement
① Import the helper
from frappe.desk.reportview \
import build_match_conditions
② Fetch the permissions
permission_sql = build_match_conditions(
"Sales Order"
)
Example output
"(`tabSales Order`.customer = 'Cust-001' OR `tabSales Order`.owner = 'user@example.com')"
③ Handle aliases CRUCIAL
The system returns a string with the full table name. If your SQL uses an alias, replace it to avoid DB errors.
permission_sql = permission_sql.replace(
"`tabSales Order`",
"so"
)
④ Inject into your query
if permission_sql:
where_clause += \
f" AND {permission_sql}"
# Empty = user has all perms
# — prevents syntax error
data = frappe.db.sql(f"""
SELECT
so.name,
so.grand_total
FROM `tabSales Order` so
WHERE
so.docstatus = 1
AND {permission_sql}
""", as_dict=True)
This query now only returns records the user can access.
Technical Details
⚠ Permission Error
If the user lacks access to a DocType, the function raises a PermissionError. Handle it:
try:
permission_sql = \
build_match_conditions(
"Purchase Order"
)
except frappe.PermissionError:
# No read access
permission_sql = "1=0"
Empty string: If no restrictions exist, the function returns an empty string. Handle this to avoid SQL syntax errors.
One DocType at a time: Call the function for each DocType individually and combine the conditions.
Reusable Helper Function
A utility that handles multiple DocTypes at once:
import frappe
from frappe.desk.reportview import \
build_match_conditions
def get_permission_filters(
doctype_map
):
"""
SQL conditions for multiple
DocTypes.
Args:
doctype_map (dict):
Key = DocType
Value = Alias
{"Sales Order": "so"}
Returns:
list of SQL conditions.
Denied -> ["1=0"]
"""
conditions = []
for dt, alias in \
doctype_map.items():
try:
rule = \
build_match_conditions(dt)
if rule:
if alias:
rule = rule.replace(
f"`tab{dt}`",
alias
)
conditions.append(
f"({rule})"
)
except frappe.PermissionError:
return ["1=0"]
return conditions
Related content
Auriga: Leveling Up for Enterprise Growth!
By ronak|2024-07-03T13:37:59+05:303 July 2024|Categories: Uncategorized|
Auriga’s journey began in 2010 crafting products for India’s [...]
Stay Close to What We’re Building
Get insights on product engineering, AI, and real-world technology decisions shaping modern businesses.






