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
Access global static data in a Code node
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.
1// Get the global static data object2const staticData = $getWorkflowStaticData('global');34// Read a value (undefined on first run)5const lastRun = staticData.lastRunAt;67// Write a value (saved automatically at end of execution)8staticData.lastRunAt = new Date().toISOString();9staticData.executionCount = (staticData.executionCount || 0) + 1;1011return [{12 json: {13 lastRun: lastRun || 'First execution',14 currentRun: staticData.lastRunAt,15 totalExecutions: staticData.executionCount16 }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.
Use node-level static data for isolation
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.
1// Get node-specific static data2const nodeData = $getWorkflowStaticData('node');34// This data is isolated to this specific node5nodeData.processedIds = nodeData.processedIds || [];67// Track processed items8const items = $input.all();9const newItems = [];1011for (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}1718return 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.
Implement deduplication with static data
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.
1const staticData = $getWorkflowStaticData('global');2staticData.processedIds = staticData.processedIds || [];34const items = $input.all();5const newItems = [];67for (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}1516// Keep only the last 1000 IDs to prevent unbounded growth17if (staticData.processedIds.length > 1000) {18 staticData.processedIds = staticData.processedIds.slice(-1000);19}2021return newItems.length > 022 ? newItems23 : [{ 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.
Track the last processed timestamp for incremental fetching
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.
1const staticData = $getWorkflowStaticData('global');23// Get the last processed timestamp (default to 24 hours ago)4const lastProcessed = staticData.lastProcessedAt5 || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();67// Use this timestamp in your API call or query8// For example, set this as a parameter in the HTTP Request node:9// URL: https://api.example.com/records?since={{ $json.since }}1011const items = $input.all();1213if (items.length > 0) {14 // Find the most recent timestamp from the results15 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 run19 staticData.lastProcessedAt = maxTimestamp;20}2122return [{23 json: {24 since: lastProcessed,25 itemsFound: items.length,26 nextRunWillFetchFrom: staticData.lastProcessedAt || lastProcessed27 }28}];Expected result: Each execution fetches only records newer than the last successfully processed timestamp, avoiding duplicate processing and reducing API load.
Clear static data when needed
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.
1// Clear all global static data2const staticData = $getWorkflowStaticData('global');34// Option 1: Delete specific keys5delete staticData.lastProcessedAt;6delete staticData.processedIds;7delete staticData.executionCount;89// Option 2: Clear all keys10for (const key of Object.keys(staticData)) {11 delete staticData[key];12}1314return [{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
1// Code Node: Complete polling workflow with static data2// Fetches new records, deduplicates, and tracks state3//4// Connect this Code node after a Schedule Trigger5// and before your processing nodes.67const staticData = $getWorkflowStaticData('global');89// Initialize static data on first run10if (!staticData.initialized) {11 staticData.initialized = true;12 staticData.lastProcessedAt = new Date(13 Date.now() - 24 * 60 * 60 * 100014 ).toISOString();15 staticData.processedIds = [];16 staticData.totalProcessed = 0;17 staticData.errorCount = 0;18}1920// Get items from input (connected to HTTP Request or DB node)21const items = $input.all();22const newItems = [];2324for (const item of items) {25 const id = String(item.json.id);26 27 // Skip already-processed items28 if (staticData.processedIds.includes(id)) {29 continue;30 }31 32 // Mark as processed33 staticData.processedIds.push(id);34 staticData.totalProcessed += 1;35 36 // Add metadata37 newItems.push({38 json: {39 ...item.json,40 _processedAt: new Date().toISOString(),41 _batchId: staticData.totalProcessed42 }43 });44}4546// Update last processed timestamp47if (newItems.length > 0) {48 const latest = newItems49 .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}5859// Trim processed IDs to prevent unbounded growth60const MAX_IDS = 2000;61if (staticData.processedIds.length > MAX_IDS) {62 staticData.processedIds = staticData.processedIds.slice(-MAX_IDS);63}6465// Return new items or a status message66if (newItems.length > 0) {67 return newItems;68}6970return [{71 json: {72 message: 'No new items to process',73 lastProcessedAt: staticData.lastProcessedAt,74 totalHistoricallyProcessed: staticData.totalProcessed,75 trackedIds: staticData.processedIds.length76 }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.
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?
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation