# How n8n Versions Workflows and Stores Them in the Database

> Discover how n8n versions workflows by storing immutable snapshots in the database. Learn about workflow history tables and active version tracking for production execution.

- Repository: [n8n - Workflow Automation/n8n](https://github.com/n8n-io/n8n)
- Tags: internals
- Published: 2026-02-24

---

**n8n stores every workflow change as an immutable snapshot in the `workflow_history` table while maintaining the current editable state in `WorkflowEntity`, using UUID-based `versionId` pointers and an `activeVersionId` to track which version executes in production.**

The n8n workflow automation platform implements a robust versioning system that persists every modification to your automations. According to the n8n-io/n8n source code, this system relies on a dual-table database architecture that separates the current working workflow from immutable historical snapshots, enabling precise version control, rollback capabilities, and efficient storage management.

## The Two-Table Architecture for Workflow Versioning

n8n’s versioning system splits workflow data across two distinct database entities: one for the current mutable state and one for immutable historical records.

### WorkflowEntity: The Current Workflow State

The **current workflow definition** lives in `packages/@n8n/db/src/entities/workflow-entity.ts`, which defines three critical versioning columns on lines 100-105:

- **`versionId`**: A UUID pointing to the latest stored version in history
- **`activeVersionId`**: A UUID pointing to the version currently used by executions (the "published" version)
- **`versionCounter`**: An incremental integer for UI ordering and display

When a user saves a draft or publishes a workflow, the system updates `versionId` to a new UUID and increments `versionCounter`. The `activeVersionId` only changes when a specific version is explicitly activated for production use.

### WorkflowHistory: Immutable Version Snapshots

Every historical version resides in `packages/@n8n/db/src/entities/workflow-history.ts` (lines 11-35). This entity stores:

- **`versionId`**: The primary key (UUID) linking back to `WorkflowEntity`
- **`nodes`**: Complete JSON definition of the workflow nodes
- **`connections`**: JSON mapping of node connections
- **`authors`**: Metadata about who made the change
- **`autosaved`**: Boolean flag distinguishing automatic saves from manual publishes

This table treats every row as immutable—once inserted, the snapshot never changes, creating a complete audit trail of workflow evolution.

## Creating New Workflow Versions

The `WorkflowHistoryService.saveVersion` method in [`packages/cli/src/workflows/workflow-history/workflow-history.service.ts`](https://github.com/n8n-io/n8n/blob/main/packages/cli/src/workflows/workflow-history/workflow-history.service.ts) (lines 27-35) handles persisting new versions. This method executes an `INSERT` operation for every save action, whether manual or automatic.

```typescript
await workflowHistoryService.saveVersion(
    user,                         // User object or string author name
    {
        versionId: uuid(),        // New UUID for the version
        nodes: workflow.nodes,
        connections: workflow.connections,
    },
    workflowId,                  // ID of the WorkflowEntity
    false,                       // autosaved = false for manual saves
);

```

Each call generates a new row in `workflow_history` with a unique `versionId`, while the parent `WorkflowEntity` updates its `versionId` pointer to match. This ensures the current workflow always references its latest historical snapshot.

## Querying Workflow Versions

Retrieving versions uses optimized queries that distinguish between metadata browsing and full content loading.

### Listing Version Metadata

For pagination and browsing, `WorkflowHistoryService.getList` (lines 28-59) queries only lightweight metadata, explicitly excluding the large JSON columns (`nodes` and `connections`):

```typescript
const versions = await workflowHistoryService.getList(
    user,
    workflowId,
    take = 20,   // pagination limit
    skip = 0,
);
// Returns: [{ versionId, authors, createdAt, name, description }, ...]

```

This approach prevents memory bloat when displaying version history in the UI.

### Fetching Complete Version Snapshots

To load a specific version for restoration or inspection, `WorkflowHistoryService.getVersion` (lines 62-85) retrieves the full workflow definition:

```typescript
const version = await workflowHistoryService.getVersion(
    user,
    workflowId,
    versionId,
    { includePublishHistory: true },
);
// version.nodes and version.connections contain the complete workflow JSON

```

This method looks up the row by composite key (`workflowId` + `versionId`) and optionally loads publish history relationships.

## Activating Specific Versions for Execution

Versioning separates editing from execution through the `activeVersionId` pointer. When you activate a specific version, the system updates `WorkflowEntity.activeVersionId` to reference that historical snapshot and emits a `workflow-version-updated` event.

```typescript
// API endpoint internal logic
await workflowsController.activate(workflowId, versionId);
// Updates WorkflowEntity.activeVersionId = versionId
// Emits workflow-version-updated event for downstream services

```

This design allows you to edit draft versions while maintaining a stable, published version for active executions.

## Pruning Old Versions to Manage Storage

To prevent unlimited database growth, n8n implements automatic compaction through `WorkflowHistoryRepository.deleteEarlierThanExceptCurrentAndActive` in `packages/@n8n/db/src/repositories/workflow-history.repository.ts` (lines 26-53).

```typescript
await workflowHistoryRepository.deleteEarlierThanExceptCurrentAndActive(
    new Date(Date.now() - 30 * DAY_MS), // cutoff date
    preserveNamedVersions = true,       // keep user-named versions
);

```

This method removes records older than the specified cutoff while preserving:
- The **current** version (referenced by `WorkflowEntity.versionId`)
- The **active** version (referenced by `WorkflowEntity.activeVersionId`)
- Any **named** versions (those with user-given names/labels)

Background services invoke this pruning logic to maintain performance while retaining critical history.

## Summary

- **Dual-table architecture**: `WorkflowEntity` stores current pointers while `workflow_history` stores immutable snapshots
- **UUID versioning**: Every save generates a new `versionId` linking the current entity to its historical record
- **Execution isolation**: The `activeVersionId` pointer determines which version runs in production, separate from the draft editing cycle
- **Optimized queries**: Listing operations exclude heavy JSON columns, while specific version lookups return complete node definitions
- **Automatic cleanup**: Background pruning removes old unnamed versions while preserving current, active, and named snapshots

## Frequently Asked Questions

### What is the difference between versionId and activeVersionId in n8n?

The `versionId` column in `WorkflowEntity` always points to the most recently saved version of a workflow, representing the current draft state. The `activeVersionId` specifically references the version that is currently "published" and used by execution engines. This separation allows you to save draft changes without affecting production workflows.

### How does n8n store workflow versions to enable rollback?

n8n stores every version as an immutable row in the `workflow_history` table containing complete `nodes` and `connections` JSON. When you request a rollback, the system retrieves the specific `versionId` from `WorkflowHistoryService.getVersion` and restores those JSON definitions to the current workflow entity, effectively reverting to that historical state.

### Does n8n automatically delete old workflow versions?

Yes, n8n includes automatic pruning through the `WorkflowHistoryRepository.deleteEarlierThanExceptCurrentAndActive` method. This background process removes versions older than a configurable threshold (typically 30 days) while preserving the current version, the active version, and any versions with user-assigned names.

### How can I retrieve a specific historical version of a workflow?

Use the `WorkflowHistoryService.getVersion` method, passing the `workflowId` and specific `versionId`. Set `includePublishHistory: true` in the options parameter if you need publication metadata. This returns the complete workflow definition including all nodes and connections stored in that snapshot.