Neon Documentation Audit
One line: a deep, generally strong docs set for a Postgres backend platform — but the agent-critical surfaces (RLS helpers, serverless transaction options, SDK identity, model IDs) contradict each other across pages, and the three new private-preview services (AI Gateway, Functions, Storage) are documented with full GA-style feature sets and inconsistent maturity signals.
1. RLS helper function contradicts itself across pages (auth.uid() vs auth.user_id()) (critical)
Location: /docs/auth/authentication-flow vs /docs/data-api/get-started
Problem: The Authentication flow page documents the row-level-security helper as auth.uid():
"The user ID is available via the
auth.uid()function" …USING (user_id = auth.uid())
The Data API quickstart documents auth.user_id() (and the Drizzle authUid() helper) for the identical purpose:
user_id text DEFAULT (auth.user_id()) NOT NULL…USING (auth.user_id() = user_id)… "auth.user_id()is a Data API helper that extracts the User ID from the JWT token".
Only one of these function names exists in the database.
Consequence: Row-level security is the entire access-control model here — and these are the two pages a developer reads to set it up. Copy the policy from the Auth page into the Data API world and CREATE POLICY either errors on an unknown function or behaves differently than the working examples. This is the single most consequential contradiction in the set: it silently lands in security-enforcing SQL, and an agent generating a policy has a coin-flip chance of emitting the non-functional name.
The fix: Pick one canonical helper name, grep every RLS example for the other, and add a one-line alias note ("auth.uid() is an alias for auth.user_id()") only if both genuinely resolve. Otherwise correct the Auth flow page to auth.user_id().
2. Serverless driver transaction() example uses an option key the prose says is wrong (critical)
Location: /docs/serverless/serverless-driver
Problem: The array-of-queries example passes isolationLevel:
{ isolationLevel: 'RepeatableRead', readOnly: true }
The prose immediately below names the canonical key isolationMode:
"These transaction-related keys are:
isolationMode,readOnlyanddeferrable. …isolationMode— This option selects a Postgres transaction isolation mode."
The example and the documented option name disagree, one paragraph apart.
Consequence: A developer who copies the example sets isolationLevel; a developer who follows the prose sets isolationMode. One of them is silently ignored — the transaction runs at the default isolation level instead of RepeatableRead, which is exactly the kind of bug that surfaces only under concurrent load in production.
The fix: Make the example and the option reference use the same key. Verify against the actual @neondatabase/serverless API which one the library accepts, and correct the other.
3. Vercel code examples are copy-paste broken (success returns 500, undefined variables) (critical)
Location: /docs/serverless/serverless-driver (WebSockets / Vercel function examples)
Problem: The Vercel Serverless Function example returns HTTP 500 on the success path and references an undefined variable:
const posts = await pool.query('SELECT * FROM posts WHERE id = $1', [postId]);
await pool.end();
return res.status(500).send(post); // 500 on success; `post` never defined (result is `posts`); `postId` never defined
The Vercel Edge Function example has the same defects:
const posts = await pool.query('SELECT * FROM posts WHERE id = $1', [postId]);
return new Response(JSON.stringify(post), { ... }); // `post`/`postId` undefined
Consequence: These are the canonical "here's how to use Neon on Vercel" snippets. Copied verbatim, the serverless one throws ReferenceError: post is not defined (and postId), and if you only fix the variable name you still ship a handler that returns 500 on every successful request. An AI agent extracting this example produces a broken endpoint with no warning.
The fix: Define postId (e.g. from the request), assign/return the same variable name (posts), and return res.status(200) on success. Run every quickstart snippet through a compile/lint gate before publishing.
4. Two different pages are both "the Neon TypeScript SDK" for two different packages (critical)
Location: /docs/reference/typescript-sdk, /docs/reference/javascript-sdk, /llms.txt
Problem: /reference/typescript-sdk is titled "Neon API TypeScript SDK" and documents @neondatabase/api-client — but its own summary calls it "the Neon TypeScript SDK." Meanwhile /reference/javascript-sdk is titled "Neon TypeScript SDK" and documents a completely different package, @neondatabase/neon-js (Auth + Data API). The URL slug javascript-sdk also contradicts its own "TypeScript SDK" title. llms.txt cements the collision at the navigation level:
Neon API TypeScript SDK — …/typescript-sdk.mdNeon TypeScript SDK — …/javascript-sdk.md
Consequence: "The Neon TypeScript SDK" resolves to two unrelated npm packages with opposite jobs (platform management vs. auth/data queries). A developer — or an agent doing npm install from a doc reference — can easily install the wrong one and import an API that doesn't exist. The two pages even cross-link to each other under different names, so there's no way to tell from the link text which package you'll land on.
The fix: Give each page a unique, package-anchored title ("Management API client (@neondatabase/api-client)" vs "Auth + Data API client (@neondatabase/neon-js)"), align the URL slug with the title, and dedupe the two near-identical entries in llms.txt.
5. SDK "Related docs" footers contain malformed, dead links (significant)
Location: /docs/reference/typescript-sdk and /docs/reference/javascript-sdk ("Related docs" footer)
Problem: Two footer links concatenate the docs base URL with an external GitHub URL:
Go SDK (Neon API)→https://neon.com/docs/https://github.com/kislerdm/neon-sdk-goNode.js / Deno SDK (Neon API)→https://neon.com/docs/https://github.com/paambaati/neon-js-sdk
Both pages carry the same broken pair.
Consequence: Every reader looking for the Go or Node/Deno SDK from the two most likely entry points hits a 404 (neon.com/docs/https://… is not a valid path). The community SDKs effectively have no working link from the official reference.
The fix: Stop prefixing absolute external URLs with the docs base path; emit https://github.com/kislerdm/neon-sdk-go and https://github.com/paambaati/neon-js-sdk directly. Add a link-checker that flags neon.com/docs/https:// patterns.
6. API deprecation advertises a sunset date that has already passed (significant)
Location: /docs/reference/typescript-sdk (Retrieve Consumption Metrics)
Problem: The deprecation notice presents a past date as a future event:
"The Get account consumption metrics endpoint (
GET /consumption_history/account) is deprecated, with a planned sunset of June 1, 2026."
Today is 2026-06-16 — the "planned" sunset is two weeks in the past, yet the notice still reads as forward-looking.
Consequence: A developer reading this can't tell whether the endpoint still works. If it was actually removed June 1, their integration is already broken and the docs imply they still have time to migrate. If it wasn't removed, the date is wrong. Either way the migration urgency is misrepresented.
The fix: Update the notice to reflect reality — either "removed on June 1, 2026" with the endpoint marked gone, or a corrected future sunset date. Prefer relative framing tied to a changelog entry over hardcoded dates that silently go stale.
7. AI Gateway is simultaneously "available" and "coming soon" (significant)
Location: /docs/ai-gateway/overview vs /docs/ai-gateway/get-started and /docs/ai-gateway/models
Problem: The overview states the feature exists and is reachable today:
"During the private preview, AI Gateway is available for new projects in the AWS us-east-2 region only".
The quickstart and models pages, for the same feature, lead with a banner that says it is not here yet:
"Coming Soon: Private Preview … it's not ready for production use".
This is not the reconcilable "available-but-preview-grade" tension — "Coming Soon" asserts the feature has not arrived, while "is available" asserts it has. The two statements directly negate each other on adjacent pages.
Consequence: A developer can't tell whether AI Gateway can be used at all today. The overview invites them to start in us-east-2; the page with the actual first request tells them the feature is still coming. This also undercuts trust in the maturity labels elsewhere in the docs.
The fix: Standardize one maturity statement and banner across all AI Gateway pages. If it's reachable today in us-east-2, drop "Coming Soon" everywhere and keep a single "private preview — not production-ready" note; if it isn't, remove "is available" from the overview.
8. Recommended model IDs don't exist in the model catalog (significant)
Location: /docs/ai-gateway/models ("Quick picks" vs catalog)
Problem: The page says short IDs are canonical:
"Use short model IDs in the model field … The
databricks-prefixed form is also accepted."
But the open-weight recommendation in Quick picks uses a short ID that the catalog never lists:
Quick picks: "Use an open-weight model →
gpt-oss-120b" Catalog (Databricks): onlydatabricks-gpt-oss-120banddatabricks-gpt-oss-20b.
So for the open-weight models, only the databricks--prefixed form actually appears — contradicting the "short IDs are canonical" rule and the Quick-picks recommendation.
Consequence: A developer copies model: 'gpt-oss-120b' from Quick picks and may get a model-not-found error, because the catalog only documents the prefixed form. The one place the short-ID rule is most load-bearing (knowing what to type) is where it's inconsistent.
The fix: Either confirm gpt-oss-120b resolves and add it as the canonical row in the catalog, or change Quick picks to the documented databricks-gpt-oss-120b. Make the catalog list the short ID for every model if short IDs are truly canonical.
9. Functions and Storage previews are documented as GA, with no production-readiness signal (significant)
Location: /docs/compute/functions/overview and /docs/storage/overview (vs /docs/ai-gateway/get-started)
Problem: Both pages document complete, GA-style feature sets — Functions ships a five-template table, a WinterTC fetch(request) handler contract, and neonctl deploy/bootstrap commands; Storage documents two bucket access modes and full S3-SDK compatibility — while flagging maturity only with a single buried sentence:
Functions: "During the private preview, Functions are available for new projects in the AWS us-east-2 region only." Storage: "During the private preview, Storage is available for new projects in the AWS us-east-2 region only."
Neither page carries a "not ready for production use" banner. AI Gateway — the same maturity tier, same us-east-2 lock — does carry exactly that banner (see #7). So three sibling private-preview services signal their maturity three different ways: AI Gateway warns "not ready for production," Functions and Storage say only "available … in us-east-2."
Consequence: A developer reading the Functions or Storage overview sees a fully-documented, ready-looking service and no signal that it is preview-grade or unfit for production — the opposite impression from the AI Gateway pages for an identically-mature feature. Inconsistent maturity disclosure across the three flagship new services makes it impossible to know which can be relied on.
The fix: Apply one shared maturity banner to every private-preview service page (AI Gateway, Functions, Storage). If "not ready for production" is true for one preview, state it on all of them; drive the banner from a single status source so the three can't drift.
10. Pricing page claims a Beta feature ships on "all plans" (significant)
Location: /pricing vs /docs/data-api/overview
Problem: The marketing pricing page asserts the Data API as a universal, included capability:
"All plans include: … and a Data API for querying over HTTP."
The Data API product page labels it Beta:
"Beta — The Neon Data API is in Beta."
Consequence: A buyer choosing a plan reads "all plans include a Data API" as a production guarantee, then discovers on the product page it's Beta. The pricing surface and the product surface disagree on whether a developer should rely on this in production.
The fix: Mark the Data API line as "(Beta)" on the pricing page, or remove the Beta label if it has graduated. Keep maturity labels consistent between pricing and product docs.
11. Neon Auth is billed as a metered plan feature but labeled Beta in the docs (significant)
Location: /docs/introduction/plans (and /pricing) vs /docs/auth/authentication-flow
Problem: The plans comparison counts Neon Auth as a billable, plan-differentiating capacity with no maturity caveat:
"Auth — Free: Up to 60k MAU / Launch: Up to 1M MAU / Scale: Up to 1M MAU".
The Authentication flow page labels the same service Beta:
"Beta — The Neon Auth with Better Auth is in Beta."
Consequence: Monthly-active-user tiers are how a buyer sizes and commits to an auth provider for production — yet the service backing those numbers is Beta everywhere except the plans table, which gives no hint. A team picks a plan based on a 1M-MAU ceiling, builds production auth on it, and only later finds the Beta label (and that neon_auth data, sign-ups open by default, lives in their own schema). This is the sharper buyer-trust mismatch of the two Beta-vs-billing cases, because auth is load-bearing for every request.
The fix: Add a "(Beta)" marker to the Auth row in the plans and pricing tables, or graduate the service before billing it as a tiered capacity. Keep the maturity label identical between the plans surface and the Auth product docs.
12. Docs plans table omits features the marketing pricing page advertises (minor)
Location: /docs/introduction/plans vs /pricing
Problem: The marketing pricing comparison advertises rows the docs comparison table doesn't have, including:
"Snapshots (scheduled) — - / Yes / Yes" and "Set spending limits — - / Yes / Yes".
The docs plans.md table has no "Set spending limits" or "Snapshots (scheduled)" row at all.
Consequence: "Set spending limits" is exactly the kind of guardrail a developer on a usage-based plan checks before committing. Its absence from the docs pricing table makes it look unavailable, and the two tables can't be reconciled side by side.
The fix: Generate both comparison tables from one source of truth so feature rows can't drift, or at minimum add the missing rows to the docs plans page.
13. Auth flow shows a tokenless Authorization: Bearer header (minor)
Location: /docs/auth/authentication-flow ("JWT is used for database queries")
Problem: The step-by-step JWT walkthrough shows the header with no token value:
"2. Adds
Authorization: Bearerheader"
The literal ends at Bearer with nothing after it — no token, no <JWT> placeholder marking it as a substitution.
Consequence: Likely a rendering artifact, but it's the only place the page shows how the auth header is constructed, and a reader (or an agent) transcribing it can emit a literal Authorization: Bearer with an empty credential, which the Data API rejects as unauthenticated. Because nothing marks the blank as a placeholder, there's no signal that a token belongs there.
The fix: Render the value with an explicit placeholder, e.g. Authorization: Bearer <session.access_token>, so both humans and agents know a token must be substituted in.
14. Typos and a grammar bug on high-traffic reference pages (minor)
Location: /docs/introduction/plans (autoscaling FAQ) and /docs/connect/connection-errors
Problem: The plans FAQ has a duplicated/garbled clause:
"Free up to 2 CU, Launch and Scale Scale up to 16 CU."
The connection-errors page has a dropped word:
"We intend deprecate this option when most libraries … provide SNI support."
The same connection-errors page also shows a malformed nslookup sample host p-cool-darkness…, which the source notes is missing a leading letter — likely the ep- endpoint prefix dropped to p-.
Consequence: Low individual impact, but the autoscaling FAQ is where users confirm their compute ceiling, and "Launch and Scale Scale up to 16 CU" reads as an unfinished edit. The malformed nslookup host could mislead someone hand-verifying DNS for an endpoint.
The fix: Correct "Launch and Scale up to 16 CU," "We intend to deprecate," and the sample hostname (restoring what is almost certainly the ep- endpoint prefix). These are quick copyedits but they sit on high-traffic reference pages.
What they do well
- Real machine-readable surfaces for agents:
llms.txt, an OpenAPI spec at/api_spec/release/v2.json, and structured rate-limit / error tables (e.g. 429 handling, 200k/1.1M TPM limits) give agents parseable contracts rather than pure prose. - Strong error reference:
/docs/connect/connection-errorsexplains scale-to-zero side effects, 5-minute idle-suspend behavior, and SNI workarounds with honest security trade-offs — better than most platforms document. - Clear architectural framing: the compute/storage separation and resource hierarchy (Org → Project → Branch → Compute/Database/Role) are explained concisely and consistently.
Top 3 recommendations
- Fix the contradictions that silently break working code first: the RLS helper (
auth.uid()vsauth.user_id(), #1), the transaction option key (isolationLevelvsisolationMode, #2), and the broken Vercel snippets (#3). These are the findings that turn a copy-paste into a production bug. - Disambiguate the SDK identity (#4) and repair the malformed GitHub links (#5) so "the Neon TypeScript SDK" resolves to exactly one package and every SDK link actually loads — both are agent-hostile as written.
- Drive every maturity and pricing label from one source so they can't drift: AI Gateway "available" vs "Coming Soon" (#7), Functions/Storage preview pages with no production warning (#9), Data API "all plans include" vs Beta (#10), Auth billed by MAU while labeled Beta (#11), and the docs-vs-marketing plan tables (#12).