Django QuerySets are safe from SQL injection because the database driver escapes the parameters. Raw SQL is where it goes wrong. The rule is simple: pass user values as separate parameters, never build the SQL string with formatting or f-strings.
What is safe by default
According to the Django security docs, queries built through the ORM are parameterized. You write Person.objects.filter(last_name=user_input) and the driver handles escaping. You do not need to think about quoting.
Where the risk starts
The docs name the escape hatches: raw(), cursor.execute(), extra() and RawSQL. Use them sparingly, and always pass user-controlled values as parameters.
# Safe: parameters passed separately
Person.objects.raw("SELECT * FROM app_person WHERE last_name = %s", [user_input])
# Unsafe: string interpolation
Person.objects.raw(f"SELECT * FROM app_person WHERE last_name = '{user_input}'")
Notice that the safe form uses %s as a placeholder, not Python string formatting. The list at the end is what carries the value to the driver.
Why formatting is dangerous even when it looks harmless
An f-string pastes the value into the SQL text before the database parses it. A value containing a quote can end your string literal and add its own SQL. A parameter, by contrast, is sent as data and never parsed as SQL.
Placeholders cannot do everything
Placeholders work for values. They do not work for table names, column names or sort direction. If a user picks a sort column, map the input to a fixed allowlist in your code and put only your own constant into the SQL.
ALLOWED_SORT = {"name": "last_name", "created": "created_at"}
column = ALLOWED_SORT.get(request.GET.get("sort"), "created_at")
A review habit that catches most cases
Search the codebase for raw(, .extra(, RawSQL and cursor.execute. For each hit, ask two questions: does any value come from a request, and is it passed as a parameter. If a value is inside the SQL string, fix it.
Dependency scanning is a separate layer. A flaw in a library you did not write cannot be found by reading your own raw queries, and Safeguard's SBOM and dependency scanning covers that side. The free scan is at gold.safeguard.sh/scan.
Quick check
Is the ORM always safe from SQL injection?
Standard QuerySet use is parameterized. The risk comes from raw SQL features and from code that passes user input into them unsafely.
Can I use a placeholder for a column name?
No. Placeholders are for values. Map the input to an allowlist of column names you control.
Sources
Last reviewed: 19 October 2026.