This workflow revolutionizes ad creative testing by automatically generating multiple ad variations and instantly testing them with AI personas to predict performance. Starting with a single headline and description, it uses Google Gemini to create 8 creative variations that maintain the core message while exploring different angles. These variations are then tested against Rally's AI audience using voting mode to simulate real user choice behavior, with results ranked by predicted click-through rates. Perfect for marketers who need to rapidly test creative concepts before committing to ad spend.
{
"active": false,
"connections": {
"Ask Rally": {
"main": [
[
{
"index": 0,
"node": "Get Individual Votes",
"type": "main"
}
]
]
},
"Basic LLM Chain": {
"main": [
[
{
"index": 0,
"node": "Ask Rally",
"type": "main"
}
]
]
},
"Calculate CTR": {
"main": [
[
{
"index": 0,
"node": "Display Report",
"type": "main"
}
]
]
},
"Get Individual Votes": {
"main": [
[
{
"index": 0,
"node": "Calculate CTR",
"type": "main"
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"index": 0,
"node": "Basic LLM Chain",
"type": "ai_languageModel"
}
]
]
},
"On form submission": {
"main": [
[
{
"index": 0,
"node": "Basic LLM Chain",
"type": "main"
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"index": 0,
"node": "Basic LLM Chain",
"type": "ai_outputParser"
}
]
]
}
},
"id": "Tl8197fxk9xDTBIK",
"meta": {
"instanceId": "7aa2e96d57ff40383569724f8ecb13d674a87bf09d39aa5d8fa5ba31f7a8407a",
"templateCredsSetupCompleted": true
},
"name": "Fan Out Creative Testing",
"nodes": [
{
"id": "c6fb516a-aa31-4d5b-9c9c-9f308a5b79c0",
"name": "On form submission",
"parameters": {
"formDescription": "Enter your creative idea",
"formFields": {
"values": [
{
"fieldLabel": "Brand",
"fieldType": "textarea",
"placeholder": "Ladder is a growth marketing agency that has run 10,000 experiments across all major growth channels. We test and learn what works so you don\u0027t have to. We have offices in New York, London, and across the EU. We have worked with clients such as Monzo Bank, Booking.com, and Time Out Magazine."
},
{
"fieldLabel": "Headline",
"placeholder": "Growth without the guesswork",
"requiredField": true
},
{
"fieldLabel": "Description",
"placeholder": "We\u0027ve run 10,000 experiments to learn what works so you don\u0027t have to ",
"requiredField": true
}
]
},
"formTitle": "Ask anything",
"options": {},
"responseMode": "lastNode"
},
"position": [
-480,
-60
],
"type": "n8n-nodes-base.formTrigger",
"typeVersion": 2.2,
"webhookId": "6c20a59e-075a-4070-850e-95353cccabf9"
},
{
"credentials": {
"httpBearerAuth": {
"id": "HeUQ9K9oUtQ5TyHr",
"name": "Bearer Auth account"
}
},
"id": "1be487f1-f971-4ee9-b199-16aa4d39ab35",
"name": "Ask Rally",
"parameters": {
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "smart",
"value": "false"
},
{
"name": "provider",
"value": "google"
},
{
"name": "query",
"value": "=You\u0027re searching on Google and you see the following ads. Which would you click?:\n\n{{ $json.output.map((ad, index) =\u003e `${String.fromCharCode(97 + index)}) ${ad.Headline}: ${ad.Description}`).join(\u0027\\n\\n\u0027) }}"
},
{
"name": "audience_id",
"value": "rb842b547c27640"
},
{
"name": "voting_mode",
"value": "true"
}
]
},
"genericAuthType": "httpBearerAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "accept",
"value": "application/json"
}
]
},
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"url": ""
},
"position": [
340,
-60
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
},
{
"id": "7e2a7f01-31bb-4ac1-975c-494337172639",
"name": "Basic LLM Chain",
"parameters": {
"batching": {},
"hasOutputParser": true,
"promptType": "define",
"text": "=Come up with 8 new very creative variations on this Google Ad by changing the Headline, Description or both. Do not deviate from the core creative idea, just express it in more creative ways. Headlines in text ads can be up to 30 characters each, while descriptions can be 90 characters each. Only respond with an array of Headline and Description combinations. Always start with the original Headline and Description you were given as the first in the list, then add your 8 new variations after.\n\n## Headline and Description\nHeadline: {{ $json.Headline }}\nDescription: {{ $json.Description }}\n\n## Brand\nHere is more information about the brand, you can use any of this so long as the core creative idea remains the same:\n{{ $json.Brand }}\n\n## Examples\nHere are some examples to show you what the task looks like when done well. You should not use these examples in your output, always make new variations relevant to the brand. \n\n**Original Ad:**\n- Headline: \"Fresh Coffee Daily\"\n- Description: \"Artisan roasted beans from around the world. Visit our cozy downtown location today!\"\n- Brand: \"Bean \u0026 Brew Coffee House - A family-owned coffee shop serving the community since 1985. Known for our warm atmosphere, friendly baristas, and commitment to fair trade coffee.\"\n\n**Output:**\n\n```json\n[\n {\n \"Headline\": \"Fresh Coffee Daily\",\n \"Description\": \"Artisan roasted beans from around the world. Visit our cozy downtown location today!\"\n },\n {\n \"Headline\": \"Artisan Coffee Hub\", \n \"Description\": \"From bean to cup perfection. Join our coffee family at the heart of downtown!\"\n },\n {\n \"Headline\": \"Your Daily Brew Fix\",\n \"Description\": \"Global beans, local love. Discover your new favorite spot in downtown today!\"\n },\n {\n \"Headline\": \"Coffee Craft Masters\",\n \"Description\": \"Handcrafted perfection in every cup. Visit our warm, welcoming coffee sanctuary!\"\n },\n {\n \"Headline\": \"Bean Perfection Daily\",\n \"Description\": \"Fair trade excellence meets downtown charm. Taste the difference quality makes!\"\n },\n {\n \"Headline\": \"Roasted Fresh Today\",\n \"Description\": \"Today\u0027s fresh roast awaits you. Step into our cozy community coffee corner!\"\n },\n {\n \"Headline\": \"Coffee Art \u0026 Heart\",\n \"Description\": \"Where passion meets the perfect cup. Experience our family tradition downtown!\"\n },\n {\n \"Headline\": \"Premium Beans Daily\",\n \"Description\": \"Exceptional beans, exceptional experience. Your new downtown coffee home awaits!\"\n }\n]\n```"
},
"position": [
-160,
-60
],
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.7
},
{
"credentials": {
"googlePalmApi": {
"id": "cWM5zhRjuahlwVKK",
"name": "Google Gemini(PaLM) Api account"
}
},
"id": "a4676bde-62da-4917-844a-3a9c5d81ab3f",
"name": "Google Gemini Chat Model",
"parameters": {
"modelName": "models/gemini-2.5-flash",
"options": {}
},
"position": [
-180,
160
],
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"typeVersion": 1
},
{
"id": "1dc2e11f-19a5-4236-b336-32e71697fd2b",
"name": "Structured Output Parser",
"parameters": {
"inputSchema": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"array\",\n \"title\": \"Google Ad Variations\",\n \"description\": \"An array of creative Google Ad headline and description combinations\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"Headline\": {\n \"type\": \"string\",\n \"maxLength\": 30,\n \"description\": \"The ad headline, maximum 30 characters\"\n },\n \"Description\": {\n \"type\": \"string\",\n \"maxLength\": 90,\n \"description\": \"The ad description, maximum 90 characters\"\n }\n },\n \"required\": [\"Headline\", \"Description\"],\n \"additionalProperties\": false\n },\n \"minItems\": 9,\n \"maxItems\": 9,\n \"uniqueItems\": true\n}",
"schemaType": "manual"
},
"position": [
40,
160
],
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.3
},
{
"id": "43b5a382-2d7e-40ec-95fc-1a67335f4044",
"name": "Sticky Note",
"parameters": {
"color": 5,
"content": "## Synthetic Testing\nTake 8 new variations and test them against the original.",
"height": 500,
"width": 540
},
"position": [
260,
-160
],
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1
},
{
"id": "1df2417b-1f17-483f-a402-b593ee1db835",
"name": "Sticky Note1",
"parameters": {
"content": "## Fan Out Creative\nGenerate 8 new creative variations based on the original.",
"height": 500,
"width": 540
},
"position": [
-300,
-160
],
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1
},
{
"id": "40925b49-241d-442b-806e-04a5a4323953",
"name": "Get Individual Votes",
"parameters": {
"fieldToSplitOut": "responses",
"options": {}
},
"position": [
560,
-60
],
"type": "n8n-nodes-base.splitOut",
"typeVersion": 1
},
{
"id": "a8b1cb15-1d95-4f83-9855-526e110302de",
"name": "Calculate CTR",
"parameters": {
"jsCode": "// Count occurrences of each option\nlet optionCounts = {};\nlet total = 0;\n\nfor (const item of $input.all()) {\n try {\n // Parse the response JSON\n const responseData = JSON.parse(item.json.response);\n \n // Get the option value\n const option = responseData.option;\n \n if (option) {\n total++;\n // Initialize count for this option if it doesn\u0027t exist\n if (!optionCounts[option]) {\n optionCounts[option] = 0;\n }\n optionCounts[option]++;\n }\n } catch (error) {\n // Handle cases where response is not valid JSON\n console.log(`Error parsing response for item ${item.json.persona_id}:`, error.message);\n }\n}\n\n// Get ad variations using n8n syntax\nlet adVariations = [];\ntry {\n adVariations = $(\u0027Basic LLM Chain\u0027).first().json.output;\n} catch (error) {\n console.log(\"Error getting ad variations:\", error.message);\n}\n\n// Marry up options with ad variations and calculate percentages\nconst variationsWithAds = [];\n\nfor (const [option, count] of Object.entries(optionCounts)) {\n const percentage = total \u003e 0 ? (count / total * 100).toFixed(1) : 0;\n \n // Convert option letter to index (a=0, b=1, c=2, etc.)\n const optionIndex = option.charCodeAt(0) - 97; // \u0027a\u0027.charCodeAt(0) = 97\n \n // Get the corresponding ad variation\n const adVariation = adVariations[optionIndex] || { Headline: \"Unknown\", Description: \"Unknown\" };\n \n variationsWithAds.push({\n option: option,\n count: count,\n percentage: parseFloat(percentage),\n headline: adVariation.Headline,\n description: adVariation.Description\n });\n \n console.log(`Option \u0027${option}\u0027: ${adVariation.Headline} - ${count} clicks (${percentage}%)`);\n}\n\n// Sort by CTR percentage descending\nvariationsWithAds.sort((a, b) =\u003e b.percentage - a.percentage);\n\nconsole.log(`\\nTotal responses: ${total}`);\nconsole.log(`CTR by variation (sorted by performance):`);\nvariationsWithAds.forEach(v =\u003e {\n console.log(` ${v.option.toUpperCase()}) ${v.headline}: ${v.description} | CTR: ${v.percentage}%`);\n});\n\n// Return comprehensive results\nreturn {\n total: total,\n variationsWithAds: variationsWithAds\n};"
},
"position": [
340,
180
],
"type": "n8n-nodes-base.code",
"typeVersion": 2
},
{
"id": "2194d5a2-caf2-40a7-a5c5-bc97dc6e01fe",
"name": "Display Report",
"parameters": {
"operation": "completion",
"respondWith": "showText",
"responseText": "=\u003ch1\u003e{{ $json.title || \u0027Ad Variation CTR Results\u0027 }}\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003eTotal Responses:\u003c/strong\u003e {{ $json.total }}\u003c/p\u003e\n\u003cbr\u003e\n\u003cp\u003e-----\u003c/p\u003e\n\u003chr\u003e\n\u003cbr\u003e\n\u003c!-- each variation with ad content, sorted by CTR descending --\u003e\n{{ $json.variationsWithAds\n .map(v =\u003e `\u0026bull;\u0026nbsp;\u003cstrong\u003e${v.option}) ${v.headline}:\u003c/strong\u003e ${v.description} \u003cbr\u003e \u003cstrong\u003eCTR: ${v.percentage}%\u003c/strong\u003e\u003cbr\u003e\u003cbr\u003e`)\n .join(\u0027\u0027) }}"
},
"position": [
560,
180
],
"type": "n8n-nodes-base.form",
"typeVersion": 1,
"webhookId": "205dca97-541a-4635-9957-4644c6400fa2"
}
],
"pinData": {
"On form submission": [
{
"json": {
"Brand": "Ladder is a growth marketing agency that has run 10,000 experiments across all major growth channels. We test and learn what works so you don\u0027t have to. We have offices in New York, London, and across the EU. We have worked with clients such as Monzo Bank, Booking.com, and Time Out Magazine.",
"Description": "We\u0027ve run 10,000 experiments to test and learn what works so you don\u0027t have to.",
"Headline": "Growth without the guesswork",
"formMode": "test",
"submittedAt": "2025-06-21T07:50:30.672-04:00"
}
}
]
},
"settings": {
"executionOrder": "v1"
},
"tags": [],
"versionId": "dab232cf-99e1-419c-a2ad-33562adc0db6"
}