How to Connect Claude API to a Postgres Database
There's no native "Postgres connector" button in Claude's API — you connect the two by writing a small integration layer in your application. Claude doesn't talk to your database directly; your backend code sits in between, executing SQL queries and feeding the results to Claude as context or tool output. The two common patterns are context injection (query Postgres first, then pass the data to Claude in the prompt) and tool use / function calling (let Claude decide when it needs data and have your code run the query on its behalf).
This article walks through both patterns with working code, covers the security rules you should never skip, and shows where a wrapper API like SubToAPI fits if you want the Claude side of this exposed as a clean HTTP endpoint for your team or product.
Pattern 1: Context injection (simple RAG-style queries)
This is the right approach when you already know what data Claude needs before the request — for example, "summarize this user's last 10 orders."
import pg from "pg";
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
async function getOrderSummary(userId) {
const { rows } = await pool.query(
"SELECT id, total, created_at FROM orders WHERE user_id = $1 ORDER BY created_at DESC LIMIT 10",
[userId]
);
return rows;
}
async function summarizeOrders(userId) {
const orders = await getOrderSummary(userId);
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 500,
messages: [
{
role: "user",
content: `Summarize this order history in 3 sentences:\n${JSON.stringify(orders)}`,
},
],
}),
});
return response.json();
}
This keeps the database access entirely under your control — Claude only ever sees the rows you decide to send. It's the safest pattern and sufficient for most dashboards, reporting tools, and support assistants.
Pattern 2: Tool use (let Claude decide what to query)
Tool use is better when you don't know in advance what the user will ask — a natural-language query interface over your database, for instance. You define a tool Claude can call, and your code is the one actually running SQL.
const tools = [
{
name: "query_database",
description: "Run a read-only SQL query against the orders and customers tables",
input_schema: {
type: "object",
properties: {
sql: { type: "string", description: "A SELECT-only SQL query" },
},
required: ["sql"],
},
},
];
When Claude responds with a tool_use block containing the SQL it wants to run, your server executes it and sends the result back as a tool_result in the next message. Claude then writes the final answer using that data.
if (toolUseBlock.name === "query_database") {
const sql = toolUseBlock.input.sql;
if (!/^\s*select/i.test(sql)) {
throw new Error("Only SELECT statements are allowed");
}
const { rows } = await pool.query(sql);
// send rows back as a tool_result message
}
If you're building this with SubToAPI instead of raw Anthropic credentials, the request/response shape for tool use is the same — see the tools documentation for the exact payload format and streaming behavior when a tool call happens mid-response.
Securing the database side
This is the part most people skip and later regret. Giving a language model the ability to generate SQL that touches your production database is a real attack surface, not a theoretical one.
- Use a dedicated read-only Postgres role. Never let Claude's tool calls run against a connection with write permissions.
- Allowlist tables and columns, not just "SELECT only." A read-only user that can
SELECT * FROM usersincluding password hashes is still a leak. - Set a statement timeout and row limit (
SET statement_timeout = '2s'andLIMIT 100enforced server-side) so a bad query can't hang your connection pool. - Never string-concatenate Claude's output into a query you execute directly without validation — even though Claude isn't a malicious actor, treat generated SQL exactly like untrusted user input.
- Log every query Claude generates so you can audit what it asked for, independent of what it returned.
A safer middle ground than freeform SQL generation is giving Claude a small set of parameterized tools instead of raw SQL access — get_customer_orders(customer_id), search_products(query), etc. This trades flexibility for a much smaller blast radius, and for most production apps it's the better trade.
const tools = [
{
name: "search_products",
description: "Search products by name or category",
input_schema: {
type: "object",
properties: {
query: { type: "string" },
limit: { type: "integer", default: 20 },
},
required: ["query"],
},
},
];
Your backend maps this to a fixed, parameterized query — Claude never sees or generates raw SQL at all.
Where connection pooling matters
If this integration runs in a serverless function (Vercel, Lambda, Cloudflare Workers), don't open a new Postgres connection per request. Use a pooler like PgBouncer or a managed pooled connection string (Supabase, Neon, and RDS Proxy all offer this), otherwise you'll exhaust connections under load — especially once Claude's tool-use loop means a single user request can trigger multiple round trips.
Exposing this as an API for your team
Once the Claude-to-Postgres logic works locally, most teams end up wrapping it behind an internal API anyway — one key per environment, usage tracking, and a stable endpoint other services can call. If you don't want to build and maintain that layer yourself, SubToAPI turns your existing Claude access into an HTTPS API with sub_live_... application keys, streaming, tool use, and per-key usage metadata, so your Postgres integration code only has to make one authenticated call. Check the quickstart and messages API reference to see the request format, and pricing for plan details — there's a free trial at signup if you want to test it against your own schema first.
questions
Can Claude query my Postgres database directly, without any backend code? No. Claude's API only receives and returns text and structured JSON — it has no network access to your database. Every integration requires your own code to run the actual SQL and pass results back to Claude.
Is it safe to let Claude generate and run arbitrary SQL? Only with strict guardrails: a read-only database role, SELECT-only validation, statement timeouts, and row limits. For most production systems, exposing a small set of parameterized tools instead of raw SQL generation is safer and easier to audit.
Should I use tool use or just inject query results into the prompt? Use context injection when you already know what data is needed for a given request. Use tool use when the user's question is open-ended and Claude needs to decide what to look up — a natural-language query interface, for example.