Skip to main content
RapidDev - Software Development Agency
n8n-tutorial

How to Use Static Data in n8n to Persist State Between Executions

Static data in n8n persists between workflow executions using $getWorkflowStaticData('global') for workflow-wide data or $getWorkflowStaticData('node') for node-specific data. It works like a simple key-value store — set properties on the returned object and they automatically save when the execution completes. Use it for counters, timestamps, deduplication, and tracking state across runs.

What you'll learn

  • How to read and write workflow-level static data with $getWorkflowStaticData
  • How to use node-level static data for isolated persistence
  • Common use cases including deduplication, counters, and last-run timestamps
  • How to clear and reset static data when needed
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read10-15 minutesn8n 1.0+, all installation methodsMarch 2026RapidDev Engineering Team
TL;DR

Static data in n8n persists between workflow executions using $getWorkflowStaticData('global') for workflow-wide data or $getWorkflowStaticData('node') for node-specific data. It works like a simple key-value store — set properties on the returned object and they automatically save when the execution completes. Use it for counters, timestamps, deduplication, and tracking state across runs.

How to Use Static Data in n8n

Most n8n workflow data is ephemeral — it exists only during the current execution and is gone when the workflow finishes. But sometimes you need to remember things between executions, such as the last processed record ID, a running counter, or a timestamp of the previous run. Static data solves this by providing a persistent key-value store that survives between executions. It is stored in the n8n database alongside your workflow definition.

Prerequisites

  • A running n8n instance with the workflow editor open
  • Basic familiarity with the Code node in n8n
  • Understanding of JavaScript objects and property access

Step-by-step guide

1

Access global static data in a Code node

Add a Code node to your workflow and use $getWorkflowStaticData('global') to get a reference to the global static data object. This object persists across all executions of the workflow. Any properties you set on it are automatically saved when the execution completes. On the first execution, the object is empty.

typescript
1// Get the global static data object
2const staticData = $getWorkflowStaticData('global');
3
4// Read a value (undefined on first run)
5const lastRun = staticData.lastRunAt;
6
7// Write a value (saved automatically at end of execution)
8staticData.lastRunAt = new Date().toISOString();
9staticData.executionCount = (staticData.executionCount || 0) + 1;
10
11return [{
12 json: {
13 lastRun: lastRun || 'First execution',
14 currentRun: staticData.lastRunAt,
15 totalExecutions: staticData.executionCount
16 }
17}];

Expected result: On the first run, lastRun shows 'First execution'. On subsequent runs, it shows the timestamp of the previous execution. The executionCount increments by 1 each time.

2

Use node-level static data for isolation

If you want static data that is scoped to a specific node rather than shared across the whole workflow, use $getWorkflowStaticData('node'). Each node gets its own isolated data store. This is useful when multiple Code nodes need their own independent counters or state without interfering with each other.

typescript
1// Get node-specific static data
2const nodeData = $getWorkflowStaticData('node');
3
4// This data is isolated to this specific node
5nodeData.processedIds = nodeData.processedIds || [];
6
7// Track processed items
8const items = $input.all();
9const newItems = [];
10
11for (const item of items) {
12 if (!nodeData.processedIds.includes(item.json.id)) {
13 nodeData.processedIds.push(item.json.id);
14 newItems.push(item);
15 }
16}
17
18return newItems.length > 0 ? newItems : [{ json: { message: 'No new items' } }];

Expected result: Only items with IDs not seen in previous executions are passed through. Previously processed IDs are filtered out.

3

Implement deduplication with static data

One of the most common uses for static data is preventing duplicate processing. Store the IDs of processed items in static data and check against them on each run. This is essential for polling workflows that might receive the same data multiple times, such as checking an API endpoint every 5 minutes.

