How to Integrate Xata with Astro: Complete Guide
Step-by-step guide to integrating Xata with your Astro website. Setup, configuration, and best practices.
How to Integrate Xata with Astro: Complete Guide
Xata is a managed PostgreSQL platform built around instant copy-on-write branching, separation of storage from compute, zero-downtime schema changes, and data masking. It speaks the standard Postgres wire protocol, which means you connect to it from Astro with an ordinary Postgres driver or ORM rather than a proprietary SDK. That makes it a clean fit for Astro projects that want managed Postgres with Git-style database branches and no vendor lock-in at the query layer.
This guide walks through connecting Xata to an Astro project, from account setup through querying data and deploying to production. It reflects the current Xata Postgres platform, not the older Xata Lite product.
A Note on the Old Xata SDK
If you followed an older tutorial, you may have seen a @xata.io/client TypeScript SDK, a @xata.io/cli npm package, and a xata codegen step that generated a XataClient class. That product line was Xata Lite, and it has been retired. The @xata.io/client package is now marked as no longer supported on npm, and the Xata Lite TypeScript and JavaScript SDK repository has been archived as deprecated, with Xata Lite permanently retired on February 28, 2026.
The current Xata is a Postgres platform. You connect using a standard connection string with any Postgres client (such as pg or postgres) or an ORM (such as Drizzle or Prisma). There is no Xata-specific runtime SDK to install and no codegen step. The rest of this guide uses that approach.
Prerequisites
Make sure you have the following before starting:
- Node.js v22.12.0 or higher (the minimum supported by current Astro; odd-numbered releases such as v23 are not supported)
- An existing Astro project (this guide targets Astro 6)
- A Xata account (sign up at console.xata.io with GitHub, Google, or email)
- A Xata project and branch created (your branch is your Postgres cluster)
- An Astro server adapter configured if you plan to query the database at request time
Installation
You do not install a Xata runtime SDK. Instead, install a standard Postgres client for Node. This guide uses the postgres (postgres-js) driver, which is also what Drizzle recommends for Xata:
npm install postgres
If you prefer node-postgres, install that instead:
npm install pg
The Xata CLI (Optional)
The Xata CLI is helpful for creating branches and fetching connection strings, but it is not required to use Xata from Astro. Note that it is no longer published as @xata.io/cli on npm; that package name does not resolve. Install it with the official script instead:
curl -fsSL https://xata.io/install.sh | bash
Then authenticate and initialize:
xata auth login
xata init
Once a branch exists, you can print its Postgres connection string at any time:
xata branch url
You can also copy the same connection string from the branch Overview page in the Xata console, so the CLI is entirely optional.
Configuration
Getting Your Connection String
Xata exposes a standard PostgreSQL connection string. The format and how each part maps to Xata is:
postgresql://[Workspace ID]:[API key]@[Region].sql.xata.sh:5432/[Database]:[Branch]?sslmode=require
A concrete example from the Xata docs:
postgresql://ws1234:xau_apikey123456@us-east-1.sql.xata.sh:5432/Games:main?sslmode=require
Two important details:
- Branch syntax uses a colon. The database and branch are written as
Database:branch. If you omit the branch, you connect tomain. - SSL is mandatory. Xata does not allow plain-text connections, so the connection string must end with
?sslmode=require.
Environment Variables
Store the connection string in a .env file. The community convention, used by both Drizzle and Prisma in their Xata guides, is DATABASE_URL:
DATABASE_URL=postgresql://ws1234:xau_apikey123456@us-east-1.sql.xata.sh:5432/Games:main?sslmode=require
In Astro, server-side environment variables are read through import.meta.env (or astro:env for typed env). Do not prefix this value with PUBLIC_, since the connection string contains your API key and must never reach the browser.
Setting Up the Client
Create a small module that initializes a single Postgres connection and reuses it. Using the postgres driver:
// src/lib/db.ts
import postgres from 'postgres';
let sql: ReturnType<typeof postgres> | null = null;
export const getDb = () => {
if (sql) return sql;
const connectionString = import.meta.env.DATABASE_URL;
if (!connectionString) {
throw new Error('DATABASE_URL is not set');
}
sql = postgres(connectionString);
return sql;
};
If you would rather use an ORM, Drizzle works over the same connection string. Its recommended Xata setup uses postgres-js:
// src/lib/db.ts (Drizzle variant)
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const client = postgres(import.meta.env.DATABASE_URL);
export const db = drizzle({ client });
Astro Config
In Astro 6 the output is static by default, and output: 'hybrid' no longer exists. It was removed in Astro 5, where static became the default and adapters became the way to enable on-demand rendering. So you do not set a hybrid mode. Instead you keep the default static build and opt individual database-backed routes into on-demand rendering, or set output: 'server' to render everything on demand.
You only need an adapter for the routes that actually query Xata at request time. Add the Node adapter:
npx astro add node
That command installs @astrojs/node and wires it into your config:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
// 'static' is the default; omit `output` to keep static pages static
// and opt specific routes into on-demand rendering with `export const prerender = false`.
adapter: node({ mode: 'standalone' }),
});
If you want every page rendered on demand instead, set output: 'server' and use export const prerender = true on the pages you want kept static.
Common Patterns
Fetching Records in an On-Demand Page
Static pages are built once, so a page that should reflect live database state must opt out of prerendering. Add export const prerender = false and query in the frontmatter:
---
// src/pages/articles.astro
export const prerender = false;
import { getDb } from '../lib/db';
const sql = getDb();
const articles = await sql`
SELECT slug, title, summary
FROM articles
WHERE published = true
ORDER BY created_at DESC
LIMIT 20
`;
---
<html>
<body>
<h1>Articles</h1>
{articles.map((article) => (
<article>
<h2><a href={`/articles/${article.slug}`}>{article.title}</a></h2>
<p>{article.summary}</p>
</article>
))}
</body>
</html>
If the data only changes at deploy time, you can leave the page static and let the query run at build instead. Choose on-demand rendering only when you need fresh data per request.
Querying From an API Route
API routes are the recommended place to talk to your database in Astro. They render on demand, so they need the adapter (or prerender = false):
// src/pages/api/search.ts
import type { APIRoute } from 'astro';
import { getDb } from '../../lib/db';
export const prerender = false;
export const GET: APIRoute = async ({ url }) => {
const query = url.searchParams.get('q') || '';
const sql = getDb();
const results = await sql`
SELECT slug, title, summary
FROM articles
WHERE published = true
AND title ILIKE ${'%' + query + '%'}
LIMIT 20
`;
return new Response(JSON.stringify(results), {
headers: { 'Content-Type': 'application/json' },
});
};
Because Xata is plain Postgres, you can use any Postgres feature for search, including full-text search with tsvector and to_tsquery, or the pg_trgm extension for fuzzy matching, rather than a proprietary search API.
Creating Records
// src/pages/api/articles.ts
import type { APIRoute } from 'astro';
import { getDb } from '../../lib/db';
export const prerender = false;
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
const sql = getDb();
const [article] = await sql`
INSERT INTO articles (title, slug, content, published)
VALUES (${body.title}, ${body.slug}, ${body.content}, false)
RETURNING *
`;
return new Response(JSON.stringify(article), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
};
The tagged-template syntax of the postgres driver parameterizes every interpolated value, which protects against SQL injection without manual escaping.
Using Database Branches
One of Xata's headline features is copy-on-write branching. Each branch has its own connection string, with the branch name after the colon in the database segment (for example Games:main versus Games:preview). To point a preview deployment at a preview branch, set its DATABASE_URL to the branch connection string. Because the branch name lives entirely in the connection string, no application code changes are needed to switch environments.
Troubleshooting
Connection refused or SSL errors: Xata does not accept plain-text connections. Make sure your connection string ends with ?sslmode=require. Some hosts also need explicit SSL options passed to the driver.
Authentication failed: The connection string uses your workspace ID as the username and your API key as the password. The API key starts with xau_. If you rotated the key in the console, update DATABASE_URL and restart the dev server so Astro reloads the value.
Empty results when data exists: Check that the branch name in the connection string matches the branch holding your data. The database segment is Database:branch, and omitting the branch connects to main.
Too many connections in serverless or per-request contexts: Long-lived or serverless functions can exhaust the connection pool. Reuse a single client instance (as in the getDb helper) rather than creating one per request, and consider a connection pooler for high-traffic on-demand routes.
Looking for @xata.io/client, XataClient, or xata codegen: Those belong to the retired Xata Lite product and no longer apply. Use a standard Postgres driver or ORM with the connection string instead.
Common Errors and Fixes
output: 'hybrid' is not a valid value: The hybrid output option was removed in Astro 5. In Astro 6 the only output modes are static (the default) and server. Remove the output: 'hybrid' line, keep the default static build, and add export const prerender = false to the specific routes that query Xata. Reference: the Astro on-demand rendering guide.
Cannot find package '@xata.io/cli' from npm: The Xata CLI is no longer published under that npm name, so npm install -g @xata.io/cli fails to resolve. Install it with curl -fsSL https://xata.io/install.sh | bash per the Xata quickstart, or skip the CLI entirely and copy the connection string from the branch Overview page.
@xata.io/client is deprecated / no longer supported: npm flags this package as no longer supported because it belongs to the retired Xata Lite SDK. Uninstall it and connect with postgres or pg using the Postgres connection string instead.
A database query runs at build time and never updates: Astro prerenders pages by default, so a page without export const prerender = false runs its query once during astro build. Add the prerender opt-out (and an adapter) for any page that must read live data on each request.
No adapter installed when enabling on-demand rendering: On-demand routes need a server adapter. Run npx astro add node (or another official adapter) so @astrojs/node is installed and registered; otherwise the build errors when it finds a non-prerendered route.
SQL injection risk from string concatenation: Do not build SQL by concatenating user input into the query string. Use the driver's parameterization (tagged templates in postgres, or $1 placeholders in pg) so values are escaped safely.
Conclusion
The current Xata gives Astro projects managed PostgreSQL with copy-on-write branching and a plain Postgres wire protocol, which means no proprietary SDK and no lock-in at the query layer. You connect with a standard driver such as postgres or pg, or an ORM like Drizzle or Prisma, using a connection string that ends in ?sslmode=require. Keep the rest of your site static, opt only the database-backed routes into on-demand rendering with export const prerender = false, and add a server adapter for those routes. Use Xata branches per environment by swapping the connection string, and lean on native Postgres features such as full-text search rather than a separate service.
Official Docs and Examples
- Xata documentation overview: https://xata.io/docs/overview
- Xata quickstart (sign up, create a branch, get a connection string, CLI install): https://xata.io/docs/quickstart
- Xata direct Postgres access (connection string format, SSL, branch syntax): https://github.com/xataio/mdx-docs/blob/main/020-Connect/010-Postgres/001-direct-access.mdx
- Astro on-demand rendering guide (output modes, prerender, adapters): https://docs.astro.build/en/guides/on-demand-rendering/
- Astro Node adapter (
@astrojs/node) guide: https://docs.astro.build/en/guides/integrations-guide/node/ - Drizzle ORM with Xata (postgres-js driver example): https://orm.drizzle.team/docs/get-started/xata-new
- Prisma ORM with Xata's Postgres service (example walkthrough): https://dev.to/xata/get-started-with-prisma-orm-and-xatas-postgres-service-1i70
- Deprecated Xata Lite TypeScript/JavaScript SDK (archived, for reference only): https://github.com/xataio/client-ts
Sources
Checked on 2026-05-29:
- @xata.io/client on the npm registry (latest 0.30.1, marked no longer supported)
- @xata.io/cli on the npm registry (returns Not Found; package no longer published)
- astro on the npm registry (latest 6.4.2)
- @astrojs/node on the npm registry (latest 10.1.2)
- postgres on the npm registry (latest 3.4.9)
- pg on the npm registry (latest 8.21.0)
- drizzle-orm on the npm registry (latest 0.45.2)
- xataio/client-ts GitHub repo (deprecated Xata Lite SDK, retired Feb 28 2026)
- Xata documentation overview
- Xata quickstart (CLI install script, branch connection string)
- Xata direct Postgres access docs (connection string format, sslmode=require, branch colon syntax)
- Astro on-demand rendering guide (static default, hybrid removed, prerender, adapters)
- Astro Node adapter guide (npx astro add node, modes)
- Drizzle ORM Get Started with Xata (postgres-js)
- Get started with Prisma ORM and Xata's Postgres service (DATABASE_URL convention)
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.