Technical guides, client SDK references, and runnable recipes for all 8 subsystems: functions, containers, PostgreSQL, auth, storage, transactional mail, realtime, and the MCP server for AI agents.
Quickstarts for TypeScript, Python and Go, type generation, MCP setup for AI agents, and production recipes.
A PGB Hub project bundles a PostgreSQL database with functions, containers, end-user auth, object storage, transactional mail, realtime channels and an MCP server for AI agents. Project services answer on https://<project>.pgbhub.app; account-level APIs answer on https://console.pgbhub.com.
# Package names for each SDK. They are not on the public registries yet:
# these commands start working with each SDK's first published release.
npm install @pgb/sdk # TypeScript / Node.js 18+ (also installs the pgb CLI)
pip install pgbhub # Python (Pydantic v2 + httpx)
go get github.com/PGBHub/pgb-sdk-go # Go
cargo add pgbhub # RustDeploy JavaScript (Node.js) or Python functions that are served over HTTPS on your project domain, run on a schedule, or receive signed webhooks.
// Function "orders" (runtime: javascript)
// The handler gets one context object: { req, db, env, auth, ... }
export default async function handler({ req, db }) {
if (req.method !== "POST") {
return Response.json({ error: "Use POST" }, { status: 405 });
}
const { items, total } = req.body;
const rows = await db.query(
"INSERT INTO orders (items, total) VALUES ($1, $2) RETURNING id",
[JSON.stringify(items), total]
);
return Response.json({ orderId: rows[0].id }, { status: 201 });
}Run a container image or a Git repository with a Dockerfile on its own HTTPS address. Idle containers are stopped and start again on the next request.
# Deploy a container from an image (control plane, Bearer API key)
curl -X POST "https://console.pgbhub.com/api/v1/projects/$PGBHUB_PROJECT_ID/containers" \
-H "Authorization: Bearer $PGBHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "myapp-api",
"image": "ghcr.io/myorg/myapp-api:2.4.1",
"port": 8080,
"health_check_path": "/health",
"env_vars": {"NODE_ENV": "production"},
"min_replicas": 0,
"max_replicas": 3
}'
# The service is then served at its own subdomain
curl "https://myapp-api.ct.pgbhub.app/health"Sign users in with email and password, 6-digit email codes, magic links or Google/GitHub OAuth, and use their identity in PostgreSQL Row-Level Security policies.
import { createClient } from "@pgb/sdk";
// In a browser or app, use the publishable key, never the secret key
const pgb = createClient({
endpoint: "https://<project>.pgbhub.app",
apiKey: process.env.PGBHUB_PUBLISHABLE_KEY,
});
const email = "[email protected]";
// 1. Email the user a 6-digit code (valid for 10 minutes)
await pgb.auth.requestOtp({ email });
// 2. Verify the code the user typed in
const codeFromUser = "849201";
const { user } = await pgb.auth.verifyOtp({ email, token: codeFromUser });
console.log("Signed in as", user.id);
// 3. The client keeps the session and sends the access token on later
// auth calls (as X-PGB-User-Token)
const me = await pgb.auth.getUser();
// Magic links work the same way
await pgb.auth.sendMagicLink({ email, redirectTo: "https://myapp.example.com/auth/callback" });Per-project object storage on Cloudflare R2. Files are uploaded directly to storage through a presigned URL that is valid for 15 minutes.
import { readFile } from "node:fs/promises";
import { createClient } from "@pgb/sdk";
// Storage is served by the project gateway and needs the secret key
const pgb = createClient({
endpoint: "https://<project>.pgbhub.app",
apiKey: process.env.PGBHUB_API_KEY, // pgb_sec_...
});
const fileBytes = await readFile("./driver_984.jpg");
// upload() requests a presigned URL (valid 15 minutes) and PUTs the bytes to it
const { path } = await pgb.storage.upload("avatars/driver_984.jpg", fileBytes, {
contentType: "image/jpeg",
});
// List and delete
const objects = await pgb.storage.list("avatars/");
await pgb.storage.delete(path);Send transactional email from your project's verified domain with one API call. Bounces and complaints are tracked for you.
import { createClient } from "@pgb/sdk";
// Sending needs the project's secret key, so call this from your server
const pgb = createClient({
endpoint: "https://<project>.pgbhub.app",
apiKey: process.env.PGBHUB_API_KEY, // pgb_sec_...
});
const res = await pgb.mail.send({
to: "[email protected]",
subject: "Your ride receipt #9842",
html: "<h1>Thanks for riding with us</h1><p>Total: $24.50</p>",
text: "Thanks for riding with us. Total: $24.50",
});
// success means the mail provider accepted the message for delivery
console.log("Accepted for delivery:", res.success);Broadcast JSON messages to named channels and receive them over Server-Sent Events. Channels are scoped to your project.
import { createClient } from "@pgb/sdk";
// Realtime is served by the control plane; use a project key
const pgb = createClient({
endpoint: "https://console.pgbhub.com",
apiKey: process.env.PGBHUB_API_KEY,
});
// Subscribe: returns a function that closes the stream
const unsubscribe = pgb.realtime.subscribe(
"orders",
({ event, data }) => console.log(event, data),
(err) => console.error("stream error", err)
);
// Broadcast from your server
await pgb.realtime.broadcast("orders", { orderId: 42, status: "paid" }, { event: "order.paid" });
unsubscribe();Connect Claude Desktop, Claude Code, Cursor and other MCP clients to your project so agents can read the schema, run parameterized SQL and deploy functions and containers.
# @pgb/mcp is not on npm yet. Build it from the public repository:
git clone https://github.com/PGBHub/pgb-mcp.git
cd pgb-mcp
npm install
npm run build
# Then point your MCP client at dist/index.js (see the MCP Config tab).
# Once @pgb/mcp is published, "command": "npx", "args": ["-y", "@pgb/mcp"] works too.Every project is a real PostgreSQL database: store JSON documents in JSONB columns, run geospatial queries with PostGIS and similarity search with pgvector.
-- Enable postgis and vector for your database from the console's Extensions list.
-- 1. JSONB containment, accelerated by a GIN index
CREATE INDEX IF NOT EXISTS venues_data_gin ON venues USING GIN (data jsonb_path_ops);
SELECT id, data->>'name' AS venue, data->'pricing'->>'tier' AS tier
FROM venues
WHERE data @> '{"status": "OPEN", "category": "restaurant"}'::jsonb;
-- 2. PostGIS: venues within 5 km, nearest first
SELECT id, data->>'name' AS name,
ST_Distance(geom::geography, ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography) AS distance_meters
FROM venues
WHERE ST_DWithin(geom::geography, ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography, 5000)
ORDER BY distance_meters ASC
LIMIT 10;
-- 3. pgvector: nearest neighbours by cosine distance
CREATE TABLE IF NOT EXISTS documents (
id bigserial PRIMARY KEY,
title text NOT NULL,
embedding vector(1536)
);
CREATE INDEX IF NOT EXISTS documents_embedding_hnsw
ON documents USING hnsw (embedding vector_cosine_ops);
SELECT id, title, 1 - (embedding <=> $1::vector) AS similarity
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT 5;Connect with any PostgreSQL driver over TLS, directly or through PgBouncer. Automated daily snapshots are copied to offsite object storage.
# Copy the exact connection strings from the console. Keep the password in
# PGPASSWORD (or ~/.pgpass) rather than in the URL.
export PGPASSWORD="<database password>"
# Direct connection (TLS required)
psql "postgresql://[email protected]:5433/$PGDATABASE?sslmode=require"
# PgBouncer, transaction pooling (TLS required)
psql "postgresql://[email protected]:6432/$PGDATABASE?sslmode=require"