This workflow corresponds to n8n.io template #14022 — we link there as the canonical source.
This workflow follows the Agent → Google Sheets 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 →
{
"nodes": [
{
"id": "ac555343-470d-4358-8a80-83fcb1f25680",
"name": "Cut out Duplicate Songs",
"type": "n8n-nodes-base.code",
"position": [
-1056,
32
],
"parameters": {
"jsCode": "const seen = new Set();\n\nreturn items\n .map(item => {\n const track = item.json.track;\n return {\n json: {\n trackName: track.name,\n artistName: track.artists[0].name,\n spotifyUrl: track.external_urls?.spotify || ''\n }\n };\n })\n .filter(item => {\n const key = item.json.spotifyUrl;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });"
},
"typeVersion": 2
},
{
"id": "4385abe2-943e-41c1-b892-50a155c9970c",
"name": "Getting Lyrics",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
-864,
32
],
"parameters": {
"url": "=https://lrclib.net/api/search?track_name={{ $json.trackName }}&artist_name={{ $json.artistName }}",
"options": {}
},
"retryOnFail": true,
"typeVersion": 4.4
},
{
"id": "5562208c-a03f-4acf-aff2-52e3ae116417",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-992,
512
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "25c8d472-8d61-466d-a4e4-26daa4b44c51",
"name": "Every Sunday at noon",
"type": "n8n-nodes-base.scheduleTrigger",
"disabled": true,
"position": [
-1472,
32
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 12 * * 7"
}
]
}
},
"typeVersion": 1.3
},
{
"id": "ea5d8c41-f819-466b-8a9e-45650617df8f",
"name": "Combine existing Words with new Words",
"type": "n8n-nodes-base.code",
"position": [
-320,
32
],
"parameters": {
"jsCode": "const lyricsData = $('Put all Lyrics together').first().json;\n\nconst existingWords = items\n .map(item => item.json.Word)\n .filter(w => w);\n\nreturn [{\n json: {\n allLyrics: lyricsData.allLyrics,\n songCount: lyricsData.songCount,\n existingWords: existingWords\n }\n}];"
},
"typeVersion": 2
},
{
"id": "8b180ba6-72d7-49e3-a3a3-ff2c92d8a5a0",
"name": "Append to All Vocabularies",
"type": "n8n-nodes-base.googleSheets",
"position": [
96,
448
],
"parameters": {
"columns": {
"value": {
"Word": "={{ $json.Word }}",
"Translation": "={{ $json.Translation }}"
},
"schema": [
{
"id": "Word",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Word",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Translation",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Translation",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0",
"cachedResultName": "All Vocabularies"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=drivesdk",
"cachedResultName": "v"
}
},
"typeVersion": 4.7
},
{
"id": "ce615da7-f013-4ecd-84fb-9e5f8d7ea435",
"name": "Append to Weekly",
"type": "n8n-nodes-base.googleSheets",
"position": [
96,
256
],
"parameters": {
"columns": {
"value": {
"Word": "={{ $json.Word }}",
"Translation": "={{ $json.Translation }}"
},
"schema": [
{
"id": "Word",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Word",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Translation",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Translation",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "={{ $('Parse, Deduplicate, Week Code').first().json.sheetName }}"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=drivesdk",
"cachedResultName": "V"
}
},
"typeVersion": 4.7
},
{
"id": "f6450cae-151f-4581-88a6-70a8aea78a40",
"name": "Create sheet",
"type": "n8n-nodes-base.googleSheets",
"onError": "continueRegularOutput",
"position": [
96,
48
],
"parameters": {
"title": "={{ $json.sheetName }}",
"options": {},
"operation": "create",
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=drivesdk",
"cachedResultName": "V"
}
},
"retryOnFail": false,
"typeVersion": 4.7
},
{
"id": "0d01d6fb-ab2e-4f9f-a58d-e4a23fef7091",
"name": "Cut out SheetName Column",
"type": "n8n-nodes-base.code",
"position": [
-80,
256
],
"parameters": {
"jsCode": "return items.map(item => ({\n json: {\n Word: item.json.Word,\n Translation: item.json.Translation\n }\n}));"
},
"typeVersion": 2
},
{
"id": "320a4012-9d05-4f84-8ea5-1d3057903760",
"name": "Parse, Deduplicate, Week Code",
"type": "n8n-nodes-base.code",
"position": [
-400,
256
],
"parameters": {
"jsCode": "// 1. Parse AI Output\nconst results = [];\nfor (const item of items) {\n const output = item.json.output || '';\n const clean = output.replace(/```json\\n?/g, '').replace(/```/g, '').trim();\n try {\n const words = JSON.parse(clean);\n for (const word of words) {\n results.push({\n Word: word.word,\n Translation: word.translation.replace(/\\s*\\(.*?\\)\\s*$/, '')\n });\n }\n } catch (e) {\n console.log('Parse Error:', e.message);\n console.log('Raw output:', output);\n }\n}\n\n// 2. Deduplicate\nconst existingWords = $('Combine existing Words with new Words')\n .first()\n .json\n .existingWords\n .map(w => String(w || '').toLowerCase().trim());\n\n\nconst unique = results.filter(r =>\n !existingWords.includes(r.Word.toLowerCase().trim())\n);\n\n// 3. Week Number\nconst now = new Date();\nconst startOfYear = new Date(now.getFullYear(), 0, 1);\nconst weekNumber = Math.ceil(((now - startOfYear) / 86400000 + startOfYear.getDay() + 1) / 7);\nconst sheetName = `Week ${weekNumber}`;\n\nreturn unique.map(word => ({\n json: {\n Word: word.Word,\n Translation: word.Translation,\n sheetName: sheetName\n }\n}));"
},
"typeVersion": 2
},
{
"id": "85b1c956-61a4-42e8-b55a-51399a9bb136",
"name": "Read all Vocabularies",
"type": "n8n-nodes-base.googleSheets",
"position": [
-496,
32
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0",
"cachedResultName": "All Vocabularies"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=drivesdk",
"cachedResultName": "v"
}
},
"typeVersion": 4.7,
"alwaysOutputData": true
},
{
"id": "a3b0fb08-7bf9-4472-a4a2-694e1eeedc5a",
"name": "Put all Lyrics together",
"type": "n8n-nodes-base.code",
"position": [
-672,
32
],
"parameters": {
"jsCode": "const songs = {};\n\nfor (const item of items) {\n const data = item.json;\n \n let cleanTrack = (data.trackName || '')\n .replace(/\\s*[\\(\\[].*?[\\)\\]]/g, '')\n .replace(/^.*?\\s*-\\s*\"?/, '')\n .replace(/['\"\\\\]/g, '')\n .trim()\n .toLowerCase();\n \n let cleanArtist = (data.artistName || '')\n .replace(/\\s*-\\s*Topic$/i, '')\n .trim()\n .toLowerCase();\n \n const key = cleanArtist + ' - ' + cleanTrack;\n const lyrics = data.plainLyrics || '';\n \n if (!songs[key] || lyrics.length > songs[key].plainLyrics.length) {\n songs[key] = {\n trackName: data.trackName || '',\n artistName: cleanArtist,\n plainLyrics: lyrics.replace(/[\\n\\r]+/g, ' ').replace(/\"/g, '\\\\\"')\n };\n }\n}\n\nlet allLyrics = '';\nconst uniqueSongs = Object.values(songs).filter(song => song.plainLyrics.length > 0);\n\nfor (const song of uniqueSongs) {\n allLyrics += `\\n\\n--- ${song.trackName} by ${song.artistName} ---\\n${song.plainLyrics}`;\n}\n\nreturn [{\n json: {\n allLyrics: allLyrics.trim(),\n songCount: uniqueSongs.length\n }\n}];"
},
"typeVersion": 2
},
{
"id": "d2f489e3-6d7b-459d-8c8b-5de5fbd416e0",
"name": "Get the recently played Songs",
"type": "n8n-nodes-base.spotify",
"position": [
-1264,
32
],
"parameters": {
"limit": 20,
"operation": "recentlyPlayed"
},
"typeVersion": 1
},
{
"id": "1746123a-c0f4-4647-af27-5405a57489f9",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1264,
-16
],
"parameters": {
"color": "#E6E6E6",
"width": 496,
"height": 96,
"content": "### \ud83c\udfb5 Fetches recently played songs from Spotify, removes duplicates, and gets lyrics from lrclib.net.\n\n"
},
"typeVersion": 1
},
{
"id": "f492d8c4-f78a-4beb-9d4b-ed8fe971e083",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-672,
-16
],
"parameters": {
"color": "#EBEBEB",
"width": 464,
"height": 96,
"content": "### \ud83d\udcd6 Reads all previously learned words from Google Sheets for duplicate checking.\n\n\n"
},
"typeVersion": 1
},
{
"id": "3f5c49f9-8034-485a-83c4-0746038de529",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-704,
464
],
"parameters": {
"color": "#FFFFFF",
"width": 304,
"height": 96,
"content": "## \ud83e\udd16 AI EXTRACTION\n\u27a1\ufe0f To change the language: edit the prompt."
},
"typeVersion": 1
},
{
"id": "950ce12a-f627-4564-8892-30c670fa4192",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-736,
256
],
"parameters": {
"text": "=All Lyrics:\n{{ $json.allLyrics }}",
"options": {
"systemMessage": "=You are a vocabulary extraction assistant for language learners.\n\nInstructions:\n- You will receive lyrics from multiple songs\n- Extract a total of 40-60 of the most useful English vocabulary words across ALL songs\n- NO duplicates - each word only once, even if it appears in multiple songs\n- Skip proper nouns, names, slang abbreviations, and filler words\n- Skip very basic words like \"I\", \"you\", \"the\", \"is\", \"and\", \"to\", \"a\"\n- If a word is slang, extract its standard English equivalent\n- For each word provide the German translation\n- Focus on B1-B2 level vocabulary that is useful for everyday conversation\n\n\nRespond ONLY with a valid JSON array, no other text:\n[{\"word\": \"new vocabulary word\", \"translation\": \"translation in the learner's native language\"}]"
},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "fd131c0f-f8a9-4f34-933c-8cfbd939023a",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1968,
-192
],
"parameters": {
"width": 448,
"height": 656,
"content": "## How it works\n\nEvery Sunday it fetches your recently played songs, finds the lyrics via lrclib.net, and uses Google Gemini to extract 40-60 useful vocabulary words (B1-B2 level). New words are deduplicated against all previously learned words and written to Google Sheets \u2014 both a master tab and a weekly tab.\n\nYou review the flashcards using the free Flashcard Lab app (iOS + Android), which reads directly from Google Sheets with built-in spaced repetition.\n\n### Setup\n1. Google Cloud Console: Create project, enable Sheets + Drive API, create OAuth2 credentials, set app to \"In Production\"\n2. Get a free Gemini API key from Google AI Studio\n3. Spotify Developer Dashboard: Create app, note Client ID + Secret\n4. Create a Google Sheet with tab \"All Vocabularies\" and headers \"Word\" + \"Translation\"\n5. Import workflow, connect credentials, select your Sheet in all Google Sheets nodes\n6. Test with \"Execute Workflow\", then enable the schedule trigger\n\n### Customization\nTo change the language: edit the prompt in \"Prepare all Lyrics into Pairs\" \u2014 that's the only place to change. Listen to music in the language you're learning for best results."
},
"typeVersion": 1
},
{
"id": "868c82e0-239c-40d2-a005-31103592e1d1",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
320,
256
],
"parameters": {
"color": 3,
"width": 224,
"height": 128,
"content": "\u26a0\ufe0f Set your Google Cloud app to \"In Production\" \u2014 in \"Testing\" mode, tokens expire after 7 days and break the weekly automation."
},
"typeVersion": 1
},
{
"id": "7a525920-6616-4cca-8207-165635336ae0",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
-160
],
"parameters": {
"color": "#EBEBEB",
"width": 304,
"height": 192,
"content": "## \ud83d\udcdd SAVE TO SHEETS\n\nCreates a weekly tab (e.g. \"Week 11\") and writes\nnew words there + to the master \"All Vocabularies\" tab.\nUse Flashcard Lab app to study from the weekly tabs.\n\n"
},
"typeVersion": 1
}
],
"connections": {
"AI Agent": {
"main": [
[
{
"node": "Parse, Deduplicate, Week Code",
"type": "main",
"index": 0
}
]
]
},
"Getting Lyrics": {
"main": [
[
{
"node": "Put all Lyrics together",
"type": "main",
"index": 0
}
]
]
},
"Every Sunday at noon": {
"main": [
[
{
"node": "Get the recently played Songs",
"type": "main",
"index": 0
}
]
]
},
"Read all Vocabularies": {
"main": [
[
{
"node": "Combine existing Words with new Words",
"type": "main",
"index": 0
}
]
]
},
"Cut out Duplicate Songs": {
"main": [
[
{
"node": "Getting Lyrics",
"type": "main",
"index": 0
}
]
]
},
"Put all Lyrics together": {
"main": [
[
{
"node": "Read all Vocabularies",
"type": "main",
"index": 0
}
]
]
},
"Cut out SheetName Column": {
"main": [
[
{
"node": "Append to Weekly",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Get the recently played Songs": {
"main": [
[
{
"node": "Cut out Duplicate Songs",
"type": "main",
"index": 0
}
]
]
},
"Parse, Deduplicate, Week Code": {
"main": [
[
{
"node": "Create sheet",
"type": "main",
"index": 0
},
{
"node": "Cut out SheetName Column",
"type": "main",
"index": 0
},
{
"node": "Append to All Vocabularies",
"type": "main",
"index": 0
}
]
]
},
"Combine existing Words with new Words": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow turns your Spotify listening history into vocabulary flashcards for language learning.
Source: https://n8n.io/workflows/14022/ — 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 Multi-Model Agency Content Engine is a high-performance editorial system designed for agencies. It solves the "blank page" problem by alternating between real-world social proof and strategic expe
This workflow automates the complete blog publishing process. It removes manual work from content creation, image generation, category management, and WordPress publishing by using AI and n8n. It help
This project is an automated news publisher for LinkedIn. It uses RSS feeds to fetch news, processes the content with the Gemini API to generate precise summaries, and automatically publishes to Linke
This workflow is the AI analysis and alerting engine for a complete social media monitoring system. It's designed to work with data scraped from X (formerly Twitter) using a tool like the Apify Tweet
LinkedIn Job Search → Hiring Manager Outreach. Uses httpRequest, agent, lmChatGoogleGemini, outputParserStructured. Scheduled trigger; 38 nodes.