Can my workflow be improved?

Hi all, I’m just getting started with n8n. I’m trying to learn how to pass data around as I need. To illustrate my problem, I have a practical example.

In my example, I’m adding all tracks from a Spotify playlist to a different playlist. Rather than using a static playlist URI for the target playlist though, I’m trying to find it based on its name. This example is a bit contrived because the name is simply set in a function node, but for my real-world workflow, the name will be generated programmatically.

I’m not sure I have the best approach for patching the target playlist URI into the Playlist ID parameter of the ‘Save to Target Playlist’ node. I would have expected I could reference the output of the function node in my ‘Save to Target Playlist’ parameter, but it can’t seem to find it. Instead, I’m merging a new property into each track record. This feels a bit hacky and I’m wondering if there’s something I’m missing.

Thanks so much for your work on n8n! It’s incredibly promising and I hope to be able to contribute once I learn more about it, brush up on my JavaScript, and finally learn TypeScript.

Here’s my workflow:

{
  "name": "Spotify: Archive Playlist Example",
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        650,
        450
      ]
    },
    {
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "custom",
              "cronExpression": "0 0 6 * * 1"
            }
          ]
        }
      },
      "name": "Cron",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        850,
        450
      ],
      "notesInFlow": true,
      "notes": "6am on Mondays"
    },
    {
      "parameters": {
        "mode": "multiplex"
      },
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        1650,
        630
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [
            {
              "name": "targetPlaylist",
              "value": "={{$json[\"uri\"]}}"
            }
          ]
        },
        "options": {}
      },
      "name": "Set Target Playlist",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        1450,
        450
      ]
    },
    {
      "parameters": {
        "functionCode": "//let playlistName = \"Discover Weekly Comprehensive\";\nlet playlistName = \"test playlist 1\";\n\nreturn items.filter((item) => {\n  return item.json.name == playlistName;\n});\n"
      },
      "name": "Find Target Playlist",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1250,
        450
      ]
    },
    {
      "parameters": {
        "resource": "playlist",
        "id": "={{$json[\"targetPlaylist\"]}}",
        "trackID": "={{$json[\"track\"][\"uri\"]}}"
      },
      "name": "Save to Target Playlist",
      "type": "n8n-nodes-base.spotify",
      "typeVersion": 1,
      "position": [
        1850,
        630
      ],
      "credentials": {
        "spotifyOAuth2Api": "Trey"
      }
    },
    {
      "parameters": {
        "resource": "playlist",
        "operation": "getUserPlaylists",
        "returnAll": true
      },
      "name": "Get All Playlists",
      "type": "n8n-nodes-base.spotify",
      "typeVersion": 1,
      "position": [
        1050,
        450
      ],
      "credentials": {
        "spotifyOAuth2Api": "Trey"
      }
    },
    {
      "parameters": {
        "resource": "playlist",
        "operation": "getTracks",
        "id": "spotify:playlist:37i9dQZEVXcU9MVTlTbyiM",
        "returnAll": true
      },
      "name": "Get Tracks From Source Playlist",
      "type": "n8n-nodes-base.spotify",
      "typeVersion": 1,
      "position": [
        1050,
        650
      ],
      "alwaysOutputData": false,
      "credentials": {
        "spotifyOAuth2Api": "Trey"
      }
    }
  ],
  "connections": {
    "Cron": {
      "main": [
        [
          {
            "node": "Get All Playlists",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Tracks From Source Playlist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Save to Target Playlist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Target Playlist": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Target Playlist": {
      "main": [
        [
          {
            "node": "Set Target Playlist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get All Playlists": {
      "main": [
        [
          {
            "node": "Find Target Playlist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Tracks From Source Playlist": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {},
  "id": "3"
}

Hey @trey! :wave:

Welcome to the community! :slightly_smiling_face:

I tried out the workflow you shared, and it works well for me!

I am not sure why you were not able to find the get output from the Function node. However, I have tried recreating your workflow, and have replaced the Function node if the IF node. Since we want to filter out a particular playlist using the IF node made more sense. I have also removed the Merge node and as I am now getting the Playlist URI from the IF node and the tracks from the Get Track from Source node.

{
  "name": "",
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "custom",
              "cronExpression": "0 0 6 * * 1"
            }
          ]
        }
      },
      "name": "Cron",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        790,
        256
      ],
      "notesInFlow": true,
      "notes": "6am on Mondays"
    },
    {
      "parameters": {
        "resource": "playlist",
        "id": "={{$node[\"IF\"].json[\"uri\"]}}",
        "trackID": "={{$json[\"track\"][\"uri\"]}}"
      },
      "name": "Save to Target Playlist",
      "type": "n8n-nodes-base.spotify",
      "typeVersion": 1,
      "position": [
        1550,
        150
      ],
      "credentials": {
        "spotifyOAuth2Api": "spotify"
      },
      "disabled": true
    },
    {
      "parameters": {
        "resource": "playlist",
        "operation": "getUserPlaylists",
        "returnAll": true
      },
      "name": "Get All Playlists",
      "type": "n8n-nodes-base.spotify",
      "typeVersion": 1,
      "position": [
        990,
        256
      ],
      "credentials": {
        "spotifyOAuth2Api": "spotify"
      }
    },
    {
      "parameters": {
        "resource": "playlist",
        "operation": "getTracks",
        "id": "spotify:playlist:37i9dQZEVXcU9MVTlTbyiM",
        "returnAll": true
      },
      "name": "Get Tracks From Source Playlist",
      "type": "n8n-nodes-base.spotify",
      "typeVersion": 1,
      "position": [
        1350,
        150
      ],
      "alwaysOutputData": false,
      "credentials": {
        "spotifyOAuth2Api": "spotify"
      }
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$node[\"Get All Playlists\"].json[\"name\"]}}",
              "value2": "test playlist 1"
            }
          ]
        }
      },
      "name": "IF",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1190,
        256
      ]
    }
  ],
  "connections": {
    "Cron": {
      "main": [
        [
          {
            "node": "Get All Playlists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get All Playlists": {
      "main": [
        [
          {
            "node": "IF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Tracks From Source Playlist": {
      "main": [
        [
          {
            "node": "Save to Target Playlist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF": {
      "main": [
        [
          {
            "node": "Get Tracks From Source Playlist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {}
}

Let me know if this what you were looking for.
Thanks!

1 Like

Thanks @harshil1712! This is very helpful and seems much cleaner since it doesn’t modify the output of the “Get Tracks from Source” node.

What I was missing is that you can’t just read from any arbitrary node in the workflow, you can only read from nodes that are plugged in to the input of the node you’re configuring. This may seem obvious in retrospect, but I was also under the wrong impression that only the closest node that was plugged in could be referenced. I understand now that you can access output from nodes earlier in the chain as long as they’re wired up.

This is also a great illustration of how to use the “If” node. Much appreciated!

3 Likes

I am glad this helped you! Feel free to ask any questions you might have.

Once again, welcome to the community. :slightly_smiling_face:

1 Like