This workflow corresponds to n8n.io template #9156 — we link there as the canonical source.
This workflow follows the Executecommand → Readwritefile recipe pattern — see all workflows that pair these two integrations.
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 →
{
"nodes": [
{
"id": "",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
112
],
"parameters": {
"color": 5,
"width": 1776,
"height": 336,
"content": "## 2. Restore Workflows from FTP"
},
"typeVersion": 1
},
{
"id": "",
"name": "SUCCESS email Credentials",
"type": "n8n-nodes-base.emailSend",
"disabled": true,
"position": [
1056,
-160
],
"parameters": {
"text": "=Credential restore from FTP \"{{ $('Init').first().json.customConfig.FTPName }}\" has been successfully executed \ud83e\udd73\n\nexitCode: {{ $json.exitCode }}\nstderr: {{ $json.stderr || \"no error\"}}\n\nSteps:\n\n1. Find FTP Last Backup: {{ $('Find Last Backup').item.json.ftpCredentialsPath }}\n\n2. Restore credentials:\n{{ $json.stdout }}",
"options": {
"appendAttribution": false
},
"subject": "=n8n SUCCESS: {{ $workflow.name }}",
"toEmail": "={{ $env.N8N_ADMIN_EMAIL }}",
"fromEmail": "admin <admin@example.com>",
"emailFormat": "text"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "",
"name": "List Credentials Folders",
"type": "n8n-nodes-base.ftp",
"position": [
-672,
48
],
"parameters": {
"path": "={{ $('Init').item.json.customConfig.FTP_BACKUP_FOLDER }}",
"operation": "list"
},
"credentials": {
"ftp": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "",
"name": "Start Restore",
"type": "n8n-nodes-base.manualTrigger",
"notes": "CRED 1 WF\u202f0",
"position": [
-1104,
48
],
"parameters": {},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "",
"name": "List Most Recent Workflows Folder",
"type": "n8n-nodes-base.ftp",
"position": [
224,
224
],
"parameters": {
"path": "={{ $('Find Last Backup').item.json.ftpWorkflowsPath }}",
"operation": "list"
},
"credentials": {
"ftp": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "",
"name": "Write Workflow Files To Disk",
"type": "n8n-nodes-base.readWriteFile",
"position": [
848,
224
],
"parameters": {
"options": {},
"fileName": "={{ $('Init').first().json.workflowConfig.PROJECT_ROOT_PATH }}/{{ $('Find Last Backup').item.json.latestBackupDate }}{{ $('Init').first().json.customConfig.workflows_temp_folder}}/{{ $json.name }}",
"operation": "write"
},
"typeVersion": 1
},
{
"id": "",
"name": "Write Credential Files To Disk",
"type": "n8n-nodes-base.readWriteFile",
"position": [
640,
-160
],
"parameters": {
"options": {},
"fileName": "={{ $('Init').first().json.workflowConfig.PROJECT_ROOT_PATH }}/{{ $('Find Last Backup').item.json.latestBackupDate }}{{ $('Init').first().json.customConfig.credentials_temp_folder}}/{{ $json.name }}",
"operation": "write"
},
"typeVersion": 1
},
{
"id": "",
"name": "SUCCESS email Workflows",
"type": "n8n-nodes-base.emailSend",
"disabled": true,
"position": [
1472,
224
],
"parameters": {
"text": "=Workflows\u2019 restore from FTP \"{{ $('Init').first().json.customConfig.FTPName }}\" has been successfully executed \ud83e\udd73\n\nexitCode: {{ $json.exitCode }}\nstderr: {{ $json.stderr || \"no error\"}}\n\nSteps:\n\n1. Find Last Backup: {{ $('Find Last Backup').item.json.ftpWorkflowsPath }}\n\n2. Exclude current workflow:\n{{ $('Exclude Current Workflow From Selection').item.json.stdout }}\n\n3. Restore workflows:\n{{ $('Restore Workflows').item.json.stdout }}",
"options": {
"appendAttribution": false
},
"subject": "=n8n SUCCESS: {{ $workflow.name }}",
"toEmail": "={{ $env.N8N_ADMIN_EMAIL }}",
"fromEmail": "admin <admin@example.com>",
"emailFormat": "text"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "",
"name": "ERROR:\u202fFind Most Recent Credentials Folder",
"type": "n8n-nodes-base.stopAndError",
"position": [
-256,
240
],
"parameters": {
"errorMessage": "={{ $json.error }}"
},
"typeVersion": 1
},
{
"id": "",
"name": "Download Workflow Files",
"type": "n8n-nodes-base.ftp",
"notes": "Ignore Credentials sub-workflow error",
"onError": "continueRegularOutput",
"position": [
432,
224
],
"parameters": {
"path": "={{ $json.path }}",
"options": {}
},
"credentials": {
"ftp": {
"name": "<your credential>"
}
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "",
"name": "Download Credential Files",
"type": "n8n-nodes-base.ftp",
"position": [
432,
-160
],
"parameters": {
"path": "={{ $json.path }}",
"options": {}
},
"credentials": {
"ftp": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "",
"name": "Filter out Credentials sub-folder",
"type": "n8n-nodes-base.filter",
"position": [
640,
224
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "",
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $binary.data }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "",
"name": "Exclude Current Workflow From Selection",
"type": "n8n-nodes-base.executeCommand",
"position": [
1056,
224
],
"parameters": {
"command": "=# Remove the current workflow file from restore folder to prevent self-restoration\n# Execute this AFTER writing files to disk but BEFORE import command\n\n# Get current workflow name and clean it\nWORKFLOW_NAME=\"{{ $workflow.name }}\"\nRESTORE_FOLDER=\"{{ $('Init').first().json.workflowConfig.PROJECT_ROOT_PATH }}/{{ $('Find Last Backup').item.json.latestBackupDate }}{{ $('Init').first().json.customConfig.workflows_temp_folder}}\"\n\n# Clean the workflow name (same logic as Clean Filename node)\nCLEANED_NAME=$(echo \"$WORKFLOW_NAME\" | sed 's/[<>:\"/\\\\|?*[:cntrl:]]/_/g' | sed 's/[[:space:].]\\+/_/g' | sed 's/_\\+/_/g' | sed 's/^_\\+//;s/_\\+$//')\n\necho \"\ud83d\udd0d Current workflow: $WORKFLOW_NAME\"\necho \"\ud83e\uddf9 Cleaned name: $CLEANED_NAME\"\necho \"\ud83d\udcc2 Restore folder: $RESTORE_FOLDER\"\n\n# Find and remove file matching the current workflow name\nif [ -f \"$RESTORE_FOLDER/${CLEANED_NAME}.json\" ]; then\n rm \"$RESTORE_FOLDER/${CLEANED_NAME}.json\"\n echo \"\u2705 Removed current workflow file: ${CLEANED_NAME}.json\"\n echo \"\u26a0\ufe0f This workflow will NOT be restored to prevent overwrite\"\nelse\n echo \"\u2139\ufe0f Current workflow file not found in restore folder (already safe)\"\nfi\n\n# Count remaining files\nFILE_COUNT=$(ls -1 \"$RESTORE_FOLDER\"/*.json 2>/dev/null | wc -l)\necho \"\ud83d\udcca Remaining workflows to restore: $FILE_COUNT\""
},
"typeVersion": 1
},
{
"id": "",
"name": "Restore Workflows",
"type": "n8n-nodes-base.executeCommand",
"position": [
1264,
224
],
"parameters": {
"command": "=n8n import:workflow --separate --input={{ $('Init').first().json.workflowConfig.PROJECT_ROOT_PATH }}/{{ $('Find Last Backup').item.json.latestBackupDate }}{{ $('Init').first().json.customConfig.workflows_temp_folder }}"
},
"typeVersion": 1
},
{
"id": "",
"name": "Restore Credentials",
"type": "n8n-nodes-base.executeCommand",
"position": [
848,
-160
],
"parameters": {
"command": "=n8n import:credentials --separate --input={{ $('Init').first().json.workflowConfig.PROJECT_ROOT_PATH }}/{{ $('Find Last Backup').item.json.latestBackupDate }}{{ $('Init').first().json.customConfig.credentials_temp_folder}}"
},
"typeVersion": 1
},
{
"id": "",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1168,
-768
],
"parameters": {
"color": 4,
"width": 1088,
"height": 1216,
"content": "## Start here\n### \u26a0 Warnings \u26a0\n\u2022 In case of full restore, restore Credentials first!\n\u2022 Any already existing credential or workflow will be overwritten\u2026\n\n### Instructions\n\u2022 Re-create your FTP credential (and optionnaly your SMTP one).\n\u2022 Manually copy this workflow to your new n8n instance.\n\u2022 Launch it and restore everything!\n\nCan be used to move your workflows to another instance \ud83d\udc4d\ud83c\udffb\n\n### Configure the \"Start Restore\" JSON according to your need\n```\n[\n {\n \"credentials\": true,\n \"worflows\": false\n }\n]\n```\n(and modify the \"notes\" too!).\n\n### Configure the \"Init\" node\n```\nconst FTP_BACKUP_FOLDER = $env.FTP_BACKUP_FOLDER || '/n8n-backups';\nconst credentials_temp_folder = '-restore-credentials';\nconst workflows_temp_folder = '-restore-workflows';\nconst FTPName = 'Your FTP Server Name';\nconst credentials = \"n8n-credentials\";\n```"
},
"typeVersion": 1
},
{
"id": "",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
-272
],
"parameters": {
"color": 3,
"width": 1776,
"height": 352,
"content": "## 1. Restore Credentials from FTP"
},
"typeVersion": 1
},
{
"id": "",
"name": "Restore Credentials?",
"type": "n8n-nodes-base.if",
"position": [
16,
-160
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $('Start Restore').first().json.credentials }}",
"rightValue": "true"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "",
"name": "Restore Workflows?",
"type": "n8n-nodes-base.if",
"position": [
16,
224
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $('Start Restore').first().json.worflows }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "",
"name": "Init",
"type": "n8n-nodes-base.code",
"position": [
-880,
48
],
"parameters": {
"jsCode": "// YOUR_AWS_SECRET_KEY_HERE==\n// \ud83d\udcc5 LOCAL DATE/TIME INITIALIZATION\n// YOUR_AWS_SECRET_KEY_HERE==\n\nconst now = new Date();\n\n// YOUR_AWS_SECRET_KEY_HERE==\n// \ud83c\udf0d USER-DEFINED TIMEZONE CONFIGURATION\n// YOUR_AWS_SECRET_KEY_HERE==\n// \u26a0\ufe0f IMPORTANT: This code runs on the n8n SERVER, not in your browser!\n// \u26a0\ufe0f Configure the timezone where you want executions to be scheduled,\n// regardless of where your n8n server is physically located.\n//\n// \ud83d\udccd Common timezone examples:\n// - 'Europe/Paris' \u2192 Central European Time (CET/CEST)\n// - 'America/New_York' \u2192 Eastern Time (EST/EDT)\n// - 'America/Chicago' \u2192 Central Time (CST/CDT)\n// - 'America/Los_Angeles'\u2192 Pacific Time (PST/PDT)\n// - 'Asia/Tokyo' \u2192 Japan Standard Time (JST)\n// - 'Asia/Shanghai' \u2192 China Standard Time (CST)\n// - 'Australia/Sydney' \u2192 Australian Eastern Time (AET)\n// - 'UTC' \u2192 Coordinated Universal Time\n//\n// \ud83d\udd27 TO CONFIGURE FOR YOUR USE CASE:\n// 1. Uncomment and edit the lines below with your desired timezone\n// 2. Comment out the automatic detection line\n//\n// To use user-defined timezone instead, UNCOMMENT these lines:\n// const USER_TIMEZONE = 'Europe/Paris'; // \ud83d\udc48 EDIT THIS for your location\n// const LOCAL_TIMEZONE = USER_TIMEZONE; // \ud83d\udc48 EDIT THIS for your location\n//\n// \ud83d\udd58 Automatic detection (uses server timezone or environment variable):\nconst LOCAL_TIMEZONE = $env.GENERIC_TIMEZONE || 'Europe/Paris'; // \ud83d\udfe2 Default fallback\n// To use user-defined timezone instead, comment out the above line\n\n// Local datetime in YYYY-MM-DD_HH-MM-SS format\nconst localDateTime = now.toLocaleString('sv-SE', { \n timeZone: LOCAL_TIMEZONE,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit'\n}).replace(' ', '_').replace(/:/g, '-');\n\n// Local formatted time for display\nconst localDateTimeFormatted = now.toLocaleString('sv-SE', { \n timeZone: LOCAL_TIMEZONE,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit'\n});\n\n// Local time HH:MM:SS format\nconst localTimeFormatted = now.toLocaleString('sv-SE', { \n timeZone: LOCAL_TIMEZONE,\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit'\n});\n\n// Local date in YYYY-MM-DD format\nconst localDate = now.toLocaleDateString('sv-SE', { timeZone: LOCAL_TIMEZONE });\n\n// Local time in French format\nconst localTimeFR = now.toLocaleString('sv-SE', { \n timeZone: LOCAL_TIMEZONE,\n hour: '2-digit',\n minute: '2-digit'\n}).replace(/:/g, 'h').replace(/^0(\\d)h/, '$1h');\n\nconsole.log('\ud83d\ude80 USER-DEFINE TIMEZONE VARIABLES INITIALISED (Local timezone):');\nconsole.log(` \ud83c\udf0d Timezone: ${LOCAL_TIMEZONE}`);\nconsole.log(` \u2728 Now (UTC): ${now}`);\nconsole.log(` \ud83d\udcc5 Local date/time: ${localDateTime}`);\nconsole.log(` \ud83d\udd50 Local time: ${localDateTimeFormatted}`);\nconsole.log(` \ud83d\udd50 Local time: ${localTimeFormatted}`);\nconsole.log(` \ud83d\udcc5 Local date: ${localDate}`);\nconsole.log(` \ud83d\udcc5 Local time (FR): ${localTimeFR}`);\n\n// Create structure for time data output\nconst timeData = {\n localTimeZone:LOCAL_TIMEZONE,\n nowDateUTC: now,\n nowUTCstring: now.toISOString(), // UTC for calculations\n localDateTime: localDateTime, // YYY-MM-DD_HH-MM-SS format\n localDateTimeFormatted: localDateTimeFormatted, // Human readable\n localTimeFormatted: localTimeFormatted, // Human readable\n localDate: localDate, // YYYY-MM-DD format\n localTime: localTimeFR // HHhMM format (FR)\n}\n\n// YOUR_AWS_SECRET_KEY_HERE==\n// \ud83d\udcdd WORKFLOW STANDARD CONFIGURATION\n// YOUR_AWS_SECRET_KEY_HERE==\n\nconst N8N_ADMIN_EMAIL = $env.N8N_ADMIN_EMAIL || 'user@example.com';\nconst WORKFLOW_NAME = $workflow.name;\nconst N8N_PROJECTS_DIR = $env.N8N_PROJECTS_DIR || '/files/n8n-projects-data'; // \u26a0\ufe0f Your projects\u2019 ROOT folder here\n// projects-root-folder/\n// \u2514\u2500\u2500 Your-project-folder-name/\n// \u251c\u2500\u2500 logs/\n// \u251c\u2500\u2500 reports/\n// \u251c\u2500\u2500 ...\n// \u2514\u2500\u2500 [other project files]\nconst PROJECT_FOLDER_NAME = \"Workflow-backups\"; // \u26a0\ufe0f Your project folder\nconst PROJECT_ROOT_PATH = `${N8N_PROJECTS_DIR}/${PROJECT_FOLDER_NAME}`;\n// const N8N_MEDIA_ROOT_PATH = $env.N8N_MEDIA_SHARED || '/files/n8n-media-shared'; // \u26a0\ufe0f Your public folder, accessible from inet\n// const mediaOutputFolder = \"/output\";\n// const mediaTempFolder = \"/temp\";\n// const N8N_FILE_SERVER_PURL = $env.N8N_FILE_SERVER_PURL || 'https://files.srv815447.hstgr.cloud'; // File serve public url\n\nconst LOGS_PATH = `${PROJECT_ROOT_PATH}/logs`;\n// const logFileName = `${localDateTime2}-${WORKFLOW_NAME.replace(/[^a-zA-Z0-9]/g, '_')}_logs.json`; // \u26a0\ufe0f Your log file name\nconst logFileName = `${localDateTime}-backup_logs.json`; // \u26a0\ufe0f Your log file name\nconst logPathFileName = `${LOGS_PATH}/${logFileName}`;\n\n// Configuration for report generation\nconst REPORTS_PATH = `${PROJECT_ROOT_PATH}/reports`;\nconst reportFileName = `${localDateTime}-report.txt`;\nconst reportPathFileName = `${REPORTS_PATH}/${reportFileName}`;\n\n// Console output\nconsole.log('\ud83e\uddfe STANDARD WORKFLOW VARIABLES INITIALISED:');\nconsole.log(` \ud83d\udcc1 Admin email: ${N8N_ADMIN_EMAIL}`);\nconsole.log(` \ud83d\udcc1 Workflow name: ${WORKFLOW_NAME}`);\nconsole.log(` \ud83d\udcc1 Projects folder: ${N8N_PROJECTS_DIR}`);\nconsole.log(` \ud83d\udcc1 Project folder name: ${PROJECT_FOLDER_NAME}`);\nconsole.log(` \ud83d\udcc1 Project root path: ${PROJECT_ROOT_PATH}`);\n// console.log(` \ud83d\udcc1 Media root path: ${N8N_MEDIA_ROOT_PATH}`);\n// console.log(` \ud83d\udcc1 File server public URL: ${N8N_FILE_SERVER_PURL}`);\nconsole.log(` \ud83d\udcc1 Log path: ${LOGS_PATH}`);\nconsole.log(` \ud83d\udcc1 Log file name: ${logFileName}`);\nconsole.log(` \ud83d\udcc1 Log file path: ${logPathFileName}`);\nconsole.log(` \ud83d\udcc1 Reports path: ${REPORTS_PATH}`);\nconsole.log(` \ud83d\udcc1 Report file name: ${reportFileName}`);\nconsole.log(` \ud83d\udcc1 Report file path: ${reportPathFileName}`);\n\n// Create structure for workflow configuration output\nconst workflowConfig = {\n workflowStep: 'init',\n N8N_ADMIN_EMAIL,\n WORKFLOW_NAME,\n N8N_PROJECTS_DIR,\n PROJECT_FOLDER_NAME,\n PROJECT_ROOT_PATH,\n // N8N_MEDIA_ROOT_PATH,\n // mediaOutputFolder,\n // mediaTempFolder,\n // N8N_FILE_SERVER_PURL,\n LOGS_PATH,\n logFileName,\n logPathFileName,\n REPORTS_PATH,\n reportFileName,\n reportPathFileName\n}\n\n// YOUR_AWS_SECRET_KEY_HERE==\n// \ud83d\udcdd WORKFLOW CUSTOM CONFIGURATION\n// YOUR_AWS_SECRET_KEY_HERE==\n// \u26a0\ufe0f INSERT HERE: your workflow custom configuration variables\n\n// Base backup path using your FTP volume configuration (folder must exist)\nconst BACKUP_FOLDER = $env.N8N_BACKUP_FOLDER || '/files/n8n-backups'; // \u26a0\ufe0f Change the default value for your n8n backup folder\nconst credentials_temp_folder = '-restore-credentials';\nconst workflows_temp_folder = '-restore-workflows';\nconst FTP_BACKUP_FOLDER = $env.FTP_BACKUP_FOLDER || '/n8n-backups';\nconst FTPName = 'Your FTP Server Name';\n\nconst credentials = \"n8n-credentials\";\n\n// Console output\nconsole.log('\ud83e\uddfe CUSTOM WORKFLOW VARIABLES INITIALISED:');\nconsole.log(` \ud83d\udcbe Backup folder: ${BACKUP_FOLDER}`);\nconsole.log(' File prefix:', localDate);\nconsole.log(' Timestamp (UTC):', now);\n\nconst customConfig = {\n backupFolder: BACKUP_FOLDER,\n credentials_temp_folder,\n workflows_temp_folder,\n FTP_BACKUP_FOLDER,\n FTPName,\n credentials\n}\n\n// YOUR_AWS_SECRET_KEY_HERE==\n// \ud83d\udcca OUTPUT DATA\n// YOUR_AWS_SECRET_KEY_HERE==\n\nconst initData = {\n timeData,\n workflowConfig,\n customConfig\n};\n\nreturn [{ json: initData }];"
},
"typeVersion": 2
},
{
"id": "",
"name": "Create Temp Folder",
"type": "n8n-nodes-base.executeCommand",
"notes": "For credential files on server",
"position": [
-256,
48
],
"parameters": {
"command": "=#!/bin/bash\n\n# Create temporary restore folders based on trigger parameters\n# This script creates folders only for selected restore options\n\n# Get configuration from Init node\nPROJECT_ROOT_PATH=\"{{ $('Init').first().json.workflowConfig.PROJECT_ROOT_PATH }}\"\nLOCAL_DATE=\"{{ $json.latestBackupDate }}\"\nCREDENTIALS_TEMP_FOLDER=\"{{ $('Init').first().json.customConfig.credentials_temp_folder }}\"\nWORKFLOWS_TEMP_FOLDER=\"{{ $('Init').first().json.customConfig.workflows_temp_folder }}\"\n\n# Get trigger parameters\nRESTORE_CREDENTIALS=\"{{ $('Start Restore').first().json.credentials }}\"\nRESTORE_WORKFLOWS=\"{{ $('Start Restore').first().json.worflows }}\"\n\necho \"\ud83d\udccb Restore parameters:\"\necho \" \ud83d\udd11 Credentials: $RESTORE_CREDENTIALS\"\necho \" \ud83d\udd04 Workflows: $RESTORE_WORKFLOWS\"\necho \"\"\n\nFOLDERS_CREATED=0\n\n# Create credentials temp folder if needed\nif [ \"$RESTORE_CREDENTIALS\" = \"true\" ]; then\n CRED_PATH=\"${PROJECT_ROOT_PATH}/${LOCAL_DATE}${CREDENTIALS_TEMP_FOLDER}\"\n mkdir -p \"$CRED_PATH\"\n echo \"\u2705 Created credentials temp folder: $CRED_PATH\"\n FOLDERS_CREATED=$((FOLDERS_CREATED + 1))\nfi\n\n# Create workflows temp folder if needed\nif [ \"$RESTORE_WORKFLOWS\" = \"true\" ]; then\n WORK_PATH=\"${PROJECT_ROOT_PATH}/${LOCAL_DATE}${WORKFLOWS_TEMP_FOLDER}\"\n mkdir -p \"$WORK_PATH\"\n echo \"\u2705 Created workflows temp folder: $WORK_PATH\"\n FOLDERS_CREATED=$((FOLDERS_CREATED + 1))\nfi\n\necho \"\"\necho \"\ud83d\udcca SUMMARY: $FOLDERS_CREATED folder(s) created\"\n\nif [ \"$FOLDERS_CREATED\" -eq 0 ]; then\n echo \"\u26a0\ufe0f No folders created (no restore option selected)\"\n exit 1\nfi"
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "",
"name": "List Credentials Files",
"type": "n8n-nodes-base.ftp",
"position": [
224,
-160
],
"parameters": {
"path": "={{ $('Find Last Backup').item.json.ftpCredentialsPath }}",
"operation": "list"
},
"credentials": {
"ftp": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "",
"name": "Find Last Backup",
"type": "n8n-nodes-base.code",
"onError": "continueErrorOutput",
"position": [
-464,
48
],
"parameters": {
"jsCode": "// YOUR_AWS_SECRET_KEY_HERE==\n// \ud83d\udd0d FIND LATEST BACKUP FOLDER FROM FTP\n// YOUR_AWS_SECRET_KEY_HERE==\n// \u26a0\ufe0f CONFIGURE THIS NODE WITH:\n// SETTINGS > ON ERROR > CONTINUE (USING ERROR OUTPUT)\n\n// Extract init data from Init node\nconst initData = $('Init').first().json;\nconst { customConfig } = initData;\n\nconst FTP_BACKUP_FOLDER = customConfig.FTP_BACKUP_FOLDER;\nconst credentials = customConfig.credentials;\n\nconsole.log(`\ud83d\udd0d Searching for latest backup in FTP: ${FTP_BACKUP_FOLDER}`);\nconsole.log(`\ud83d\udcc1 Target credentials folder: ${credentials}`);\n\ntry {\n // Get FTP listing from previous node (List FTP)\n const ftpListing = $input.all();\n \n if (!ftpListing || ftpListing.length === 0) {\n throw new Error('No FTP listing found. Please check FTP connection.');\n }\n \n console.log(`\ud83d\udcc2 FTP returned ${ftpListing.length} item(s)`);\n \n // Filter for directories only (type 'd') with date format YYYY-MM-DD\n const datePattern = /^\\d{4}-\\d{2}-\\d{2}$/;\n const dateDirectories = ftpListing\n .filter(item => {\n const itemData = item.json;\n // FTP node returns items with 'name' and 'type' properties\n // type 'd' = directory, type '-' = file\n const isDirectory = itemData.type === 'd' || itemData.type === 1; // type can be string or number\n const matchesDateFormat = datePattern.test(itemData.name);\n \n if (isDirectory && matchesDateFormat) {\n console.log(`\u2713 Found date directory: ${itemData.name}`);\n }\n \n return isDirectory && matchesDateFormat;\n })\n .map(item => item.json.name);\n \n console.log(`\ud83d\udcc5 Found ${dateDirectories.length} date-formatted directories`);\n \n if (dateDirectories.length === 0) {\n throw new Error('No date-formatted directories (YYYY-MM-DD) found in FTP backup folder');\n }\n \n // Sort directories by date (newest first)\n const sortedDirectories = dateDirectories.sort((a, b) => {\n const dateA = new Date(a);\n const dateB = new Date(b);\n return dateB - dateA; // Descending order (newest first)\n });\n \n console.log(`\ud83d\udcc5 Sorted directories (newest first): ${sortedDirectories.join(', ')}`);\n \n // Get the most recent directory\n const latestBackupDate = sortedDirectories[0];\n const ftpCredentialsPath = `${FTP_BACKUP_FOLDER}/${latestBackupDate}/${credentials}`;\n const ftpWorkflowsPath = `${FTP_BACKUP_FOLDER}/${latestBackupDate}/`;\n \n console.log(`\u2705 Latest backup date: ${latestBackupDate}`);\n console.log(`\u2705 FTP credentials path: ${ftpCredentialsPath}`);\n \n // Return result for next node\n const result = {\n latestBackupDate,\n ftpCredentialsPath,\n ftpWorkflowsPath,\n FTP_BACKUP_FOLDER,\n credentialsFolder: credentials\n };\n \n return [{ json: result }];\n \n} catch (error) {\n console.error(`\u274c Error finding latest backup: ${error.message}`);\n throw new Error(`Failed to find latest backup: ${error.message}`);\n}"
},
"typeVersion": 2
}
],
"connections": {
"Init": {
"main": [
[
{
"node": "List Credentials Folders",
"type": "main",
"index": 0
}
]
]
},
"Start Restore": {
"main": [
[
{
"node": "Init",
"type": "main",
"index": 0
}
]
]
},
"Find Last Backup": {
"main": [
[
{
"node": "Create Temp Folder",
"type": "main",
"index": 0
}
],
[
{
"node": "ERROR:\u202fFind Most Recent Credentials Folder",
"type": "main",
"index": 0
}
]
]
},
"Restore Workflows": {
"main": [
[
{
"node": "SUCCESS email Workflows",
"type": "main",
"index": 0
}
]
]
},
"Create Temp Folder": {
"main": [
[
{
"node": "Restore Credentials?",
"type": "main",
"index": 0
},
{
"node": "Restore Workflows?",
"type": "main",
"index": 0
}
]
]
},
"Restore Workflows?": {
"main": [
[
{
"node": "List Most Recent Workflows Folder",
"type": "main",
"index": 0
}
]
]
},
"Restore Credentials": {
"main": [
[
{
"node": "SUCCESS email Credentials",
"type": "main",
"index": 0
}
]
]
},
"Restore Credentials?": {
"main": [
[
{
"node": "List Credentials Files",
"type": "main",
"index": 0
}
]
]
},
"List Credentials Files": {
"main": [
[
{
"node": "Download Credential Files",
"type": "main",
"index": 0
}
]
]
},
"Download Workflow Files": {
"main": [
[
{
"node": "Filter out Credentials sub-folder",
"type": "main",
"index": 0
}
]
]
},
"List Credentials Folders": {
"main": [
[
{
"node": "Find Last Backup",
"type": "main",
"index": 0
}
]
]
},
"Download Credential Files": {
"main": [
[
{
"node": "Write Credential Files To Disk",
"type": "main",
"index": 0
}
]
]
},
"Write Workflow Files To Disk": {
"main": [
[
{
"node": "Exclude Current Workflow From Selection",
"type": "main",
"index": 0
}
]
]
},
"Write Credential Files To Disk": {
"main": [
[
{
"node": "Restore Credentials",
"type": "main",
"index": 0
}
]
]
},
"Filter out Credentials sub-folder": {
"main": [
[
{
"node": "Write Workflow Files To Disk",
"type": "main",
"index": 0
}
]
]
},
"List Most Recent Workflows Folder": {
"main": [
[
{
"node": "Download Workflow Files",
"type": "main",
"index": 0
}
]
]
},
"Exclude Current Workflow From Selection": {
"main": [
[
{
"node": "Restore Workflows",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
ftpsmtp
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Perfect for disaster recovery or migrating between environments, this workflow automatically identifies your most recent FTP backup and provides a manual restore capability that intelligently excludes the current workflow to prevent conflicts. Works seamlessly with…
Source: https://n8n.io/workflows/9156/ — 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.
Complete backup solution that saves both workflows and credentials to local/server disk with optional FTP upload for off-site redundancy.
Perfect for disaster recovery or migrating between environments, this workflow automatically identifies your most recent backup and provides a manual restore capability that intelligently excludes the
Perfect for content publishing with organic scheduling patterns, social media automation, API systems that need to avoid rate limiting, or any automation requiring randomised timing control across mul
This n8n workflow template uses community nodes and is only compatible with the self-hosted version of n8n.
N8N Workflow. Uses localFileTrigger, executeCommand, readWriteFile. Event-driven trigger; 16 nodes.