typescript
1const staticData = $getWorkflowStaticData('global');
2staticData.processedIds = staticData.processedIds || [];
3
4const items = $input.all();
5const newItems = [];
6
7for (const item of items) {
8 const id = item.json.id;
9
10 if (!staticData.processedIds.includes(id)) {
11 staticData.processedIds.push(id);
12 newItems.push(item);
13 }
14}
15
16// Keep only the last 1000 IDs to prevent unbounded growth
17if (staticData.processedIds.length > 1000) {
18 staticData.processedIds = staticData.processedIds.slice(-1000);
19}
20
21return newItems.length > 0
22 ? newItems
23 : [{ json: { message: 'All items already processed', skipped: items.length } }];

Expected result: Duplicate items are filtered out. Only new items that were not seen in previous executions pass through to the next node.

4

Track the last processed timestamp for incremental fetching

For workflows that poll an API or database for new records, store the timestamp of the last successfully processed record. On the next run, use this timestamp to fetch only records created after that point. This is more efficient than fetching all records and filtering duplicates.

typescript
1const staticData = $getWorkflowStaticData('global');
2
3// Get the last processed timestamp (default to 24 hours ago)
4const lastProcessed = staticData.lastProcessedAt
5 || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
6
7// Use this timestamp in your API call or query
8// For example, set this as a parameter in the HTTP Request node:
9// URL: https://api.example.com/records?since={{ $json.since }}
10
11const items = $input.all();
12
13if (items.length > 0) {
14 // Find the most recent timestamp from the results
15 const timestamps = items.map(i => new Date(i.json.createdAt).getTime());
16 const maxTimestamp = new Date(Math.max(...timestamps)).toISOString();
17
18 // Update static data for next run
19 staticData.lastProcessedAt = maxTimestamp;
20}
21
22return [{
23 json: {
24 since: lastProcessed,
25 itemsFound: items.length,
26 nextRunWillFetchFrom: staticData.lastProcessedAt || lastProcessed
27 }
28}];

Expected result: Each execution fetches only records newer than the last successfully processed timestamp, avoiding duplicate processing and reducing API load.

5

Clear static data when needed

To reset static data, set the object properties to their initial values or delete them. You can create a separate workflow or a manual trigger that clears the static data when you need a fresh start. This is useful when testing or when you need to reprocess all items from scratch.

typescript
1// Clear all global static data
2const staticData = $getWorkflowStaticData('global');
3
4// Option 1: Delete specific keys
5delete staticData.lastProcessedAt;
6delete staticData.processedIds;
7delete staticData.executionCount;
8
9// Option 2: Clear all keys
10for (const key of Object.keys(staticData)) {
11 delete staticData[key];
12}
13
14return [{
15 json: {
16 message: 'Static data cleared',
17 clearedAt: new Date().toISOString(),
18 remainingKeys: Object.keys(staticData)
19 }
20}];

Expected result: All static data properties are removed. The next workflow execution starts with an empty static data object, as if it were the first run.

Complete working example

static-data-polling-workflow.js
1// Code Node: Complete polling workflow with static data
2// Fetches new records, deduplicates, and tracks state
3//
4// Connect this Code node after a Schedule Trigger
5// and before your processing nodes.
6
7const staticData = $getWorkflowStaticData('global');
8
9// Initialize static data on first run
10if (!staticData.initialized) {
11 staticData.initialized = true;
12 staticData.lastProcessedAt = new Date(
13 Date.now() - 24 * 60 * 60 * 1000
14 ).toISOString();
15 staticData.processedIds = [];
16 staticData.totalProcessed = 0;
17 staticData.errorCount = 0;
18}
19
20// Get items from input (connected to HTTP Request or DB node)
21const items = $input.all();
22const newItems = [];
23
24for (const item of items) {
25 const id = String(item.json.id);
26
27 // Skip already-processed items
28 if (staticData.processedIds.includes(id)) {
29 continue;
30 }
31
32 // Mark as processed
33 staticData.processedIds.push(id);
34 staticData.totalProcessed += 1;
35
36 // Add metadata
37 newItems.push({
38 json: {
39 ...item.json,
40 _processedAt: new Date().toISOString(),
41 _batchId: staticData.totalProcessed
42 }
43 });
44}
45
46// Update last processed timestamp
47if (newItems.length > 0) {
48 const latest = newItems
49 .map(i => i.json.createdAt || i.json.updatedAt)
50 .filter(Boolean)
51 .sort()
52 .pop();
53
54 if (latest) {
55 staticData.lastProcessedAt = latest;
56 }
57}
58
59// Trim processed IDs to prevent unbounded growth
60const MAX_IDS = 2000;
61if (staticData.processedIds.length > MAX_IDS) {
62 staticData.processedIds = staticData.processedIds.slice(-MAX_IDS);
63}
64
65// Return new items or a status message
66if (newItems.length > 0) {
67 return newItems;
68}
69
70return [{
71 json: {
72 message: 'No new items to process',
73 lastProcessedAt: staticData.lastProcessedAt,
74 totalHistoricallyProcessed: staticData.totalProcessed,
75 trackedIds: staticData.processedIds.length
76 }
77}];

