This workflow corresponds to n8n.io template #16954 — we link there as the canonical source.
This workflow follows the Form → Form Trigger 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 →
{
"name": "Generate a sound effect variation pack from one brief using ElevenLabs and Google Drive",
"tags": [],
"nodes": [
{
"name": "When a Brief Is Submitted",
"type": "n8n-nodes-base.formTrigger",
"position": [
48,
288
],
"parameters": {
"options": {
"appendAttribution": false
},
"formTitle": "Sound effect variation pack",
"formFields": {
"values": [
{
"fieldType": "textarea",
"fieldLabel": "Description",
"placeholder": "e.g. heavy wooden door creaking open slowly in an empty hall",
"requiredField": true
},
{
"fieldType": "number",
"fieldLabel": "Number of takes",
"placeholder": "1 to 5 (default 3)"
},
{
"fieldType": "number",
"fieldLabel": "Duration seconds",
"placeholder": "0.5 to 30, leave blank to let ElevenLabs decide"
},
{
"fieldType": "number",
"fieldLabel": "Prompt influence",
"placeholder": "0 to 1, leave blank for 0.3"
}
]
},
"formDescription": "Describe one sound and get several ElevenLabs takes to choose from."
},
"typeVersion": 2.6
},
{
"name": "Plan Takes and Folder",
"type": "n8n-nodes-base.code",
"position": [
336,
336
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const DEFAULT_TAKES = 3;\nconst MAX_TAKES = 5;\nconst MIN_DURATION = 0.5;\nconst MAX_DURATION = 30;\nconst DEFAULT_INFLUENCE = 0.3;\nconst INFLUENCE_STEP = 0.1;\nconst PARENT_FOLDER_ID = 'PASTE_YOUR_SFX_OUTPUT_FOLDER_ID_HERE';\n\nconst clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));\nconst form = $input.first().json;\n\nconst brief = String(form['Description'] || '').trim();\n\nlet takes = Math.round(Number(form['Number of takes']) || DEFAULT_TAKES);\ntakes = clamp(takes, 1, MAX_TAKES);\n\nlet duration = null;\nconst rawDuration = form['Duration seconds'];\nif (rawDuration !== '' && rawDuration != null && !isNaN(Number(rawDuration))) {\n duration = clamp(Number(rawDuration), MIN_DURATION, MAX_DURATION);\n}\n\nlet baseInfluence = DEFAULT_INFLUENCE;\nconst rawInfluence = form['Prompt influence'];\nif (rawInfluence !== '' && rawInfluence != null && !isNaN(Number(rawInfluence))) {\n baseInfluence = clamp(Number(rawInfluence), 0, 1);\n}\n\nconst slug = brief.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'sound-effect';\nconst now = new Date();\nconst pad = (x) => String(x).padStart(2, '0');\nconst stamp = now.getFullYear() + pad(now.getMonth() + 1) + pad(now.getDate()) + '-' + pad(now.getHours()) + pad(now.getMinutes()) + pad(now.getSeconds());\nconst folderName = slug + '-' + stamp;\n\nconst list = [];\nfor (let i = 0; i < takes; i++) {\n const influence = clamp(Number((baseInfluence + i * INFLUENCE_STEP).toFixed(3)), 0, 1);\n list.push({ takeNumber: i + 1, prompt: brief, duration: duration, prompt_influence: influence });\n}\n\nreturn [{ json: { brief: brief, totalTakes: takes, duration: duration, baseInfluence: baseInfluence, folderName: folderName, parentFolderId: PARENT_FOLDER_ID, takes: list } }];",
"language": "javaScript"
},
"typeVersion": 2
},
{
"name": "Create Take Folder in Drive",
"type": "n8n-nodes-base.googleDrive",
"onError": "continueErrorOutput",
"maxTries": 3,
"position": [
528,
336
],
"parameters": {
"name": "={{ $json.folderName }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {
"simplifyOutput": true
},
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.parentFolderId }}"
},
"resource": "folder",
"operation": "create",
"authentication": "oAuth2"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 3,
"waitBetweenTries": 5000
},
{
"name": "Build Take Requests",
"type": "n8n-nodes-base.code",
"position": [
752,
256
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const plan = $('Plan Takes and Folder').first().json;\nconst takes = Array.isArray(plan.takes) ? plan.takes : [];\n\nreturn takes.map((t) => {\n const body = { text: t.prompt, prompt_influence: t.prompt_influence };\n if (t.duration != null) body.duration_seconds = t.duration;\n return {\n json: {\n takeNumber: t.takeNumber,\n totalTakes: plan.totalTakes,\n prompt: t.prompt,\n prompt_influence: t.prompt_influence,\n duration: t.duration,\n bodyJson: JSON.stringify(body),\n },\n pairedItem: { item: 0 },\n };\n});",
"language": "javaScript"
},
"typeVersion": 2
},
{
"name": "Generate Sound Effect",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"maxTries": 3,
"position": [
1104,
336
],
"parameters": {
"url": "https://api.elevenlabs.io/v1/sound-generation",
"method": "POST",
"options": {
"timeout": 120000,
"response": {
"response": {
"responseFormat": "file",
"outputPropertyName": "data"
}
}
},
"jsonBody": "={{ $json.bodyJson }}",
"sendBody": true,
"sendQuery": true,
"contentType": "json",
"specifyBody": "json",
"specifyQuery": "keypair",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "output_format",
"value": "mp3_44100_128"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.4,
"waitBetweenTries": 5000
},
{
"name": "Upload Take to Drive",
"type": "n8n-nodes-base.googleDrive",
"onError": "continueErrorOutput",
"maxTries": 3,
"position": [
1344,
256
],
"parameters": {
"name": "=take-{{ $('Build Take Requests').item.json.takeNumber }}.mp3",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Create Take Folder in Drive').first().json.id }}"
},
"resource": "file",
"operation": "upload",
"authentication": "oAuth2",
"inputDataFieldName": "data"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 3,
"waitBetweenTries": 5000
},
{
"name": "Record Successful Take",
"type": "n8n-nodes-base.code",
"position": [
1584,
240
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const file = $json;\nconst takeNumber = $('Build Take Requests').item.json.takeNumber;\nconst id = file.id || '';\nconst link = file.webViewLink || (id ? ('https://drive.google.com/file/d/' + id + '/view') : '');\nreturn {\n json: {\n takeNumber: takeNumber,\n status: 'ok',\n fileName: file.name || ('take-' + takeNumber + '.mp3'),\n link: link,\n },\n};",
"language": "javaScript"
},
"typeVersion": 2
},
{
"name": "Record Failed Take",
"type": "n8n-nodes-base.code",
"position": [
1584,
416
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "let takeNumber = null;\ntry { takeNumber = $('Build Take Requests').item.json.takeNumber; } catch (e) {}\nif (takeNumber == null) takeNumber = ($json.takeNumber != null ? $json.takeNumber : null);\nconst err = $json.error;\nconst message = (err && (err.message || err.description)) || (typeof err === 'string' ? err : '') || 'Generation or upload failed';\nreturn {\n json: {\n takeNumber: takeNumber,\n status: 'failed',\n fileName: '',\n link: '',\n error: String(message),\n },\n};",
"language": "javaScript"
},
"typeVersion": 2
},
{
"name": "Build Results Summary",
"type": "n8n-nodes-base.code",
"position": [
1856,
304
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const rows = $input.all().map((i) => i.json).filter(Boolean);\nrows.sort((a, b) => (a.takeNumber || 0) - (b.takeNumber || 0));\n\nconst plan = $('Plan Takes and Folder').first().json;\nconst brief = plan.brief || '';\nconst total = (plan.totalTakes != null ? plan.totalTakes : rows.length);\nconst folderId = $('Create Take Folder in Drive').first().json.id || '';\nconst folderUrl = folderId ? ('https://drive.google.com/drive/folders/' + folderId) : '';\n\nconst okRows = rows.filter((r) => r.status === 'ok');\nconst failedRows = rows.filter((r) => r.status !== 'ok');\nconst missing = Math.max(0, total - okRows.length - failedRows.length);\n\nconst esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n\nconst listItems = rows.map((r) => {\n if (r.status === 'ok') {\n return '<li>Take ' + esc(r.takeNumber) + ': <a href=\"' + esc(r.link) + '\" target=\"_blank\" rel=\"noopener\">' + esc(r.fileName || ('take-' + r.takeNumber + '.mp3')) + '</a></li>';\n }\n return '<li>Take ' + esc(r.takeNumber) + ': failed (' + esc(r.error || 'unknown error') + ')</li>';\n}).join('');\n\nconst folderLine = folderUrl\n ? '<p><strong>Folder:</strong> <a href=\"' + esc(folderUrl) + '\" target=\"_blank\" rel=\"noopener\">open the variation pack in Google Drive</a></p>'\n : '<p>The Drive folder link is unavailable.</p>';\n\nlet countLine = okRows.length + ' of ' + total + ' takes generated';\nif (failedRows.length) countLine += ', ' + failedRows.length + ' failed';\nif (missing) countLine += ', ' + missing + ' did not report back';\ncountLine += '.';\n\nconst html = '<div style=\"font-family:system-ui,Segoe UI,Arial,sans-serif;max-width:640px;line-height:1.55;\">' +\n '<h2>Your sound effect variation pack</h2>' +\n '<p><strong>Brief:</strong> ' + esc(brief) + '</p>' +\n '<p>' + countLine + '</p>' +\n folderLine +\n '<ul>' + listItems + '</ul>' +\n '</div>';\n\nreturn [{ json: { html: html, folderUrl: folderUrl, okCount: okRows.length, failedCount: failedRows.length, missingCount: missing, total: total, results: rows } }];",
"language": "javaScript"
},
"typeVersion": 2
},
{
"name": "Show the Variation Pack",
"type": "n8n-nodes-base.form",
"position": [
2064,
304
],
"parameters": {
"operation": "completion",
"respondWith": "showText",
"responseText": "={{ $json.html }}"
},
"typeVersion": 2.5
},
{
"name": "Explain Folder Problem",
"type": "n8n-nodes-base.form",
"position": [
752,
416
],
"parameters": {
"operation": "completion",
"respondWith": "showText",
"responseText": "<div style=\"font-family:system-ui,Segoe UI,Arial,sans-serif;max-width:640px;line-height:1.55;\"><h2>Output folder not ready</h2><p>The workflow could not create the Drive subfolder for this run. This usually means the output folder has not been set yet.</p><p>Open the <strong>Plan Takes and Folder</strong> node and set <code>PARENT_FOLDER_ID</code> to a real Google Drive folder ID, and make sure the Google Drive credential is connected on both Drive nodes. Then submit again.</p></div>"
},
"typeVersion": 2.5
},
{
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
-496
],
"parameters": {
"width": 1264,
"height": 572,
"content": "## Generate a sound effect variation pack from one brief using ElevenLabs and Google Drive\n\n### How it works\n\n1. A user submits one sound brief and a take count from 1 to 5 through an n8n form.\n2. A code step clamps the inputs and prepares one request per take, and a dated Drive subfolder is created for the run.\n3. Each take calls the ElevenLabs sound-generation API on its own, and because the API has no seed, every call returns a different result.\n4. Each returned MP3 is uploaded into the subfolder as take-1.mp3 through take-N.mp3.\n5. The form shows a link to the folder plus a per-take list marking each take ok or failed.\n\n### Setup steps\n\n- [ ] Create an ElevenLabs Header Auth credential named ElevenLabs with header name xi-api-key.\n- [ ] Create a Google Drive OAuth2 credential and select it on both Drive nodes.\n- [ ] Open Plan Takes and Folder and paste your output folder ID into PARENT_FOLDER_ID.\n- [ ] Leave the workflow inactive until both credentials and the folder ID are set.\n\n### Customization\n\nChange the default and maximum take count, adjust the duration clamp, or change how prompt influence is spread across takes in the Plan Takes and Folder node."
},
"typeVersion": 1
},
{
"name": "Sticky Collect",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
96
],
"parameters": {
"color": 7,
"width": 280,
"height": 384,
"content": "## Collect the brief\n\nOne form takes the sound description, the take count, and optional duration and influence."
},
"typeVersion": 1
},
{
"name": "Sticky Plan",
"type": "n8n-nodes-base.stickyNote",
"position": [
272,
96
],
"parameters": {
"color": 7,
"width": 720,
"height": 496,
"content": "## Plan takes and make a folder\n\nClamp the inputs, build one request per take, and create a dated Drive subfolder for this run."
},
"typeVersion": 1
},
{
"name": "Sticky Generate",
"type": "n8n-nodes-base.stickyNote",
"position": [
1024,
96
],
"parameters": {
"color": 7,
"width": 732,
"height": 472,
"content": "## Generate and store each take\n\nCall ElevenLabs once per take for a different result, upload each MP3, and record ok or failed."
},
"typeVersion": 1
},
{
"name": "Sticky Return",
"type": "n8n-nodes-base.stickyNote",
"position": [
1792,
96
],
"parameters": {
"color": 7,
"width": 470,
"height": 396,
"content": "## Return the variation pack\n\nCollapse the take results and show the folder link plus a per-take status list."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"connections": {
"Record Failed Take": {
"main": [
[
{
"node": "Build Results Summary",
"type": "main",
"index": 0
}
]
]
},
"Build Take Requests": {
"main": [
[
{
"node": "Generate Sound Effect",
"type": "main",
"index": 0
}
]
]
},
"Upload Take to Drive": {
"main": [
[
{
"node": "Record Successful Take",
"type": "main",
"index": 0
}
],
[
{
"node": "Record Failed Take",
"type": "main",
"index": 0
}
]
]
},
"Build Results Summary": {
"main": [
[
{
"node": "Show the Variation Pack",
"type": "main",
"index": 0
}
]
]
},
"Generate Sound Effect": {
"main": [
[
{
"node": "Upload Take to Drive",
"type": "main",
"index": 0
}
],
[
{
"node": "Record Failed Take",
"type": "main",
"index": 0
}
]
]
},
"Plan Takes and Folder": {
"main": [
[
{
"node": "Create Take Folder in Drive",
"type": "main",
"index": 0
}
]
]
},
"Record Successful Take": {
"main": [
[
{
"node": "Build Results Summary",
"type": "main",
"index": 0
}
]
]
},
"When a Brief Is Submitted": {
"main": [
[
{
"node": "Plan Takes and Folder",
"type": "main",
"index": 0
}
]
]
},
"Create Take Folder in Drive": {
"main": [
[
{
"node": "Build Take Requests",
"type": "main",
"index": 0
}
],
[
{
"node": "Explain Folder Problem",
"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.
googleDriveOAuth2ApihttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow collects a single sound-effect brief via an n8n form, generates multiple variation takes with the ElevenLabs sound-generation API, and uploads each MP3 into a timestamped Google Drive folder, then returns a results page with links and per-take status. Receives a…
Source: https://n8n.io/workflows/16954/ — 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.
Generate SRT and VTT subtitle files from a media URL using Gladia and Google Drive. Uses formTrigger, httpRequest, googleDrive, form. Event-driven trigger; 16 nodes.
This generate unique AI-powered music tracks using the ElevenLabs Music API.
Generate royalty-free sound effects for all your projects: ASMR, YouTube videos, podcasts, and more. This workflow generates unique AI-powered sound effects using the ElevenLabs Sound Effects API.
This workflow allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t
LDX hub All Services Demo. Uses formTrigger, httpRequest, form, n8n-nodes-ldxhub. Event-driven trigger; 53 nodes.