This workflow corresponds to n8n.io template #9369 — we link there as the canonical source.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Simple Table as Cache",
"nodes": [
{
"id": "ae9f420d-1e6b-48d7-a064-7486ddd05f5f",
"name": "When Executed by Another Workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"notes": "To write set the value to true.\nTTL is in milliseconds",
"position": [
-16,
-96
],
"parameters": {
"workflowInputs": {
"values": [
{
"name": "trueToWrite",
"type": "boolean"
},
{
"name": "cacheKey"
},
{
"name": "writeValue",
"type": "any"
},
{
"name": "writeTTLms",
"type": "number"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "46e0b000-b24c-467a-9b1a-c50eebbf4b57",
"name": "Upsert row(s)",
"type": "n8n-nodes-base.dataTable",
"position": [
656,
-192
],
"parameters": {
"columns": {
"value": {
"key": "={{ $json.cacheKey }}",
"ttl": "={{ $now.plus($json.writeTTLms > 0 ? $json.writeTTLms : 10000) }}",
"value": "={{ JSON.stringify($json.writeValue) }}"
},
"schema": [
{
"id": "key",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "key",
"defaultMatch": false
},
{
"id": "value",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "value",
"defaultMatch": false
},
{
"id": "ttl",
"type": "dateTime",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "ttl",
"defaultMatch": false
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"filters": {
"conditions": [
{
"keyName": "key",
"keyValue": "={{ $json.cacheKey }}"
}
]
},
"operation": "upsert",
"dataTableId": {
"__rl": true,
"mode": "list",
"value": "RO0ChvBMPizGf4bb",
"cachedResultUrl": "/projects/IMwUHp4OzaUhAJNR/datatables/RO0ChvBMPizGf4bb",
"cachedResultName": "cache"
}
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "be98ade2-7c04-4ac8-863a-7b9fbc01f57a",
"name": "Return Value",
"type": "n8n-nodes-base.set",
"position": [
880,
-192
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={{ $('When Executed by Another Workflow').item.json.writeValue }}"
},
"typeVersion": 3.4
},
{
"id": "9d06984a-4645-4306-83b6-9d998d60f50c",
"name": "Action Write Value",
"type": "n8n-nodes-base.noOp",
"position": [
432,
-192
],
"parameters": {},
"typeVersion": 1
},
{
"id": "3341e665-6c30-4ccc-b598-5eaa2443a9e4",
"name": "Get row(s)",
"type": "n8n-nodes-base.dataTable",
"position": [
656,
0
],
"parameters": {
"filters": {
"conditions": [
{
"keyName": "key",
"keyValue": "={{ $json.cacheKey }}"
}
]
},
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "list",
"value": "RO0ChvBMPizGf4bb",
"cachedResultUrl": "/projects/IMwUHp4OzaUhAJNR/datatables/RO0ChvBMPizGf4bb",
"cachedResultName": "cache"
}
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "96ae4530-e574-467a-95a9-095882c0161a",
"name": "If Expired Cache",
"type": "n8n-nodes-base.if",
"position": [
1104,
80
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "03943791-3760-46e3-ac12-028fdbd5b440",
"operator": {
"type": "dateTime",
"operation": "after"
},
"leftValue": "={{ $now }}",
"rightValue": "={{ DateTime.fromISO($json.ttl) }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "9254b2a6-d265-4c3e-931f-9487ed2dccf5",
"name": "Read Action",
"type": "n8n-nodes-base.noOp",
"position": [
432,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "0a656812-609f-4b51-be4c-d364b42273a6",
"name": "Check Action Type",
"type": "n8n-nodes-base.if",
"position": [
208,
-96
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cfe96997-a2c7-44d1-a270-798fa5fdbeee",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ !!$json.trueToWrite }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "f20a262f-1880-4b20-ab84-5849a2846a5d",
"name": "If not in Cache",
"type": "n8n-nodes-base.if",
"position": [
880,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a3e38df9-cfdc-4a30-943f-34b42c37eb63",
"operator": {
"type": "object",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "1ca6a00f-3363-4252-aa72-1779dbc9e1e1",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-672,
-96
],
"parameters": {
"width": 592,
"height": 320,
"content": "## IMPORTANT READ THIS!\nThis flow ***Requires*** the Beta version of N8N Tables to function.\nYou will need a table called \"cache\" <- All lowercase.\nThat table will need the following columns, again all lowercase:\n1. key: string\n2. ttl: datetime\n3. value: string\n\n\nPlease create this table and update the two datatable nodes to point to this table."
},
"typeVersion": 1
},
{
"id": "ca4d32ac-31f9-4656-ba7f-b77fb0410cb1",
"name": "No cache found, use error detection to detect this.",
"type": "n8n-nodes-base.stopAndError",
"position": [
1328,
-96
],
"parameters": {
"errorMessage": "No Entry or Expired Cache Item."
},
"typeVersion": 1
},
{
"id": "fa7762d4-0cce-4b73-bc58-f8569e8a9b28",
"name": "Return Value from Cache",
"type": "n8n-nodes-base.set",
"position": [
1328,
128
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={{ JSON.parse($('Get row(s)').item.json.value) }}"
},
"typeVersion": 3.4
},
{
"id": "d1cbe73a-2810-4410-9e21-eb51e85a8b45",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
-752
],
"parameters": {
"color": 5,
"width": 960,
"height": 544,
"content": "## How To Use\nCall this node via the Execute Sub-Flow node with the specified inputs.\n(Optional) You can also \"activate\" this flow to enable hourly cleaning of the cache table to help keep data sizes down.\n\n## Output\nThis subflow will return the JSON.parse() representation of the string currently stored at the key in the cache table. This will be the same value written by the cache write input to this node if it has not expired.\n\nIf no value is found in the cache table for the input key, then a error is thrown. Listen for this error by setting your error response mode to be <On Error: \"Continue (Using Error Output)\"> in the node settings.\n\n## Inputs\n#### Action Read Required\n\"cacheKey\": <any string>\n\n#### Action Write Required\n\"cacheKey\": <any string>\n\"trueToWrite\": true,\n\"writeValue\": <any value including null. You are limited to data size of the table string field so don't stuff 20MB of JSON here.>,\n\"writeTTLms\": <optional, any number above 0 as milliseconds. Defaults to 10000>"
},
"typeVersion": 1
},
{
"id": "fedbcf7d-d7e2-4920-b57c-8b2e026c9dbe",
"name": "1 Hour Clean for Cache Table",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-16,
112
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "8e086bcf-d753-4187-b93c-ed2406d600cb",
"name": "Drop all rows with expired cache entires",
"type": "n8n-nodes-base.dataTable",
"position": [
208,
112
],
"parameters": {
"filters": {
"conditions": [
{
"keyName": "ttl",
"keyValue": "={{ $now }}",
"condition": "lt"
}
]
},
"options": {
"dryRun": true
},
"operation": "deleteRows",
"dataTableId": {
"__rl": true,
"mode": "list",
"value": "RO0ChvBMPizGf4bb",
"cachedResultUrl": "/projects/IMwUHp4OzaUhAJNR/datatables/RO0ChvBMPizGf4bb",
"cachedResultName": "cache"
}
},
"typeVersion": 1
}
],
"connections": {
"Get row(s)": {
"main": [
[
{
"node": "If not in Cache",
"type": "main",
"index": 0
}
]
]
},
"Read Action": {
"main": [
[
{
"node": "Get row(s)",
"type": "main",
"index": 0
}
]
]
},
"Upsert row(s)": {
"main": [
[
{
"node": "Return Value",
"type": "main",
"index": 0
}
]
]
},
"If not in Cache": {
"main": [
[
{
"node": "No cache found, use error detection to detect this.",
"type": "main",
"index": 0
}
],
[
{
"node": "If Expired Cache",
"type": "main",
"index": 0
}
]
]
},
"If Expired Cache": {
"main": [
[
{
"node": "No cache found, use error detection to detect this.",
"type": "main",
"index": 0
}
],
[
{
"node": "Return Value from Cache",
"type": "main",
"index": 0
}
]
]
},
"Check Action Type": {
"main": [
[
{
"node": "Action Write Value",
"type": "main",
"index": 0
}
],
[
{
"node": "Read Action",
"type": "main",
"index": 0
}
]
]
},
"Action Write Value": {
"main": [
[
{
"node": "Upsert row(s)",
"type": "main",
"index": 0
}
]
]
},
"1 Hour Clean for Cache Table": {
"main": [
[
{
"node": "Drop all rows with expired cache entires",
"type": "main",
"index": 0
}
]
]
},
"When Executed by Another Workflow": {
"main": [
[
{
"node": "Check Action Type",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
If you're in need of a quick and dirty cache that doesn't need anything other than the current version of N8N, boy do I have a dodgy script for you to try!
Source: https://n8n.io/workflows/9369/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Agendamiento. Uses n8n-nodes-evolution-api, redis, dataTable, executeWorkflowTrigger. Event-driven trigger; 60 nodes.
Prevent concurrent workflow runs using Redis. Uses executeWorkflowTrigger, manualTrigger, stickyNote, executeWorkflow. Event-driven trigger; 43 nodes.
This workflow sets a small "lock" value in Redis so that only one copy of a long job can run at the same time. If another trigger fires while the job is still busy, the workflow sees the lock, stops e
This template facilitates the transfer of a folder, along with all its files and subfolders, within a Nextcloud instance. The Nextcloud user must have access to both the source and destination folders
> v2: Now it can read multiple types of LLM usages. Better dynamic approach for reading model usage.