All skills
neondatabase avatar

/neon-postgres

@dd290a9 official

Guides and best practices for working with Lakebase Postgres on Neon: connections, pooled vs direct, schema migrations, branching, autoscaling, scale-to-zero, instant restore, read replicas, IP allow lists, logical replication, and Lakebase Search. Use when the work is an existing DATABASE_URL, SQL, schema, inspect, or search. New backends, Auth, files, Functions, and LLM calls go to the parent `neon` skill. Also use for "@neondatabase/serverless", "@neondatabase/neon-js", "neon inspect db", "semantic search", "vector search", "full-text search", "BM25", or "hybrid search".

Use this Skill: https://skilld.dev/gh/neondatabase/agent-skills/neon-postgres

This session only. Nothing lands on disk.

referenceslakebase-search-drizzle.md

≈1.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Managing Lakebase Search with Drizzle

When the user wants Lakebase Search managed through Drizzle, treat the SQL in Vector Search, Full-Text Search, and Hybrid Search as the source of truth and apply it as below. Use Drizzle for all schema and migration management unless the user says otherwise.

Requires drizzle-orm 0.36+ and drizzle-kit 0.27+: the schema below returns its indexes as an array from the pgTable extra-config callback. Those versions also include generated-column support for the tsvector column, the custom-method .using(...).op(...) index API for lakebase_ann, the vector column type, and the cosineDistance helper.

Contents:

  • Config: drizzle.config.ts and the migration connection
  • Extensions: custom migration required to create extensions (Drizzle can't)
  • Schema: columns, generated tsvector, and the ANN index
  • BM25 Index: created after the corpus is seeded
  • Query: vector, BM25, and hybrid reads
  • Tune Per Query: per-query GUCs

Rules:

  • Express everything Drizzle can in schema.ts: the columns, the generated tsvector, and the lakebase_ann index. Only CREATE EXTENSION and the post-seed lakebase_bm25 index need custom migrations.
  • Use drizzle-kit generate then migrate. Never run drizzle-kit push (it reconciles the database to schema.ts, so it drops the post-seed lakebase_bm25 index and any other object not declared there)
  • Run every migration over the direct (unpooled) connection.
  • The extension must exist before the vector column and the lakebase_ann index that depend on it.

Config

drizzle-kit generate and migrate read drizzle.config.ts. Point dbCredentials.url at the direct (unpooled) connection string:

// drizzle.config.ts
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/schema.ts",
  out: "./drizzle",
  dialect: "postgresql",
  // Direct (unpooled) URL. Neon exposes it as DATABASE_URL_UNPOOLED.
  dbCredentials: { url: process.env.DATABASE_URL_UNPOOLED },
});

Extensions

Drizzle cannot express CREATE EXTENSION, and the vector column and lakebase_ann index below depend on lakebase_vector, so generate a custom migration for the extensions first so it runs before the schema migration:

npx drizzle-kit generate --custom --name=lakebase_extensions
-- drizzle/0000_lakebase_extensions.sql
CREATE EXTENSION IF NOT EXISTS lakebase_vector CASCADE;
CREATE EXTENSION IF NOT EXISTS lakebase_text;

Schema

The columns, the generated tsvector, and the lakebase_ann index all go in schema.ts. tsvector has no built-in Drizzle type, so define it using the customType:

// src/schema.ts
import { pgTable, bigint, text, vector, index, customType } from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm";

const tsvector = customType<{ data: string }>({
  dataType() {
    return "tsvector";
  },
});

export const documents = pgTable(
  "documents",
  {
    id: bigint("id", { mode: "number" }).generatedByDefaultAsIdentity().primaryKey(),
    title: text("title").notNull(),
    body: text("body").notNull(),
    embedding: vector("embedding", { dimensions: 1536 }),
    bodyTsv: tsvector("body_tsv").generatedAlwaysAs(
      sql`to_tsvector('english', "body")`,
    ),
  },
  (table) => [
    index("documents_embedding_ann").using(
      "lakebase_ann",
      table.embedding.op("vector_cosine_ops"),
    ),
  ],
);

Set the dimension to match your embedding model. Postgres maintains body_tsv, so never write it from the app. Generate and apply the migration after the extensions migration above:

npx drizzle-kit generate --name=lakebase_search
npx drizzle-kit migrate

BM25 Index

Keep the lakebase_bm25 index out of schema.ts. It must be built only after the initial corpus is loaded, so its build-time statistics are meaningful (see Full-text search) — a schema migration would build it against an empty table. Add it in a later custom migration that runs after seeding:

npx drizzle-kit generate --custom --name=bm25_index
-- drizzle/NNNN_bm25_index.sql, applied after the corpus is seeded
CREATE INDEX documents_body_bm25 ON documents USING lakebase_bm25 (body_tsv);

Query

