This workflow corresponds to n8n.io template #15058 — we link there as the canonical source.
This workflow follows the Agent → Google Docs 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "aaecaa49-73c9-4268-bd20-fcbf6bb22190",
"name": "Parse Content Brief",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueErrorOutput",
"position": [
1264,
1872
],
"parameters": {
"text": "=Extract and clean the content brief from this Tally submission:\n\n{{ JSON.stringify($json) }}\n\nReturn a JSON object with: topic (string), target_audience (string), tone (string), keywords (array of strings), cta (string), competitor_urls (array of strings), submission_id (string).",
"options": {
"systemMessage": "You are a data extraction agent. Parse Tally form JSON into structured content brief fields.\n\nCRITICAL: Return ONLY a raw JSON object. No markdown fences, no explanation.\n\nOUTPUT SCHEMA: {\"topic\": \"string\", \"target_audience\": \"string\", \"tone\": \"string\", \"keywords\": [\"string\"], \"cta\": \"string\", \"competitor_urls\": [\"string\"], \"submission_id\": \"string\"}"
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "96ceb857-2ef0-4e5e-a3db-e3867a6b07b0",
"name": "GPT-5.4 Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1264,
2048
],
"parameters": {
"model": "gpt-5.4",
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "0ddef954-541d-4d6d-89d1-aefdd5a82b80",
"name": "Parse Brief Fields",
"type": "n8n-nodes-base.code",
"position": [
1632,
1856
],
"parameters": {
"jsCode": "const raw = $input.first().json.output || $input.first().json.text || '';\nconst clean = raw.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\nlet parsed;\ntry {\n parsed = JSON.parse(clean);\n} catch (e) {\n parsed = { topic: '', target_audience: '', tone: 'professional', keywords: [], cta: '', competitor_urls: [], submission_id: '', parse_error: e.message };\n}\nreturn [{ json: { ...parsed, processed_at: new Date().toISOString() } }];"
},
"typeVersion": 2
},
{
"id": "ef198c5c-5ca1-4644-915a-16897faaec57",
"name": "Build Blog Outline",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueErrorOutput",
"position": [
1920,
1856
],
"parameters": {
"text": "=Create a detailed blog post outline for the following brief:\n\nTopic: {{ $json.topic }}\nTarget audience: {{ $json.target_audience }}\nTone: {{ $json.tone }}\nKeywords to include: {{ ($json.keywords || []).join(', ') }}\nDesired CTA: {{ $json.cta }}\nCompetitor URLs to reference: {{ ($json.competitor_urls || []).join(', ') }}\n\nIf competitor URLs are provided, factor their content angles into the outline so the post covers what they cover plus additional value.\n\nReturn a JSON object with: title (suggested post title), sections (array of objects each with heading and bullet_points array), estimated_word_count (number).",
"options": {
"systemMessage": "You are a content strategist who builds well-structured blog outlines. Each section should have 3-5 bullet points that the writer will expand.\n\nCRITICAL: Return ONLY a raw JSON object. No markdown fences, no explanation.\n\nOUTPUT SCHEMA: {\"title\": \"string\", \"sections\": [{\"heading\": \"string\", \"bullet_points\": [\"string\"]}], \"estimated_word_count\": 1200}"
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "74493072-603e-4bfb-843c-cd2259857333",
"name": "GPT-5.4 Model 2",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1920,
2032
],
"parameters": {
"model": "gpt-5.4",
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "926313c3-eb82-45f4-ab2b-f7f212ba49f4",
"name": "Parse Outline",
"type": "n8n-nodes-base.code",
"position": [
2272,
1840
],
"parameters": {
"jsCode": "const brief = $('Parse Brief Fields').first().json;\nconst raw = $input.first().json.output || $input.first().json.text || '';\nconst clean = raw.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\nlet outline;\ntry {\n outline = JSON.parse(clean);\n} catch (e) {\n outline = { title: brief.topic, sections: [], estimated_word_count: 0, parse_error: e.message };\n}\nreturn [{ json: { ...brief, outline_title: outline.title, outline_sections: outline.sections, estimated_word_count: outline.estimated_word_count } }];"
},
"typeVersion": 2
},
{
"id": "7f5bf6b0-48d6-422c-8a8c-00331931d680",
"name": "Write Full Draft",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueErrorOutput",
"position": [
2448,
1840
],
"parameters": {
"text": "=Write a complete blog post draft based on this outline and brief:\n\nTitle: {{ $json.outline_title }}\nTone: {{ $json.tone }}\nTarget audience: {{ $json.target_audience }}\nKeywords: {{ ($json.keywords || []).join(', ') }}\nCTA: {{ $json.cta }}\nCompetitor URLs referenced: {{ ($json.competitor_urls || []).join(', ') }}\nOutline sections: {{ JSON.stringify($json.outline_sections || []) }}\n\nIf competitor URLs were provided, ensure the draft adds unique value beyond what competitors cover.\n\nReturn a JSON object with: title (string), body (full blog post in markdown, including all sections expanded from the outline, ending with the CTA).",
"options": {
"systemMessage": "You are an expert blog writer. Expand the provided outline into a complete, engaging blog post. Write in the specified tone.\n\nFORMATTING RULES:\n\nUse HTML tags for all formatting.\n\nUse <h2> for all section headings.\n\nUse <b> or <strong> for bold text.\n\nUse <ul> and <li> for bullet points.\n\nUse <p> for paragraphs.\n\nDO NOT use Markdown symbols like ## or **.\n\nEnd with a clear CTA paragraph.\n\nCRITICAL: Return ONLY a raw JSON object. No outer markdown fences.\n\nOUTPUT SCHEMA: > {\n\"title\": \"string\",\n\"body\": \"full HTML blog post as a string\"\n}"
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "174cd138-0240-4df1-8f6e-b5c6bdbf6973",
"name": "GPT-5.4 Model 3",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
2448,
2032
],
"parameters": {
"model": "gpt-5.4",
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "13ab9d25-b055-454b-98f7-8734495788dc",
"name": "Prepare Doc Content",
"type": "n8n-nodes-base.code",
"position": [
2832,
1824
],
"parameters": {
"jsCode": "for (const item of $input.all()) {\n if (item.json.output) {\n try {\n // 1. Parse the AI JSON output\n const data = JSON.parse(item.json.output);\n \n // 2. Strip HTML tags and convert to plain text\n const stripHtml = (html) => {\n if (!html) return \"\";\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\") // Convert line breaks to newlines\n .replace(/<\\/p>/gi, \"\\n\\n\") // Double newline after paragraphs\n .replace(/<\\/li>/gi, \"\\n\") // Newline after list items\n .replace(/<[^>]+>/g, \"\"); // Remove all remaining HTML tags\n };\n\n // 3. Extract clean title and body\n item.json.clean_title = data.title ? data.title.trim() : \"\";\n item.json.clean_body = data.body ? stripHtml(data.body).trim() : \"\";\n \n // Remove raw output to avoid downstream confusion\n delete item.json.output;\n\n } catch (e) {\n item.json.error = \"Parsing error: \" + e.message;\n }\n }\n}\n\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "716d3987-4094-49bd-8135-d0d1e7ec4611",
"name": "Create Google Doc",
"type": "n8n-nodes-base.googleDocs",
"position": [
3056,
1824
],
"parameters": {
"title": "={{ $json.clean_title }}",
"folderId": "default"
},
"credentials": {},
"typeVersion": 2
},
{
"id": "a4483c89-4d6f-4fc3-b747-4b7477e87667",
"name": "Tally Trigger",
"type": "n8n-nodes-tallyforms.tallyTrigger",
"position": [
1072,
1872
],
"parameters": {
"formId": "YOUR_TALLY_FORM_ID"
},
"credentials": {
"tallyApi": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "cf51c6b3-d0ea-433e-a78d-e0f8aa0e46ed",
"name": "Sticky Note - Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
464,
1616
],
"parameters": {
"width": 500,
"height": 964,
"content": "## Generate Blog Drafts from Content Briefs with Tally, OpenAI and Google Docs\n\n### How it works\n1. A marketer submits a content brief through a Tally form: topic, target audience, tone, keywords, CTA, and optional competitor URLs.\n2. The Tally Trigger fires on submission. Agent 1 parses the raw Tally payload into clean, structured brief fields.\n3. Agent 2 builds a detailed blog post outline with section headings and bullet points. If competitor URLs were provided in the Tally form, it factors their angles in.\n4. Agent 3 expands the outline into a complete blog post draft written in the specified tone.\n5. The finished draft is saved as a new Google Doc, ready for human review.\n\n### Setup\n1. Create your content brief form in Tally with fields: Blog Topic, Target Audience, Tone (dropdown), Keywords, CTA, Competitor URLs (optional).\n2. Connect the Tally Trigger node and select your Tally form from the dropdown.\n3. Add your OpenAI API key under Credentials > OpenAI.\n4. Add Google Docs OAuth2 credentials.\n5. Submit a test brief through your Tally form. A new Google Doc will appear in the root of your Drive.\n\n### Customization\n- Edit Agent 3's system prompt to enforce your brand voice or word count target.\n- Tally's conditional logic lets you show/hide the competitor URLs field based on a checkbox."
},
"typeVersion": 1
},
{
"id": "7b7f106a-57bb-445a-86f4-3a5422e6b9ee",
"name": "Sticky Note - Data Capture",
"type": "n8n-nodes-base.stickyNote",
"position": [
992,
1616
],
"parameters": {
"color": 7,
"width": 852,
"height": 584,
"content": "## Data Capture & Parsing\n\nTally Trigger captures the content brief. Agent 1 normalizes the raw form data into structured fields. The Code node cleans the AI output for downstream processing."
},
"typeVersion": 1
},
{
"id": "19210e56-3bf4-4636-9809-ead24200cfdd",
"name": "Sticky Note - AI Processing",
"type": "n8n-nodes-base.stickyNote",
"position": [
1888,
1616
],
"parameters": {
"color": 7,
"width": 804,
"height": 632,
"content": "## Outline & Draft Generation\n\nAgent 2 builds a structured blog outline with section headings and bullet points. Agent 3 expands it into a complete draft in the requested tone."
},
"typeVersion": 1
},
{
"id": "16cefba1-93d2-4d69-b67f-4148be316c9b",
"name": "Sticky Note - Output",
"type": "n8n-nodes-base.stickyNote",
"position": [
2736,
1616
],
"parameters": {
"color": 7,
"width": 708,
"height": 552,
"content": "## Google Docs Output\n\nPrepare Doc Content formats the draft. Create Google Doc saves it as a new document. Update Google Doc inserts the body text."
},
"typeVersion": 1
},
{
"id": "5a4e8a4d-e7c9-43d3-bc2e-6765fcc674fa",
"name": "Update Google Doc",
"type": "n8n-nodes-base.googleDocs",
"position": [
3264,
1824
],
"parameters": {
"actionsUi": {
"actionFields": [
{
"text": "={{ $('Prepare Doc Content').item.json.clean_body }}",
"action": "insert"
}
]
},
"operation": "update"
},
"credentials": {},
"typeVersion": 2
}
],
"connections": {
"GPT-5.4 Model": {
"ai_languageModel": [
[
{
"node": "Parse Content Brief",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Parse Outline": {
"main": [
[
{
"node": "Write Full Draft",
"type": "main",
"index": 0
}
]
]
},
"Tally Trigger": {
"main": [
[
{
"node": "Parse Content Brief",
"type": "main",
"index": 0
}
]
]
},
"GPT-5.4 Model 2": {
"ai_languageModel": [
[
{
"node": "Build Blog Outline",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"GPT-5.4 Model 3": {
"ai_languageModel": [
[
{
"node": "Write Full Draft",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Write Full Draft": {
"main": [
[
{
"node": "Prepare Doc Content",
"type": "main",
"index": 0
}
]
]
},
"Create Google Doc": {
"main": [
[
{
"node": "Update Google Doc",
"type": "main",
"index": 0
}
]
]
},
"Build Blog Outline": {
"main": [
[
{
"node": "Parse Outline",
"type": "main",
"index": 0
}
]
]
},
"Parse Brief Fields": {
"main": [
[
{
"node": "Build Blog Outline",
"type": "main",
"index": 0
}
]
]
},
"Parse Content Brief": {
"main": [
[
{
"node": "Parse Brief Fields",
"type": "main",
"index": 0
}
]
]
},
"Prepare Doc Content": {
"main": [
[
{
"node": "Create Google Doc",
"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.
openAiApitallyApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow turns a simple content brief into a full blog post draft, saved as a Google Doc ready for editing. It helps marketing teams go from idea to first draft in under a minute.
Source: https://n8n.io/workflows/15058/ — 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.
The best content automation template in the market is now even better—with “deep research” on time-sensitive topics\! Unlike most n8n content automation templates that are mainly for “demo purposes,”
Agent Nodes. Uses lmChatOpenAi, slack, stopAndError, errorTrigger. Event-driven trigger; 72 nodes.
Transcript Evalu8r V2 is a robust browser-based transcript analysis tool powered by Deepgram’s speech-to-text API and built into an n8n workflow template. This release introduces full in-browser audio
Transcript Evalu8r is an AI-powered transcript analysis workflow that automates the processing, visualization, and evaluation of transcribed conversations. This n8n workflow template is designed to he
Ask any question and get five different answers instantly. Each answer is written for a different audience—from kids to business executives. Your Telegram bot delivers all five explanations in under 1