# How the SimStudioAI Workflow Persistence Layer Saves and Loads Workflows

> Discover how the simstudioai/sim workflow persistence layer ensures reliable saving and loading of workflows. Learn about normalization, Zod validation, and Drizzle ORM integration.

- Repository: [Sim/sim](https://github.com/simstudioai/sim)
- Tags: internals
- Published: 2026-05-02

---

**The `@sim/workflow-persistence` package exposes two primary functions—`saveWorkflow` and `loadWorkflow`—that handle normalization, Zod schema validation, and database operations to persist workflow definitions and runtime states through Drizzle ORM.**

Within the `simstudioai/sim` repository, the workflow persistence layer isolates storage concerns from the editor UI and execution engine. It transforms in-memory workflow objects—complete with blocks, connections, and nested sub-flows—into normalized JSON blobs suitable for database storage, and reverses the process during retrieval to reconstruct fully functional workflows.

## Core Persistence API

### Persisting Workflows with `saveWorkflow`

The [`saveWorkflow`](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/save.ts) function in [`packages/workflow-persistence/src/save.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/save.ts) handles the complete storage lifecycle when a workflow is modified or an execution run completes.

The process follows four distinct stages:

1. **Normalization**: The runtime workflow object passes through helpers in [[`subflow-helpers.ts`](https://github.com/simstudioai/sim/blob/main/subflow-helpers.ts)](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/subflow-helpers.ts) and [[`subblocks.ts`](https://github.com/simstudioai/sim/blob/main/subblocks.ts)](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/subblocks.ts) to flatten nested sub-flows and strip transient runtime data, producing a minimal storage representation.

2. **Serialization and Validation**: The normalized structure is stringified to JSON and strictly validated against the **Zod** schema defined in [[`types.ts`](https://github.com/simstudioai/sim/blob/main/types.ts)](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/types.ts), ensuring type safety before database interaction.

3. **Database Upsert**: Using **Drizzle ORM** from `@sim/db`, the function performs an atomic upsert (`insert … on conflict do update`) into the `workflow_persistence` table. The row key is the workflow **UUID** generated via `@sim/utils/id`.

4. **Cache Refresh**: Upon successful commit, the in-memory cache (`@sim/utils/cache`) updates to ensure subsequent reads return the latest version without additional database round-trips.

### Retrieving Workflows with `loadWorkflow`

The [`loadWorkflow`](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/load.ts) function in [`packages/workflow-persistence/src/load.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/load.ts) rehydrates workflows for editing or execution through a systematic reconstruction process.

The retrieval pipeline executes these steps:

- **Database Fetch**: Retrieves the JSON blob from the `workflow_persistence` table using the workflow UUID.
- **Schema Validation**: Parses the JSON and validates it against `WorkflowPersistedSchema` to detect data corruption or version mismatches.
- **Sub-Flow Reconstruction**: Invokes `reconstructSubflows` from [`subflow-helpers.ts`](https://github.com/simstudioai/sim/blob/main/subflow-helpers.ts) to restore nested workflow structures from their flattened storage format.
- **Runtime Hydration**: Re-attaches transient fields such as live state containers and unique runtime IDs that are not stored but required by the executor in `apps/sim/executor`.

## Data Transformation and Integrity

### Bidirectional Sub-Flow Handling

Nested workflows are stored as independent JSON fragments to prevent duplication and circular reference issues. The persistence layer uses isolated helpers to manage the bidirectional conversion between runtime objects and storage-ready fragments, ensuring that sub-flows are never duplicated in the database.

### Schema Enforcement with Zod

All persisted data conforms to the centralized **Zod** schema in [`types.ts`](https://github.com/simstudioai/sim/blob/main/types.ts). Because the persistence package lives in the `packages/` boundary, it safely imports Zod without exposing validation logic to API routes, maintaining strict separation of concerns and guaranteeing that any structural change to workflows is caught at compile-time and runtime.

## Database Strategy and Caching

The persistence layer relies on **Drizzle ORM** for type-safe SQL operations against the `workflow_persistence` table. The **upsert** pattern eliminates the need for callers to decide between "create" versus "update" logic, as the database handles row existence checks automatically based on the workflow UUID. After every successful save, the cache layer (`@sim/utils/cache`) refreshes its entry, minimizing database load during rapid editing sessions or frequent execution cycles.

## Implementation Examples

The following patterns demonstrate how the editor and executor consume the persistence layer.

**Saving from the Workflow Editor**:

```tsx
import { saveWorkflow } from '@sim/workflow-persistence'
import { useWorkflowStore } from '@/stores/workflows/store'

async function onSave() {
  const workflow = useWorkflowStore.getState().currentWorkflow
  await saveWorkflow(workflow.id, workflow)
}

```

**Loading for Execution**:

```tsx
import { loadWorkflow } from '@sim/workflow-persistence'
import { executeWorkflow } from '@/executor/engine'

async function runWorkflow(workflowId: string) {
  const workflow = await loadWorkflow(workflowId)
  const result = await executeWorkflow(workflow)
  await saveWorkflow(workflow.id, result.updatedWorkflow)
}

```

## Key Source Files

| File | Responsibility | Source |
|------|---------------|--------|
| [`packages/workflow-persistence/src/save.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/save.ts) | `saveWorkflow` implementation with normalization and upsert logic | [save.ts](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/save.ts) |
| [`packages/workflow-persistence/src/load.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/load.ts) | `loadWorkflow` implementation with reconstruction and hydration | [load.ts](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/load.ts) |
| [`packages/workflow-persistence/src/types.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/types.ts) | Central Zod schemas for type-safe persistence | [types.ts](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/types.ts) |
| [`packages/workflow-persistence/src/subflow-helpers.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/subflow-helpers.ts) | Utilities for flattening and reconstructing sub-flows | [subflow-helpers.ts](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/subflow-helpers.ts) |
| [`packages/workflow-persistence/src/subblocks.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/subblocks.ts) | Helpers for extracting block-level persistence details | [subblocks.ts](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/subblocks.ts) |
| [`packages/workflow-persistence/src/index.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/index.ts) | Public API exports | [index.ts](https://github.com/simstudioai/sim/blob/main/packages/workflow-persistence/src/index.ts) |

## Summary

- The `@sim/workflow-persistence` package centralizes all workflow durability logic through `saveWorkflow` and `loadWorkflow`.
- **Normalization** in [`save.ts`](https://github.com/simstudioai/sim/blob/main/save.ts) flattens sub-flows and strips transient state before Zod validation and Drizzle ORM upserts.
- **Reconstruction** in [`load.ts`](https://github.com/simstudioai/sim/blob/main/load.ts) validates stored JSON against `WorkflowPersistedSchema` and rebuilds nested structures using `reconstructSubflows`.
- Atomic **upsert** operations ensure idempotent saves without requiring create-or-update decision logic in callers.
- An integrated **cache** layer minimizes database load by refreshing in-memory entries immediately after writes.

## Frequently Asked Questions

### What database technology does the persistence layer use?

The layer uses **Drizzle ORM** via the internal `@sim/db` package to interact with the `workflow_persistence` table, performing standard SQL upsert operations to handle both new and existing workflow records atomically.

### How does the system handle nested sub-workflows without data duplication?

Sub-flows are extracted and normalized into independent JSON fragments during save, then reconstructed during load using utilities in [`subflow-helpers.ts`](https://github.com/simstudioai/sim/blob/main/subflow-helpers.ts). This prevents circular references and ensures each sub-workflow definition is stored only once.

### Why does the load process require runtime hydration?

Runtime hydration re-attaches ephemeral execution state—such as live state containers and unique runtime IDs—that are intentionally excluded from persistent storage to keep the database records lightweight and environment-agnostic.

### How does schema validation prevent corrupted data from reaching the application?

Every save and load operation validates data against strict **Zod** schemas defined in [`types.ts`](https://github.com/simstudioai/sim/blob/main/types.ts). This catches structural mismatches at runtime boundaries before corrupted data can propagate to the workflow editor or execution engine.