Use the query builder with Drizzle's cosineDistance helper for vector search. It emits the <=> operator, so keep the index on vector_cosine_ops:

import { cosineDistance } from "drizzle-orm";
import { documents } from "./schema";

// queryEmbedding: number[] from the same model used for stored documents
const distance = cosineDistance(documents.embedding, queryEmbedding);

const rows = await db
  .select({ id: documents.id, title: documents.title, distance })
  .from(documents)
  .orderBy(distance)
  .limit(k);

BM25 has no Drizzle helper: <@> and to_bm25query require raw SQL. Reference the generated column by its body_tsv name. Bind user input as parameters through the sql template:

import { sql } from "drizzle-orm";

const rows = await db.execute(sql`
  SELECT id, title,
    body_tsv <@> to_bm25query(
      to_tsvector('english', ${queryText}),
      'documents_body_bm25'::regclass
    ) AS score
  FROM documents
  ORDER BY score
  LIMIT ${k}
`);

Run the hybrid search RRF query the same way: raw SQL through db.execute.

Tune Per Query

Per-query GUCs (lakebase_ann.probes, lakebase_ann.epsilon, lakebase_bm25.default_limit, lakebase_bm25.prefilter) must be set with SET LOCAL inside a transaction so they apply to the same pooled connection as the query:

import { cosineDistance, sql } from "drizzle-orm";
import { documents } from "./schema";

const distance = cosineDistance(documents.embedding, queryEmbedding);

const rows = await db.transaction(async (tx) => {
  // SET LOCAL scopes the GUC to this transaction's connection; do not hoist it out.
  // Keep probes at 'auto' unless an IVF `lists` layout exists: a numeric value must
  // match the `lists` shape or it errors ("need 0 probes ..."). See vector-search.md.
  await tx.execute(sql`SET LOCAL lakebase_ann.probes = 'auto'`);
  return tx
    .select({ id: documents.id, title: documents.title, distance })
    .from(documents)
    .orderBy(distance)
    .limit(k);
});

Sources:

Source: SKILL.md on GitHub

No alerts7d5 checks · Risk SAFE
  • Gen Agent Trust Hub7d

    The skill provides a comprehensive set of instructions and references for managing Neon Postgres databases. It includes procedures for project setup, connection management, schema migrations, and performance diagnostics using the official Neon CLI and MCP server. It also features detailed technical guides for semantic vector search, full-text search, and hybrid search strategies. No security issues were detected, and the skill follows industry best practices for environment variable management and vendor tool utilization.

  • Socket7d

    No alerts

  • Snyk7d

    Risk: LOW · No issues

  • Runlayer7mo

    4/14 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at dd290a9. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Activeupdated last week
Other metadata
metadata
{
  "parent": "neon",
  "source": "https://github.com/neondatabase/agent-skills/tree/main/skills/neon-postgres"
}

README badge

README badge for neondatabase/agent-skills/neon-postgres

Guides setup, connections, branching, and advanced features for Neon Serverless Postgres. Covers the Neon CLI, MCP server, REST API, TypeScript and Python SDKs, connection pooling strategies for different runtimes (node-postgres vs. serverless driver), Neon Auth, and infrastructure-as-code via neon.ts. Use when a user asks about Neon setup, DATABASE_URL, scale-to-zero, autoscaling, read replicas, or connecting an ORM like Drizzle.

Generated from the current SKILL.md.

Does Neon work with my ORM or database client?
Yes. Neon is fully compatible with Postgres, so it works with any language, framework, or ORM that supports Postgres — including Drizzle, Prisma, TypeORM, node-postgres, and the Neon serverless driver. Pick the driver based on your runtime: use node-postgres for long-running environments and the serverless driver for fully-isolated serverless platforms like Netlify.
What's the difference between the serverless driver and node-postgres?
The serverless driver (@neondatabase/serverless) queries over HTTP and is designed for fully-isolated serverless runtimes like Netlify where a persistent TCP connection can't be reused per request. node-postgres (pg) maintains a persistent connection pool and is better for long-running or shared-runtime environments like Vercel Fluid compute or Neon Functions.
Can I use this skill to set up a new Neon project from scratch?
Yes. Run `npx -y neonctl@latest init --agent <agent-name>` to automatically create an API key, set up the MCP server or extension, install the skill, and guide you through project selection or creation. This is the recommended starting point.
Does this skill cover Neon Auth setup?
Yes. The skill covers Neon Auth setup, including provisioning it via the MCP server and integrating it with the Neon JS SDK for combined auth and data API workflows. Skip it if your app doesn't need user authentication.
Can I use infrastructure-as-code to configure Neon branches?
Yes, via `neon.ts` (from @neondatabase/config). Declare which services (Neon Auth, Data API) your branches need and per-branch compute settings like autoscaling and scale-to-zero, then use `neonctl config apply` to provision them.

Generated from the current SKILL.md. These answers refresh after source changes.