This workflow follows the Execute Workflow Trigger → Postgres 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 →
{
"id": "wf004b-asset-quality-gate-0000",
"name": "WF-004b-asset-quality-gate",
"nodes": [
{
"parameters": {
"inputSource": "passthrough"
},
"id": "c4b00000-0000-0000-0000-000000000001",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
240,
400
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ `SELECT forbidden_phrases, prompt_prefix, fact_policy, tone FROM brand_profiles WHERE channel_id = ${$json.channel_id} LIMIT 1` }}",
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000002",
"name": "Fetch Brand Profile",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
460,
400
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT pv.system_prompt, pv.user_prompt_template, pv.model_params FROM prompt_versions pv JOIN prompts p ON p.id = pv.prompt_id WHERE p.name = 'quality_gate' AND pv.version = p.current_version AND p.is_active = true LIMIT 1",
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000003",
"name": "Load Quality Gate Prompt",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
680,
400
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const trigger = $('Execute Workflow Trigger').item.json;\nconst brand = $('Fetch Brand Profile').item.json;\nconst prompt = $input.item.json;\n\nconst userPrompt = (prompt.user_prompt_template || '')\n .replace('{{ content_asset_json }}', JSON.stringify(trigger.asset || {}))\n .replace(\"{{ brand_profile.forbidden_phrases | join(', ') }}\", (brand.forbidden_phrases || []).join(', '))\n .replace('{{ brand_profile.fact_policy }}', brand.fact_policy || '')\n .replace('{{ brand_profile.tone }}', brand.tone || '');\n\nreturn [{\n json: {\n asset_id: trigger.asset_id,\n topic_id: trigger.topic_id,\n topic_title: trigger.topic_title,\n channel_id: trigger.channel_id,\n system_prompt: prompt.system_prompt,\n user_prompt: userPrompt,\n task_type: 'quality_gate',\n output_format: 'json',\n max_tokens: 1500,\n workflow_name: 'WF-004b-asset-quality-gate'\n }\n}];"
},
"id": "c4b00000-0000-0000-0000-000000000004",
"name": "Build QG Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
900,
400
]
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "2673a82a-e155-4d0c-85b4-92922b74849d",
"mode": "id"
},
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000005",
"name": "Call SUB-001 Pass1",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.1,
"position": [
1120,
400
]
},
{
"parameters": {
"jsCode": "const raw = $input.item.json;\nconst ctx = $('Build QG Prompt').item.json;\n\nlet scores = raw.response_json;\nif (!scores || typeof scores !== 'object' || Array.isArray(scores)) {\n try { scores = JSON.parse(raw.response_text); } catch (e) { scores = {}; }\n}\n\nconst factual_completeness = Number(scores.factual_completeness) || 0;\nconst citation_coverage = Number(scores.citation_coverage) || 0;\nconst hook_strength = Number(scores.hook_strength) || 0;\nconst readability = Number(scores.readability) || 0;\nconst seo_completeness = Number(scores.seo_completeness) || 0;\nconst brand_consistency = Number(scores.brand_consistency) || 0;\n\nconst overall_score = Math.round((\n factual_completeness * 0.20 +\n citation_coverage * 0.20 +\n hook_strength * 0.20 +\n readability * 0.15 +\n seo_completeness * 0.15 +\n brand_consistency * 0.10\n) * 100) / 100;\n\nreturn [{\n json: {\n asset_id: ctx.asset_id,\n topic_id: ctx.topic_id,\n topic_title: ctx.topic_title,\n channel_id: ctx.channel_id,\n factual_completeness, citation_coverage, hook_strength,\n readability, seo_completeness, brand_consistency,\n overall_score,\n failure_reasons: Array.isArray(scores.failure_reasons) ? scores.failure_reasons : [],\n llm_feedback: scores.llm_feedback || '',\n provider_used: raw.provider_name || raw.provider_used || null\n }\n}];"
},
"id": "c4b00000-0000-0000-0000-000000000006",
"name": "Parse Scores Pass1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1340,
400
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "c4b00000-0000-0000-0000-000000000007-c",
"leftValue": "={{ $json.overall_score }}",
"rightValue": 0.5,
"operator": {
"type": "number",
"operation": "gte"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000007",
"name": "Passed Pass1?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1560,
400
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ `INSERT INTO asset_quality_gates (asset_id, status, factual_completeness, citation_coverage, hook_strength, readability, seo_completeness, brand_consistency, overall_score, failure_reasons, llm_feedback, retry_count, provider_used, created_at, updated_at) VALUES (${$json.asset_id}, 'passed', ${$json.factual_completeness}, ${$json.citation_coverage}, ${$json.hook_strength}, ${$json.readability}, ${$json.seo_completeness}, ${$json.brand_consistency}, ${$json.overall_score}, ARRAY[${($json.failure_reasons||[]).map(r => `'${String(r).replace(/'/g, \"''\")}'`).join(',')}]::TEXT[], '${(($json.llm_feedback)||'').replace(/'/g, \"''\")}', 0, ${$json.provider_used ? `'${$json.provider_used}'` : 'NULL'}, NOW(), NOW()) RETURNING id` }}",
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000008",
"name": "Insert Quality Gate Passed",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
1780,
280
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c4b00000-r-1",
"name": "passed",
"value": true,
"type": "boolean"
},
{
"id": "c4b00000-r-2",
"name": "asset_id",
"value": "={{ $('Parse Scores Pass1').item.json.asset_id }}",
"type": "number"
},
{
"id": "c4b00000-r-3",
"name": "overall_score",
"value": "={{ $('Parse Scores Pass1').item.json.overall_score }}",
"type": "number"
}
]
},
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000009",
"name": "Return Passed Pass1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2000,
280
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ `INSERT INTO asset_quality_gates (asset_id, status, factual_completeness, citation_coverage, hook_strength, readability, seo_completeness, brand_consistency, overall_score, failure_reasons, llm_feedback, retry_count, provider_used, created_at, updated_at) VALUES (${$json.asset_id}, 'failed', ${$json.factual_completeness}, ${$json.citation_coverage}, ${$json.hook_strength}, ${$json.readability}, ${$json.seo_completeness}, ${$json.brand_consistency}, ${$json.overall_score}, ARRAY[${($json.failure_reasons||[]).map(r => `'${String(r).replace(/'/g, \"''\")}'`).join(',')}]::TEXT[], '${(($json.llm_feedback)||'').replace(/'/g, \"''\")}', 1, ${$json.provider_used ? `'${$json.provider_used}'` : 'NULL'}, NOW(), NOW()) RETURNING id` }}",
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000010",
"name": "Insert Quality Gate Failed",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
1780,
520
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const scores = $('Parse Scores Pass1').item.json;\nconst base = $('Build QG Prompt').item.json;\n\nconst correctivePrompt = base.user_prompt +\n `\\n\\nPrevious evaluation failed with reasons: ${(scores.failure_reasons || []).join('; ')}. Re-evaluate carefully and address these specific issues in your scoring.`;\n\nreturn [{\n json: {\n asset_id: scores.asset_id,\n topic_id: scores.topic_id,\n topic_title: scores.topic_title,\n channel_id: scores.channel_id,\n system_prompt: base.system_prompt,\n user_prompt: correctivePrompt,\n task_type: 'quality_gate',\n output_format: 'json',\n max_tokens: 1500,\n workflow_name: 'WF-004b-asset-quality-gate'\n }\n}];"
},
"id": "c4b00000-0000-0000-0000-000000000011",
"name": "Build Corrective Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2000,
520
]
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "2673a82a-e155-4d0c-85b4-92922b74849d",
"mode": "id"
},
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000012",
"name": "Call SUB-001 Pass2",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.1,
"position": [
2220,
520
]
},
{
"parameters": {
"jsCode": "const raw = $input.item.json;\nconst ctx = $('Build Corrective Prompt').item.json;\n\nlet scores = raw.response_json;\nif (!scores || typeof scores !== 'object' || Array.isArray(scores)) {\n try { scores = JSON.parse(raw.response_text); } catch (e) { scores = {}; }\n}\n\nconst factual_completeness = Number(scores.factual_completeness) || 0;\nconst citation_coverage = Number(scores.citation_coverage) || 0;\nconst hook_strength = Number(scores.hook_strength) || 0;\nconst readability = Number(scores.readability) || 0;\nconst seo_completeness = Number(scores.seo_completeness) || 0;\nconst brand_consistency = Number(scores.brand_consistency) || 0;\n\nconst overall_score = Math.round((\n factual_completeness * 0.20 +\n citation_coverage * 0.20 +\n hook_strength * 0.20 +\n readability * 0.15 +\n seo_completeness * 0.15 +\n brand_consistency * 0.10\n) * 100) / 100;\n\nreturn [{\n json: {\n asset_id: ctx.asset_id,\n topic_id: ctx.topic_id,\n topic_title: ctx.topic_title,\n channel_id: ctx.channel_id,\n factual_completeness, citation_coverage, hook_strength,\n readability, seo_completeness, brand_consistency,\n overall_score,\n failure_reasons: Array.isArray(scores.failure_reasons) ? scores.failure_reasons : [],\n llm_feedback: scores.llm_feedback || '',\n provider_used: raw.provider_name || raw.provider_used || null\n }\n}];"
},
"id": "c4b00000-0000-0000-0000-000000000013",
"name": "Parse Scores Pass2",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2440,
520
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "c4b00000-0000-0000-0000-000000000014-c",
"leftValue": "={{ $json.overall_score }}",
"rightValue": 0.5,
"operator": {
"type": "number",
"operation": "gte"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000014",
"name": "Passed Pass2?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
2660,
520
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ `UPDATE asset_quality_gates SET status = 'passed', factual_completeness = ${$json.factual_completeness}, citation_coverage = ${$json.citation_coverage}, hook_strength = ${$json.hook_strength}, readability = ${$json.readability}, seo_completeness = ${$json.seo_completeness}, brand_consistency = ${$json.brand_consistency}, overall_score = ${$json.overall_score}, failure_reasons = ARRAY[${($json.failure_reasons||[]).map(r => `'${String(r).replace(/'/g, \"''\")}'`).join(',')}]::TEXT[], llm_feedback = '${(($json.llm_feedback)||'').replace(/'/g, \"''\")}', updated_at = NOW() WHERE asset_id = ${$json.asset_id} RETURNING id` }}",
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000015",
"name": "Update Quality Gate Passed",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
2880,
420
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c4b00000-r2-1",
"name": "passed",
"value": true,
"type": "boolean"
},
{
"id": "c4b00000-r2-2",
"name": "asset_id",
"value": "={{ $('Parse Scores Pass2').item.json.asset_id }}",
"type": "number"
},
{
"id": "c4b00000-r2-3",
"name": "overall_score",
"value": "={{ $('Parse Scores Pass2').item.json.overall_score }}",
"type": "number"
}
]
},
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000016",
"name": "Return Passed Pass2",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
3100,
420
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ `UPDATE asset_quality_gates SET status = 'manual_review', factual_completeness = ${$json.factual_completeness}, citation_coverage = ${$json.citation_coverage}, hook_strength = ${$json.hook_strength}, readability = ${$json.readability}, seo_completeness = ${$json.seo_completeness}, brand_consistency = ${$json.brand_consistency}, overall_score = ${$json.overall_score}, failure_reasons = ARRAY[${($json.failure_reasons||[]).map(r => `'${String(r).replace(/'/g, \"''\")}'`).join(',')}]::TEXT[], llm_feedback = '${(($json.llm_feedback)||'').replace(/'/g, \"''\")}', updated_at = NOW() WHERE asset_id = ${$json.asset_id} RETURNING id` }}",
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000017",
"name": "Update Quality Gate Manual Review",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
2880,
620
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const esc = s => String(s == null ? '' : s).replace(/_/g, ' ').replace(/[*`\\[]/g, '');\nconst s = $('Parse Scores Pass2').item.json;\nreturn [{\n json: {\n message: `\\u26a0\\ufe0f *QUALITY GATE FAILED \\u2014 Manual Review Required*\\n\\nAsset ID: ${s.asset_id}\\nTopic: ${esc(s.topic_title)}\\nScore: ${s.overall_score} / 0.70 required\\nFailures: ${(s.failure_reasons || []).map(esc).join(', ')}`,\n parse_mode: 'Markdown',\n inline_keyboard: [],\n asset_id: s.asset_id\n }\n}];"
},
"id": "c4b00000-0000-0000-0000-000000000018",
"name": "Build Manual Review Alert",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3100,
620
]
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "sub003-telegram-notification-000000",
"mode": "id"
},
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000019",
"name": "Notify Manual Review",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.1,
"position": [
3320,
620
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c4b00000-r3-1",
"name": "passed",
"value": false,
"type": "boolean"
},
{
"id": "c4b00000-r3-2",
"name": "asset_id",
"value": "={{ $('Parse Scores Pass2').item.json.asset_id }}",
"type": "number"
},
{
"id": "c4b00000-r3-3",
"name": "status",
"value": "manual_review",
"type": "string"
}
]
},
"options": {}
},
"id": "c4b00000-0000-0000-0000-000000000020",
"name": "Return Manual Review",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
3540,
620
]
}
],
"connections": {
"Execute Workflow Trigger": {
"main": [
[
{
"node": "Fetch Brand Profile",
"type": "main",
"index": 0
}
]
]
},
"Fetch Brand Profile": {
"main": [
[
{
"node": "Load Quality Gate Prompt",
"type": "main",
"index": 0
}
]
]
},
"Load Quality Gate Prompt": {
"main": [
[
{
"node": "Build QG Prompt",
"type": "main",
"index": 0
}
]
]
},
"Build QG Prompt": {
"main": [
[
{
"node": "Call SUB-001 Pass1",
"type": "main",
"index": 0
}
]
]
},
"Call SUB-001 Pass1": {
"main": [
[
{
"node": "Parse Scores Pass1",
"type": "main",
"index": 0
}
]
]
},
"Parse Scores Pass1": {
"main": [
[
{
"node": "Passed Pass1?",
"type": "main",
"index": 0
}
]
]
},
"Passed Pass1?": {
"main": [
[
{
"node": "Insert Quality Gate Passed",
"type": "main",
"index": 0
}
],
[
{
"node": "Insert Quality Gate Failed",
"type": "main",
"index": 0
}
]
]
},
"Insert Quality Gate Passed": {
"main": [
[
{
"node": "Return Passed Pass1",
"type": "main",
"index": 0
}
]
]
},
"Insert Quality Gate Failed": {
"main": [
[
{
"node": "Build Corrective Prompt",
"type": "main",
"index": 0
}
]
]
},
"Build Corrective Prompt": {
"main": [
[
{
"node": "Call SUB-001 Pass2",
"type": "main",
"index": 0
}
]
]
},
"Call SUB-001 Pass2": {
"main": [
[
{
"node": "Parse Scores Pass2",
"type": "main",
"index": 0
}
]
]
},
"Parse Scores Pass2": {
"main": [
[
{
"node": "Passed Pass2?",
"type": "main",
"index": 0
}
]
]
},
"Passed Pass2?": {
"main": [
[
{
"node": "Update Quality Gate Passed",
"type": "main",
"index": 0
}
],
[
{
"node": "Update Quality Gate Manual Review",
"type": "main",
"index": 0
}
]
]
},
"Update Quality Gate Passed": {
"main": [
[
{
"node": "Return Passed Pass2",
"type": "main",
"index": 0
}
]
]
},
"Update Quality Gate Manual Review": {
"main": [
[
{
"node": "Build Manual Review Alert",
"type": "main",
"index": 0
}
]
]
},
"Build Manual Review Alert": {
"main": [
[
{
"node": "Notify Manual Review",
"type": "main",
"index": 0
}
]
]
},
"Notify Manual Review": {
"main": [
[
{
"node": "Return Manual Review",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
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.
postgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
WF-004b-asset-quality-gate. Uses executeWorkflowTrigger, postgres. Event-driven trigger; 20 nodes.
Source: https://github.com/navin-labs/content-factory-os/blob/main/n8n/workflows/main/WF-004b-asset-quality-gate.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.
Reagendamiento_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 89 nodes.
Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.
Cancelacion_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 46 nodes.
Save_Extraction. Uses executeWorkflowTrigger, postgres, httpRequest. Event-driven trigger; 22 nodes.
Youtube Searcher. Uses splitInBatches, httpRequest, manualTrigger, executeWorkflowTrigger. Event-driven trigger; 21 nodes.