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": "VIS \u2014 Weekly Visual Health Audit",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 9,
"triggerAtDay": 1
}
]
}
},
"id": "vis-schedule-trigger",
"name": "Every Monday 9am UTC",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
240,
300
]
},
{
"parameters": {
"command": "cd $VIS_PROJECT_ROOT && node scripts/audit-visual-health.mjs --json"
},
"id": "vis-run-audit",
"name": "Run Visual Health Audit",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
480,
300
]
},
{
"parameters": {
"jsCode": "// Parse audit JSON and format Slack message\nconst rawOutput = $input.first().json.stdout;\nconst audit = JSON.parse(rawOutput);\nconst { summary } = audit;\n\n// Calculate health score (mirrors audit-visual-health.mjs logic)\nconst score = Math.max(0, 100 - (summary.high * 15) - (summary.medium * 5) - (summary.low * 2));\nlet status;\nif (score >= 90) status = 'EXCELLENT';\nelse if (score >= 70) status = 'GOOD';\nelse if (score >= 50) status = 'NEEDS ATTENTION';\nelse status = 'CRITICAL';\n\n// Count categories from registry (approximate from issues)\nconst categories = new Set();\nlet totalSizeKB = 0;\nfor (const issue of audit.issues) {\n if (issue.image) {\n const parts = issue.image.split('/');\n if (parts.length >= 3) categories.add(parts[2]);\n }\n if (issue.sizeKB) totalSizeKB += issue.sizeKB;\n}\n\nconst totalSizeMB = (totalSizeKB / 1024).toFixed(0);\n\n// Build top actions\nconst actions = [];\nconst placeholders = audit.issues.filter(i => i.type === 'placeholder');\nconst oversized = audit.issues.filter(i => i.type === 'oversized');\nconst duplicates = audit.issues.filter(i => i.type === 'duplicate-hero');\n\nif (placeholders.length > 0) {\n actions.push(`Replace ${placeholders.length} placeholder(s): ${placeholders.slice(0, 3).map(i => i.file).join(', ')}`);\n}\nif (oversized.length > 0) {\n const savingsMB = (oversized.reduce((sum, i) => sum + (i.sizeKB - 2000), 0) / 1024).toFixed(0);\n actions.push(`Optimize ${oversized.length} oversized images (~${savingsMB}MB savings potential)`);\n}\nif (duplicates.length > 0) {\n actions.push(`Fix ${duplicates.length} duplicate hero image(s) in blog posts`);\n}\nif (actions.length === 0) {\n actions.push('No urgent actions required');\n}\n\n// Score emoji\nlet scoreEmoji;\nif (score >= 90) scoreEmoji = ':white_check_mark:';\nelse if (score >= 70) scoreEmoji = ':large_blue_circle:';\nelse if (score >= 50) scoreEmoji = ':warning:';\nelse scoreEmoji = ':red_circle:';\n\nconst slackMessage = `:mag: *Weekly Visual Health Report*\\n\\n${scoreEmoji} *Score: ${score}/100* (${status})\\n\\n:bar_chart: *Issues:*\\n\\u2022 :red_circle: HIGH: ${summary.high} (placeholders in production)\\n\\u2022 :large_yellow_circle: MEDIUM: ${summary.medium} (duplicate heroes)\\n\\u2022 :large_blue_circle: LOW: ${summary.low} (oversized images)\\n\\u2022 :information_source: INFO: ${summary.info} (orphaned images)\\n\\n:chart_with_upwards_trend: *Registry:* ${audit.issues.length > 0 ? summary.total : 0} total issues found\\n\\n:clipboard: *Top Actions:*\\n${actions.map((a, i) => `${i + 1}. ${a}`).join('\\n')}\\n\\n_Timestamp: ${audit.timestamp}_\\n_Run \\`vis report --html\\` for full details_`;\n\nreturn [{ json: { text: slackMessage, score, status, summary, timestamp: audit.timestamp } }];"
},
"id": "vis-format-slack",
"name": "Format Slack Message",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
300
]
},
{
"parameters": {
"authentication": "oAuth2",
"channel": {
"__rl": true,
"mode": "name",
"value": "#visual-health"
},
"text": "={{ $json.text }}",
"otherOptions": {
"unfurl_links": false,
"unfurl_media": false
}
},
"id": "vis-slack-post",
"name": "Post to Slack",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.2,
"position": [
960,
300
],
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Every Monday 9am UTC": {
"main": [
[
{
"node": "Run Visual Health Audit",
"type": "main",
"index": 0
}
]
]
},
"Run Visual Health Audit": {
"main": [
[
{
"node": "Format Slack Message",
"type": "main",
"index": 0
}
]
]
},
"Format Slack Message": {
"main": [
[
{
"node": "Post to Slack",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner",
"errorWorkflow": ""
},
"versionId": "vis-weekly-audit-v1",
"tags": [
{
"name": "VIS",
"createdAt": "2026-03-21T00:00:00.000Z",
"updatedAt": "2026-03-21T00:00:00.000Z"
},
{
"name": "visual-intelligence",
"createdAt": "2026-03-21T00:00:00.000Z",
"updatedAt": "2026-03-21T00:00:00.000Z"
}
],
"meta": {
"templateCredsSetupCompleted": false
}
}
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.
slackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
VIS — Weekly Visual Health Audit. Uses executeCommand, slack. Scheduled trigger; 4 nodes.
Source: https://github.com/frankxai/visual-intelligence/blob/e8d2235e38624a19dce6577a243d3ffba6690314/n8n/weekly-visual-audit.json — 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.
ABM trigger orchestration. Uses readBinaryFile, httpRequest, executeCommand, slack. Scheduled trigger; 7 nodes.
티스토리 자동 포스팅 워크플로우. Uses executeCommand, slack. Scheduled trigger; 6 nodes.
Automated n8n Backup to Git. Uses executeCommand, slack. Scheduled trigger; 5 nodes.
This workflow fully automates your team's daily standup process using Slack for communication, Notion for structured data storage, and Redis for real-time session management.
debug. Uses httpRequest, slack, redis, mailgun. Scheduled trigger; 60 nodes.