Prisma's queryRaw, used with a template tag and no string building, passes values as parameters and is safe for ordinary input. queryRawUnsafe takes a raw string, so any user input concatenated into it can inject SQL. The Prisma docs say the Unsafe methods carry significant risk of making your code vulnerable.
The four raw methods
Prisma Client exposes four methods for raw SQL. The documentation lists what each returns and flags the difference between them.
| Method | Returns | Safety note |
|---|---|---|
| queryRaw | Records | Safe with a template tag, no concatenation |
| executeRaw | Count of affected rows | Same as queryRaw |
| queryRawUnsafe | Records, from a raw string | Significant injection risk with user input |
| executeRawUnsafe | Affected rows, from a raw string | Same risk |
The docs say the safe pair is safe to use with a simple template tag, no string building, and no concatenation. They also warn that more complex use cases can still introduce injection if used in certain ways, so the safe name is not a blanket guarantee.
Safe and unsafe, side by side
// Values are sent as parameters
const rows = await prisma.$queryRaw`SELECT * FROM "User" WHERE email = ${email}`;
// The string is built first, so email can change the SQL
const bad = await prisma.$queryRawUnsafe(`SELECT * FROM "User" WHERE email = '${email}'`);
In the first call the template tag turns each interpolated value into a parameter. In the second the value is already part of the SQL text before Prisma sees it.
The dynamic table name problem
The documentation notes that you cannot interpolate table names in queryRaw on PostgreSQL. Its suggested route is queryRawUnsafe, followed by a warning that using it with user input risks injection. That is the situation where people reach for the unsafe method for a legitimate reason.
The fix is to never let the user supply the identifier. Map a fixed choice to a known name:
const tables = { users: '"User"', teams: '"Team"' } as const;
const key = req.query.entity as keyof typeof tables;
if (!(key in tables)) throw new Error('unknown entity');
const rows = await prisma.$queryRawUnsafe(`SELECT count(*) FROM ${tables[key]}`);
The user chooses a key. Your code chooses the SQL. Values still go through parameters in any query that takes them.
A review checklist
- Search the codebase for the two Unsafe method names.
- For each hit, ask whether any argument comes from a request, a file or another service.
- If yes, rewrite with the template tag, or use an allowlist for identifiers.
- Add a lint or search rule in CI so new uses are noticed.
- Remember the docs note that each call runs a single query. That limits stacked statements but does not stop injection inside one.
See our wider post on ORM calls that bypass parameter binding for the same pattern in other libraries.
Safeguard scans dependencies, including ORMs and drivers, and policy gates can hold a pull request when a check fails. A free scan is at gold.safeguard.sh/scan.
Try it yourself
Run the code scan and look at how a raw query call is flagged when input reaches it. Compare that with a call that passes values as parameters.
Click through it at your own pace, or open it full screen.
Quick check
Is queryRaw always safe?
Prisma says it is safe with a simple template tag and no string building. More complex uses can still introduce injection.
Can I run two statements in one call?
The docs say each raw method runs one query at a time.
How do I handle a dynamic ORDER BY or table?
Map a fixed choice to a known identifier in your code. Do not pass user text into the SQL.
Sources
Last reviewed: 19 October 2026.