Common mistakes when using Static Data in n8n to Persist State Between Executions

Why it's a problem: Storing large arrays or objects in static data without size limits

How to avoid: Add a maximum size check and trim old entries. For example, keep only the last 1000 processed IDs: if (staticData.ids.length > 1000) staticData.ids = staticData.ids.slice(-1000).

Why it's a problem: Expecting static data to persist after a failed execution

How to avoid: Static data changes are only saved when the execution completes successfully. If the workflow fails, changes made during that execution are discarded. Design your logic to handle this.

Why it's a problem: Using static data for complex relational data that belongs in a database

How to avoid: Static data is a simple key-value store. For complex data relationships, use a PostgreSQL or MySQL node to read and write to a proper database.

Why it's a problem: Renaming a node that uses node-level static data, losing the stored state

How to avoid: Node-level static data is keyed by node name. If you rename the node, it starts with fresh empty data. Copy the old static data to the new key or use global static data instead.

Best practices

  • Always limit the size of arrays stored in static data to prevent unbounded growth over time
  • Use global static data for workflow-level state and node static data for node-specific counters
  • Initialize static data with default values on first access to avoid undefined errors
  • Create a dedicated workflow or manual trigger to reset static data when you need to start fresh
  • Do not store large objects or binary data in static data — it is designed for small pieces of state like IDs and timestamps
  • Remember that static data is only saved when the execution completes successfully — failed executions do not persist static data changes
  • Document what each static data key is used for with comments in your Code node

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have an n8n workflow that polls an API every 5 minutes. How do I use $getWorkflowStaticData to track which records have already been processed and avoid duplicates across executions?

n8n Prompt

Add a Code node that uses static data to keep a running count of processed items and stores the timestamp of the last successfully processed record. Include a size limit on the stored IDs array.

Frequently asked questions

Where is static data stored physically?

Static data is stored in the n8n database alongside the workflow definition. If you use SQLite, it is in the .n8n directory. If you use PostgreSQL, it is in the workflow_entity table as part of the workflow JSON.

Is static data shared between different workflows?

No. Each workflow has its own isolated static data. Global static data is shared across all nodes within the same workflow, but not between different workflows.

What happens to static data if I delete and reimport a workflow?

Static data is part of the workflow entity in the database. If you delete the workflow, its static data is also deleted. Exported workflow JSON does not include static data, so importing the workflow starts with empty static data.

Can I view static data without running the workflow?

There is no built-in UI to view static data. You can add a Code node that reads and returns the static data object, or query the n8n database directly to inspect the staticData field in the workflow_entity table.

Is there a size limit for static data?

There is no enforced size limit, but static data is stored as JSON in the database. Very large static data objects can slow down workflow loading and execution. Keep it under a few megabytes and store larger data in an external database.

Can I use static data in expressions outside of Code nodes?

No. The $getWorkflowStaticData function is only available inside Code nodes and Function nodes. To use static data values in other nodes, read the static data in a Code node and pass the values as output items that downstream nodes can reference with expressions.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.