
Spotify Playlist AI Automation
On building things
(with just enough arrogance to try)
Happening upon new music should be one of life's little pleasures but often feels like gold panning with straight trash (ie. swirling endless algorithmic sludge that somehow manages to be both generic and weirdly specific at the same time).
I think Spotify's existing AI playlist generator exemplifies this perfectly. I just never seem to have good luck with its suggestions. The algorithm might know which are the "popular songs", but it has no real understanding of what I actually need (here I am throwing shade as if I understand it myself).
This disconnect made me wonder, ‘what if I could build something that understood my context the way a human curator (a neurodivergent one at that) would?’ Something that could take a mood and activity combo, cross-reference it against actual taste preferences, and produce a playlist that felt genuinely mine, without the manual labour?
The ‘problem’ with algorithmic curation
I think existing playlist generators often fail (solely based on my own experience) because they optimise for engagement metrics rather than coherence. They focus on what's popular, what keeps people listening, and what drives platform retention.
What they don't know is why you need James Taylor right now or you’ll scream, or that when you say "driving music", you mean something very specific about reliving your teenage angst amidst the traffic so that it makes sitting in a car feel less boring.
Human curation works because it starts with intent and builds from there. Algorithmic curation starts with data patterns and hopes they align with what you actually want.
I wanted (nay, needed) something that could bridge this gap, maintaining the convenience of automation while preserving the intentionality of (my very specific) human taste.
Designing the system: Five connected domains
Rather than trying to solve everything at once, I broke my challenge into distinct domains, each with its own purpose and tools:
- Capture: How do I input what I want?
- Context: What existing knowledge can inform the decision?
- Ideation: How do I generate a coherent list of songs?
- Execution: How do I turn that list into an actual playlist?
- Feedback: How do I close the loop and learn from the results?
The key insight was that only one of these domains (ideation) needed to be "intelligent". Everything else could be deterministic, reliable, and debuggable.
Domain 1: Capture (Notion)
First I built a simple form in Notion with six fields:
- Date / Name (for reference)
- Current mood
- Target mood
- Planned activity
- Extra context (era, genre, specific requirements)
- Track count (usually 20-35 for coherent flow)
The responses all sync to a database that stores the playlist requests and outcomes.
The form now lives as a bookmark/shortcut on my phone. The friction between "I want music for this mood" and "I have a curated playlist" is now three taps and ±1 minute of limbo away.
Domain 2: Context
(Notion + Light Processing)
Before asking AI to generate anything, the system enriches each request with relevant context from my listening history. I have a separate (editable) database of artists I like (pulled from over 500 of my previously liked/saved songs). So not everything I've ever played, just the ones I'd want to hear more of.
When a new request comes in, the workflow pulls this artist list and trims it to a single, usable string. This gives the AI a rough flavour profile to work with while keeping the overall prompt manageable.
Domain 3: Ideation
(n8n + OpenAI GPT)
This is where the magic happens, but it's carefully constrained magic.
The AI receives:
- The mood/activity brief
- The list of artists that I like
- A structured JSON schema for the response
- Hard rules about era accuracy and genre consistency
- A built-in scoring rubric to self-assess relevance
The prompt includes specific guardrails: if I mention "1970s", every track must actually be from that decade. If I want "no ballads", the model treats that as a Boolean filter, not a suggestion.
Note: the AI itself never touches Spotify directly. It simply produces a plan with a JSON object with a title, description, and list of {artist, track} pairs.
Domain 4: Execution
(n8n + Spotify API)
Slide 1 of 3
Once I have my plan, everything becomes deterministic. The workflow is set up to:
- Create an empty Spotify playlist with the proposed title and description
- Loop through each track in the plan
- Search Spotify for that specific artist/track combination (and availability in my region)
- Add found tracks to the playlist (skip any that don't exist or aren’t available)
- Respect rate limits with appropriate throttling
The system handles failures gracefully: a missing track doesn't break the entire playlist, and rate limit errors trigger appropriate delays rather than crashes.
Domain 5: Feedback
(Notion Updates)
When everything completes, the original request row in Notion updates automatically. The Spotify playlist URL gets added, and I have a permanent record of what I asked for and what I got.
If something fails upstream (OpenAI is down, Spotify authentication expires), an error branch captures the details and marks the request as "Failed" with any diagnostic information.
The Critical Piece
Proper Prompt Engineering
Getting the AI to understand my specific nuance required precision. My first attempts produced playlists like “Acoustic Gems of the '60s & '70s" that included Drake, Mumford & Sons and a little Lana Del Rey (only one mention of Simon & Garfunkel, which is obviously unacceptable).
The solution wasn't more creative prompting (although I did beef it up a little), it was more structured constraints:
- Era fidelity: "If the brief mentions a decade, every track MUST be from that era ± 3 years."
- Genre consistency: "If a style keyword appears, at least 80% of tracks must match that style."
- Relevance scoring: "For each candidate song, ask: 'Would a human curator say this clearly fits the brief?' Remove any scoring less than 4 out of 5."
These rules transformed vague AI creativity into focused curation that actually matched intent.
Results and Reflections
After refining the prompt engineering, the system consistently produces playlists that feel hand-curated. "Rainy Day Reverie" became exactly what I wanted: James Taylor, Crosby Stills Nash & Young, The Seekers, and other period-appropriate artists that matched both the era and the contemplative mood I was after.
The satisfaction wasn't just in having a good playlist. It was in solving a problem (that I made up) systematically. Breaking down "I want better music discovery" into discrete, solvable pieces and then orchestrating those pieces into something that actually works.
The entire project took about 4.5 hours after dinner on a rainy evening. Not because the individual components were particularly complex, but because getting them to work together harmoniously required patience and iteration.
The Technical Deep Dive
My Actual n8n Workflow Architecture
System Architecture Overview
This is the complete technical breakdown of building an automated Spotify playlist generator using n8n, OpenAI, and Notion.
The workflow orchestrates five key services:
- Notion: Form capture and data persistence
- OpenAI GPT-4o: Playlist ideation and curation logic
- Spotify Web API: Playlist creation and track management
- n8n: Workflow orchestration and error handling
- JavaScript: Data transformation and rate limiting
Data Flow Diagram
Notion Form → Notion Trigger → Flatten Fields → Query Liked Artists → Merge Context → OpenAI Ideation → Create Playlist → Split Tracks → [Loop: Search Track → Add to Playlist] → Update Notion RecordMilestone-by-Milestone Build Process
This section is for those who want to understand exactly how my workflow operates under the hood.
Milestone 0: Workflow Foundation
Workflow Foundation
Setup Requirements:
- New n8n workflow (blank canvas, no default Start node)
- Settings: Enable "Execute Single Nodes in Own Process" (prevents OpenAI crashes from killing entire runs)
- Notion integration with database access permissions
- Spotify Developer App with appropriate scopes
- OpenAI API key with GPT-4o access
Milestone 1: Trigger and Data Flattening
Node 1: Notion Trigger
Type: Notion → Database TriggerDatabase: "Playlist Requests"Event: Page Created
This fires immediately when a new form submission creates a database row—no polling delays.
Node 2: Set (Flatten Fields)
Type: Set
Purpose: Extract nested Notion properties to root-level JSON
Mappings:
- mood_current: {{ $json.properties["Current Mood"].rich_text[0].plain_text }}
- mood_target: {{ $json.properties["Target Mood"].rich_text[0].plain_text }}
- activity: {{ $json.properties["Activity"].rich_text[0].plain_text }}
- track_count: {{ $json.properties["Track Count"].number }}
- extra_prompt: {{ $json.properties["Extra
Prompt"].rich_text[0].plain_text }}
- pageId: {{ $json.id }}Why this matters: Notion's API returns deeply nested JSON (properties.field_name.rich_text[0].plain_text). Flattening once at the start makes every downstream expression simple ($json.track_count).
Milestone 2: Taste Profile Generation
(Separate Workflow)
This runs once manually to populate the "Liked Artists" database:
Workflow Chain:
- Manual Trigger → Spotify: Get User's Saved Tracks (limit: 500)
- Split Out Items → Spotify: Get Track Details → Extract Artist Names
- Remove Duplicates → Notion: Create Pages (bulk insert to Liked Artists DB)
Result: Static reference database of 500 artists, manually curated (via my Saved Songs) to include only those I'd want more of.
Milestone 3: Context Enrichment
Node 3: Notion → Read Pages (Liked Artists)
Database: "Liked Artists"Fields: ["Artist Name", "Spotify Artist ID"]
Node 4: Code (Prepare Artist List)
// Trim to first 150 artists to manage token budget
const artists = items.flatMap(item =>
item.json.properties["Artist Name"].title[0]?.plain_text || []
);
const trimmedList = artists.slice(0, 150).join(', ');
return [{
json: {
likedArtists: trimmedList
}
}];