Beta
Overview
This guide describes how you can implement database versioning for AI agent and code generation platforms using Neon's snapshot APIs. With snapshots, you can create point-in-time database versions, perform instant rollbacks, and maintain stable database connection strings for your applications. See a working implementation in the demonstration repository.
Terminology note: This guide uses "versions" to describe saved database states from the user's perspective, and "snapshots" when referring to Neon's technical implementation. You may also see these called "checkpoints" or "edits" in some AI agent contexts.
Synopsis
Use the project's root branch for production, whose database connection string stays the same when a snapshot restore is finalized. Create snapshots to save database versions. For rollbacks, restore snapshots with finalize_restore: true
and target_branch_id
set to your root branch ID, then poll operations until complete before connecting. For previews, use finalize_restore: false
to create temporary branches with their own database connection strings.
Why use snapshots for versioning
Standard database branching is great for development but less suitable for versioning. Each new branch gets a new database connection string and creates dependency chains that complicate deletion. This pattern solves both problems.
By restoring a Neon snapshot to your active branch with finalize_restore: true
, you replace its data in-place while preserving the original, stable connection string. This makes the snapshot-restore pattern ideal for versioned environments where connection stability is needed.
Quick start with the demo
The best way to understand this pattern is to see it in action:
- Clone the snapshots demo app:
- Key files to examine:
- lib/neon/create-snapshot.ts - Snapshot creation implementation
- lib/neon/apply-snapshot.ts - Complete restore workflow with operations polling
- lib/neon/operations.ts - Operation status polling logic
- app/[checkpointId]/page.tsx - UI integration showing versions and rollbacks
- Run locally or use the public demo to see version creation, rollbacks, and previews in action
Note: The demo repository uses "checkpoint" terminology which maps to "version" in this guide. The demo implements a contacts application that evolves through agent prompts, demonstrating version creation and restoration at each stage:
v0: empty app → v1: basic contacts → v2: add role/company → v3: add tags
The active branch pattern
Every agent project maps to one Neon project with a designated root branch that serves as the production database.
Important: Snapshots can only be created from root branches in Neon. A root branch is a branch with no parent (typically named main
or production
).
The active branch:
- Gets its data replaced during finalized rollbacks
- Maintains a consistent database connection string through Neon's restore mechanism — see How restore works for details
- Must be a root branch for snapshot creation
The snapshots:
- Capture point-in-time database versions
- Store only incremental changes (cost-efficient)
- Can be restored to the active branch or to a temporary preview branch
Implementation
Creating snapshots
Create a snapshot to capture the current database version using the snapshot endpoint:
POST /api/v2/projects/{project_id}/branches/{branch_id}/snapshot
Demo implementation: See lib/neon/create-snapshot.ts for an example with error handling and operation polling. Path parameters:
project_id
(string, required): The Neon project IDbranch_id
(string, required): The active branch ID (must be a root branch)
Query parameters:
lsn
(string): Target Log Sequence Number. Cannot be used withtimestamp
timestamp
(string): Target timestamp (RFC 3339). Cannot be used withlsn
name
(string): Name for the snapshotexpires_at
(string): Auto-deletion time (RFC 3339)
Example:
curl --request POST \
--url 'https://console.neon.tech/api/v2/projects/{project_id}/branches/{branch_id}/snapshot?name=version-session-1&expires_at=2025-08-13T00:00:00Z' \
--header 'authorization: Bearer $NEON_API_KEY'
When to create snapshots:
- Start of each agent session
- Before database schema changes
- After successful operations
- User-initiated save points
Rolling back to (restoring) a snapshot
Restore any snapshot to recover a previous version using the restore endpoint:
POST /api/v2/projects/{project_id}/snapshots/{snapshot_id}/restore
Demo implementation: See lib/neon/apply-snapshot.ts for the complete restore workflow including operation polling and error handling. Path parameters:
project_id
(string, required): The Neon project IDsnapshot_id
(string, required): The snapshot ID being restored
Body parameters:
name
(string): A name for the newly restored branch. If omitted, a default name is generatedtarget_branch_id
(string): The ID of the branch to restore the snapshot into. If not specified, the branch from which the snapshot was originally created will be used. Set this value to your root branch ID for rollbacks to preserve connection stringsfinalize_restore
(boolean): Set totrue
to finalize the restore operation immediately. This will complete the restore and move computes from the current branch to the new branch, which keeps the database connection string the same. Defaults tofalse
. Set totrue
when restoring to the active branch for rollbacks,false
to create preview branches
Connection warning
Do not connect to the database until current API operations are complete. Any connection attempt before operations finish will either fail or connect to the old database state, not your restored version.
How restore works
Understanding the restore mechanism explains why the connection string remains stable:
-
New branch creation: When you restore with
finalize_restore: true
, Neon first creates a new branch from your snapshot. This new branch has a different, system-generated branch ID. -
Endpoint transfer: Neon then transfers the compute endpoint (and its associated connection string) from your original active branch to this newly created branch.
-
Settings migration: All branch settings, including its name, are copied to the new active branch, making it appear identical to the old one. Only the branch ID is different.
-
Branch orphan: Your original branch becomes "orphaned." It is disconnected from the compute endpoint and renamed by adding an "(old)" suffix (e.g.,
main (old)
) to the branch name.
Branch ID changes after restore
The connection string remains stable, but the branch ID changes with every finalize_restore: true
operation. If you store the branch ID for use in subsequent API calls (e.g., to create the next snapshot), you must retrieve and store the new branch ID after the restore operation completes.
Rollback workflow
Restore any snapshot to your active branch, preserving the connection string:
{
"target_branch_id": "br-active-branch-123456", // Your root branch ID
"finalize_restore": true // Moves computes and preserves connection string
}
Important: When restoring with
finalize_restore: true
, your previous active branch becomes orphaned and is renamed with(old)
appended, such asproduction (old)
or similar. This orphaned branch is no longer connected to any compute endpoint but preserves your pre-restore state. Delete it during cleanup to avoid unnecessary costs. After calling the restore API:
- Extract the array of operation IDs from the API response.
- For each operation ID, poll the operations endpoint until its status reaches a terminal state (finished, failed, cancelled, or skipped).
- Do not attempt to connect to the database until all operations are complete. Connections made before completion will point to the old, pre-restore database state.
- After verifying a successful restore, delete the orphaned branch (e.g.,
main (old)
) to avoid incurring storage costs.
See the poll operation status documentation for related information. Polling operations example:
// Poll operation status until complete
async function waitForOperation(projectId, operationId) {
while (true) {
const response = await fetch(
`https://console.neon.tech/api/v2/projects/${projectId}/operations/${operationId}`,
{ headers: { Authorization: `Bearer ${NEON_API_KEY}` } }
);
const { status } = await response.json();
// Terminal states - safe to proceed
if (['finished', 'skipped', 'cancelled'].includes(status)) {
return;
}
// Error state - handle appropriately
if (status === 'failed') {
throw new Error('Operation failed');
}
// Still running - wait and retry
await new Promise((resolve) => setTimeout(resolve, 5000));
}
}
// After restore API call
const restoreResponse = await restoreSnapshot(projectId, snapshotId);
const operationIds = restoreResponse.operations.map((op) => op.id);
// Wait for all operations to complete
for (const id of operationIds) {
await waitForOperation(projectId, id);
}
// NOW safe to connect to the restored database
Potential restore-related problems:
- Connection to old state: Ensure all operations completed
- Target branch not found: Verify branch exists
- Operation timeout: Retry with longer timeout
- Accumulating orphaned branches: Delete orphaned branches (e.g.,
production (old)
) after successful restore verification
Preview environments
Create a temporary branch to preview a version without affecting the active branch:
{
"name": "preview-version-123",
"finalize_restore": false // Creates new branch for preview without moving computes
}
This creates a new branch with its own connection string for preview. The active branch remains unchanged. Preview branches should be deleted after use to avoid storage costs.
Managing snapshots
List available snapshots
Get all snapshots with IDs, names, and timestamps using the list snapshots endpoint:
GET /api/v2/projects/{project_id}/snapshots
Path parameters:
project_id
(string, required): The Neon project ID
Delete snapshot
Remove a snapshot using the delete endpoint:
DELETE /api/v2/projects/{project_id}/snapshots/{snapshot_id}
Path parameters:
project_id
(string, required): The Neon project IDsnapshot_id
(string, required): The snapshot ID
Update snapshot name
Rename a snapshot using the update endpoint:
PATCH /api/v2/projects/{project_id}/snapshots/{snapshot_id}
Path parameters:
project_id
(string, required): The Neon project IDsnapshot_id
(string, required): The snapshot ID
Body:
{
"snapshot": {
"name": "important-milestone"
}
}
Cleanup strategy
Proper cleanup reduces costs and keeps your project manageable:
- Delete snapshots when no longer reachable by users
- Restoring from a snapshot doesn't lock that snapshot from deletion, unlike branches where creating child branches prevents deleting the parent
- Delete orphaned branches created during restores (named like
production (old)
) - These orphaned branches accumulate with each restore and consume storage
- Reduces snapshot management fees while shared storage remains
- Set
expires_at
for automatic cleanup or delete manually as needed - Consider removing snapshots after merging features or completing rollback testing
Concepts and terminology
Neon core concepts
- Project: Neon project that owns branches, computes, and snapshots, and more
- Branch: An isolated database environment with its own data and schema that you can connect to and modify
- Snapshot: An immutable, point-in-time backup of a branch's schema and data. Read-only until restored
- Root branch: A branch with no parent (typically named
main
orproduction
). The only type of branch from which snapshots can be created - Operations: Backend operations that return operation IDs you must poll to completion
Pattern-specific terminology
- Active branch: The root branch that serves as your agent's production database (though technically replaced during restores). Connection string never changes even when data is replaced via restore. Preview branches may be created alongside for temporary exploration.
- Version: A saved database state captured as a snapshot, which may also be referred to a checkpoint or edit. Users create and restore versions through your application interface.
- Orphaned branch: Created when restoring with
finalize_restore: true
. The previous active branch becomes orphaned (disconnected from compute) and is renamed tobranch-name (old)
. Can be safely deleted after verifying the restore. - Preview branch: Temporary branch created from a snapshot for safe exploration, to preview a version
API quick reference
Operation | Endpoint | Description |
---|---|---|
Create snapshot | POST /api/v2/projects/{project_id}/branches/{branch_id}/snapshot | Save current database state as a new version |
Restore snapshot | POST /api/v2/projects/{project_id}/snapshots/{snapshot_id}/restore | Restore database to a previous version |
List snapshots | GET /api/v2/projects/{project_id}/snapshots | Get all available versions |
Delete snapshot | DELETE /api/v2/projects/{project_id}/snapshots/{snapshot_id} | Remove a saved version |
Update snapshot | PATCH /api/v2/projects/{project_id}/snapshots/{snapshot_id} | Rename a version |
Poll operation | GET /api/v2/projects/{project_id}/operations/{operation_id} | Check restore status |
List branches (for cleanup) | GET /api/v2/projects/{project_id}/branches | Find orphaned branches to clean up |
Implementation checklist
- Create one Neon project per agent project
- Designate the root branch (main/production) as the "active" branch
- Store the active branch connection string and branch ID
- Create snapshots at key points to save database versions
- For rollbacks: restore with
finalize_restore: true
and settarget_branch_id
to the root branch ID - After a rollback, update your stored active branch ID
- For previews: restore with
finalize_restore: false
to create temporary branches - Poll all operation IDs to terminal states before connecting
- Implement a cleanup strategy: set snapshot expiration dates and delete orphaned branches
Best practices
- Set
target_branch_id
for rollbacks: When restoring to the active branch, always specifytarget_branch_id
to prevent accidental restores - Poll operations: Wait for terminal states before connecting to the database
- Snapshot naming: Use conventions like
snapshot-{GIT_SHA}-{TIMESTAMP}
or maintain sequential version numbers - Cleanup strategy: Set
expires_at
on temporary snapshots and preview branches. Delete orphaned branches (e.g.,production (old)
) created during restores - Version metadata: Keep version metadata separate to preserve audit trail across restores
FAQ
- —Why must I poll operations after restore?
- With
finalize_restore: true
, Neon moves compute resources to the new state. Until operations complete, connections still point to the old compute. - —What happens to my active branch when I restore?
- When restoring with
finalize_restore: true
, your current active branch becomes orphaned (disconnected from the compute endpoint) and is renamed with "(old)" appended. This orphaned branch preserves your pre-restore state temporarily, but you should delete it after verifying the restore to avoid storage costs. - —What if we need multiple preview environments?
- Restore different snapshots to new branches using
finalize_restore: false
. Each restore creates a new branch with its own connection string. - —Why use snapshots instead of branches for versioning?
- Snapshots: Restoring a snapshot onto your active branch (
finalize_restore: true
) replaces the data but keeps the same database connection string. This is ideal for production rollbacks. - Branches: Creating a new branch always generates a new connection string, which would require reconfiguring your application for every version change. Branches also create dependency chains that can complicate deletion.
Summary
The active branch pattern with Neon snapshots provides a simple, reliable versioning solution for AI agent and codegen platforms. By keeping database connection strings stable through the restore mechanism and using snapshots to implement version control, you get stable connection strings for your main database, instant rollbacks to previous versions, and the flexibility to create preview branches when needed. The implementation is straightforward: create snapshots to save versions, restore with finalize_restore: true
to the active branch for rollbacks, or with finalize_restore: false
for preview branches. Always poll operations to completion before connecting. See the demo repository for a complete example.
Need help?
Join our Discord Server to ask questions or see what others are doing with Neon. For paid plan support options, see Support.