How to repeat action(s) in your Zap for a variable number of values

How to repeat action(s) in your Zap for a variable number of values
Userlevel 7
Badge +3
  • Zapier Staff
  • 21 replies

Last Updated: 05-27-2021

Update

There is now a 3rd option for accomplishing this: Looping by Zapier!

Looping by Zapier was built with some of the issues in mind that could crop up using a Code Step, like not inputting Line Item data with null values properly. It works similarly to the Code Step below behind the scenes, but has some nice bells and whistles, like Line Item support and more.

You can read about it here: https://zapier.com/help/create/other-functions/loop-your-zap-actions

You can add it to your Zaps by searching for Looping by Zapier as the app when adding a new Step.

Intro

Sometimes we want to run an Action or set of Actions more than once in our Zaps. Today I’d like to show you how we can do that if the number of times we want to run that action is variable. 

In the world of programming, we can create loops that run a bit of code once for each of a set of values. We can do this in a Zap as well. If you don’t know how to code, that’s ok! It’s my hope that with this guide, you won’t need to know how to take advantage of this.

Let’s say I have a Trigger that provides a Line Item (array) or comma separated text containing email addresses and first names and I’d like to send a customized email to each one of them:

If we tried to map the email addresses into the “To:” field, that line item would be converted into comma separated text, so a single email would be sent to all the recipients, but they would all see each other’s email address. Worse, the names would be a comma separated text list in the greeting of our email.

What we really want is for the Zap to run the email Step once for each email and name so that each recipient gets a separate email. 

The ideas that we’ll explore would also apply to circumstances where we want to add multiple contacts or leads to a CRM, create multiple documents, send multiple text messages, and more. If an Action supports line items, that support can be used many times for a similar effect, like adding multiple rows to a spreadsheet in Google Sheet’s “Create Spreadsheet Row(s)”. Many actions don’t support line items, and that’s where these techniques come in handy. 

There are two ways to create a loop to repeat one or more actions for different values:

Option 1: Use Google Sheets as an intermediary

This method involves setting up two Zaps and a Google Sheet:

Google Sheet

  • Create a header for each value that you want to repeat in one or more actions

Zap 1

  • Your original Trigger
  • Actions before you want to loop
  • If the values aren’t already in Line Item format, use the Formatter App’s Utility Action: Line Itemizer transform to convert the values into line items (unless they already are)
  • Action: Google Sheets: Create Spreadsheet Row(s) (Mapping each value you want to send to  into the various fields in the sheet - 1 or more rows will be created for the line items. You can mix and match single values and line item values. Line item values each be used once per row. Single values will be repeated for each row - think values like name or email address)

Zap 2 (runs once for each row created by the line items in Zap 1): 

  • Trigger: Google Sheets: New Spreadsheet Row
  • Action: One or more Action(s) that you want to repeat 

In summary, Zap 1 “sets up” the loop, and Zap 2 runs the looped actions with the values posted to each row of the sheet.

Option 2: Use a “Code by Zapier” Action (sometimes referred to as a “Code Step”) to “fork” a Zap

Option 1 works well enough, but it is more difficult to manage over time than a single Zap because we have two Zaps and a spreadsheet to keep track of. Let’s take a look at how using a small amount of reusable code, we can keep things in a single Zap.

Most Steps in Zapier give an output of one JSON object wrapped in an array. When using any value from that object in a subsequent Step, that Step will run once. If we use a Code Step to output multiple JSON objects of the same structure wrapped in an array, the later Steps will run once for each object in the array instead, creating the loop we’re looking for. 

A common example of this is that you're already familiar with is a Zap Trigger. Triggers that ask an app for new data to Trigger on get 0 or more objects in an array returned, and then the Zap runs once for each object. 

​When we’re testing this in the Zap editor, similar to how we can only use one Sample from a Trigger at a time, we’ll only see the first object, and the Zap Steps will only run once when testing. When running live, they will run once per object (set of values).

First, let’s take a look at the values we’re going to input into the Code Step. They could be Line Items like this:

Or comma separated text like this:

When we map the values into the Code Step, they will be converted to comma separated text if they’re not already, so the both examples above will work the same way.

When you’re adding the Code Step to your Zap, you’ll specifically want to use “Run Javascript” if you’re using the code I’ve included here.

When we map those values into the Code Step, we’ll want to types names for each set of values on the left of the Input Data fields (green highlight) and map the actual series of values on the right (purple):

You can paste the following Code into the Code Field as-is. You won’t need to modify it all, regardless of the names and quantities of different values you’re using. It will detect the names of your values automatically from the Input Data field (green highlight). Here’s the code to copy and paste:

/* Add as many Input Data fields as you like above as comma seperated text or mapped line items (will be 
converted to comma seperated text). This code will find each Input Data field and output an array of
objects with the same structure that can be used to "Fork" the Zap.
Example: https://cdn.zappy.app/9de81901f3750ef26bcbbd0737b0937b.png */

// get Input Data field names
let keys = Object.keys(inputData)
let data = [];

// loop through each Input Data field
for (let key of keys) {
// split the contents of each Input Data field on the commas into an array
let li = inputData[key].split(",");
for (let i=0; i<li.length; i++) {
if (typeof data[i] === "undefined") data[i] = {};
data[i][key] = li[i];
// add a record number (in case we want to break the fork/loop with a Filter)
data[i].recordNumber = i+1;
}
}

// preview the whole data structure in the output
console.log(data);
// output the data
output = data;

When you test the Code Step, you’ll see something like this:

As you can see, the the values that we get back (in red) match the names of the Input Data fields and additionally have a “recordNumber” value, but we only get the first set of values to test with, just like when we’re testing a Trigger. By mapping any of those values into a field in the next Action, when the Zap runs live, that Action will be repeated for each set of values. 

In order to give you a better preview of what will actually happen with the live Zap, I’ve set the code to output a “log” that shows all the sets of value (highlighted in green). You can see how the first record is listed there, too (in red).

Here’s what it looks like if we map those values into an Email by Zapier action:


​Once the Zap starts looping, every Action from that point on will loop and run multiple times. If we need to break the loop, we can do that by:

  • Where we want the loop to stop, add a Filter that only continues the Zap if the “Record Number” value exactly matches to “1”. 
  • The Filter will loop, too, but will only pass once for the first set of values in the loop, meaning that we’ll be back to not looping for the Actions after that. 

If we don’t need to run any Actions just once after the ones that we want to loop, we don’t need to worry about breaking the loop.


And that should do it! With these workflows, Zap Actions that can only send a single set of values to an app can send multiple sets of values instead!

If you have any questions about this, I’d be happy to field them here!

Please note

We aren't always able to help with Code questions via Zapier Support because not everyone on the Support Team is familiar with Javascript and Python, and supporting custom code is technically outside of our scope of support. A great place to ask if we can't support you directly is here in our community or on Stack Overflow by tagging your question "Zapier": https://stackoverflow.com/questions/tagged/zapier

 


This post has been closed for comments. Please create a new post if you need help or have a question about this topic.

39 replies

Userlevel 1

Hi Tim, 

Amazing work, thanks so much! This opens up lot’s of possibilities.

I am trying my luck with the first option before cutting my teeth with the second.

It all seems to work perfectly, except that my line items contain some null values.

 

 

This results in uneven pasting onto the Google Sheet.

 

The Date and Status fields are not aligned with the message field…

What would you do in that case?🙏

Thanks!

Andreas

Userlevel 7
Badge +3

Hi Andreas!

You’re welcome, and I’m glad to see you giving this a try :)

I took a look at your screenshots, and I can’t quite tell why things are not lining up. If we’ve created the line items with Line Itemizer, even if you have null values, the resulting rows in the Google sheet should line up, with some blank cells where the null values were. At least the subject and status rows should be lining up because there don’t appear to be any nulls there.

I see one “Subject” value in your line items “RE: Re: Note from...” that I don’t see in the spreadsheet screenshot, so I’m not sure what is happening there.

One suggestion I have here is if your date needs to be repeated for all the rows, for example, is to map in the single Date value in the Create Multiple Row(s) Step instead of the Line Item version. When you combine single values and line item values, the single values will be repeated once for each line.

For the other parts, it may be easier to troubleshoot if we could see the Zap setups. Because you’re not working on any Code Steps here, I’d recommend writing in to Support (https://zapier.com/app/get-help) if you still have questions about this one. If you could post the general answer here afterwards, that would be fantastic!

Best, Tim

Userlevel 1

Hi Tim, it now works perfectly for me! And I am able to format and filter the forked results, so that my spreadsheet looks perfect.

 

I do have one question though, which may not have a satisfactory answer 🤞 

For one of my values, there is a comma within the text. This means that the code steps splits the text in half when it tries to split it at the end of the value.

Is there any way to avoid this from happening or does the code work best when there are no commas within the value itself?

 

Userlevel 7
Badge +3

Hi Andreas, nice work getting this all set up!

You’re right that the code works best when there are no commas in your values, but in your case, there is a way you can work around this by using a Formatter Step for each Input Data Value before the loop starts.

Currently, you have Line Items mapped into your Input Data fields, and what happens is the Code Step converts those into comma delimited text and then sends them into the Code. If we use a Formatter Utilities Action with the “Line Item to text” transform, then we can choose our own delimiter. 

Instead of outputting the text with commas in-between, I like to use three pipes: |||

Then, in the Code Step if you change the Input Data values to your 3-pipe delimited text from the Formatters, you can change the code you highlighted to split on the pipes instead of single commas and that will protect your actual value containing a comma.

That line would change to: let li = inputData[key].split(“|||”);

And that should do the trick!

Userlevel 1

Wow, this will work for sure! I will do it and report back on results, thanks so much...

Userlevel 1

Hi Tim,

 

Is there a limit to the number of records / repeatable flows that can be played through the code?

 

This worked great when I tried it with zaps using a small number of records. Then I tried to set one up with a higher number of records (732 comma-separated values) and the zap seemed to get stuck and not play past the code.

 

Any thoughts?

 

Jonah

Userlevel 7
Badge +7

Hi @jonah I think I can answer your question.

The code by Zapier action has got some limitations. This is probably required so the system doesn’t get overused calculated by the usage, costs and amount of users. These are the following limitations:

  • The environment in which your Code steps run (AWS Lambda) has an I/O limit of 6MB. The total size of the code and the data processed by the Code cannot exceed that. If you're hitting this error, try to limit the amount of data your return from your function. For instance, don't return an entire JSON structure, just the keys you need.
  • Free users are limited to 1 second and 128mb of RAM. Paid users get well over double that at 10 seconds and 256mb of RAM. Your Zap will hit an error if you exceed these limits.

If you like, you can read more about this here. A solution within this limitations is already there however! If I you were one of my clients I would advice you to use AWS lambda yourself to run your code. I use it for some of my clients as well, and the great thing is you can activate it threw Zapier as well.

It does require some setting up of the environment, but it will allow you to run a bigger amount of data. Hope this helps you!

 

~ Bjorn

 

Userlevel 7
Badge +3

Hi @jonah and @ForYourIT,

Thanks for the questions and answers here! @ForYourIT is correct that we need to keep those limitations in mind, though in this case the answer is likely something else. 

I consulted with our Support Escalations team and I learned that the maximum number of times we can loop/fork in a Zap is 250. With 732 values to loop through, we exceed that limit and the Zap won’t work. 

I don’t believe that number can be changed, but I’m looking into learning more about this, and if there is a good workaround/solution, I’ll post another reply.

Userlevel 2

OMG OMG! I was banging my head over the last few days to achieve this functionality. You made my day @TimS  😘🤯😍🔥

Userlevel 1
Badge

This functionality is exactly what I needed. Typed in csv data fields and it created multiple records in Airtable.

I have input from Jotform that looks like this.

Configurable list

[{"Product":"8 \"","Quantity":"2","Flavour":"B Choc"},{"Product":"10\"","Quantity":"1","Flavour":"Lemon"},{"Product":"10\"","Quantity":"1","Flavour":"Vanilla"},{"Product":"12\"","Quantity":"2","Flavour":"Carrot"}]

Is there a way that your code could be adjusted to use this format or would it have to be extracted somehow and put into one of your 2 formats. Maybe some kind of Run Python in Code by Zapier. 

Python looks a bit complex to learn. 

Hi @TimS ,

Your code works wonderful to run a loop. I want to create a loop inside loop. as you can see in SS. I want to run first loop against the total line items and the second loop against the quantity.

Can you please help. 

 

Thanks and Stay Safe,

 

Shahzad

Userlevel 3
Badge +1

Here’s a little hack that serves me well in these situations:

When the various array items that generate the fork or loop have associated values - in one of my cases each is associated with a line item which has an image and a list of options.

Before hitting the fork I create a set of arrays (I could do this with an array of objects, too, but arrays are easier).

Before hitting the fork in the road I zero out a value in Storage.

As each loop is executed, I first retrieve the value and use it as an index to the earlier arrays.

Before ending the loop, I increment the value.

This is a tedious way of saying I use a storage variable as the index of a loop.

Userlevel 1
Badge

Your code worked perfectly. I added a code step to split my arrays. I use this to add multiple occurrences of the same record in Airtable.

 

 

My understanding of your fork step is that all of the steps after the fork will run as many times as the fork triggers. I would like to add a second independant fork step that would run after this fork completes to add another set of different records based on another set of data. Is this possible?

Userlevel 3
Badge +1

Chrisboat - While I’ve not tried it myself, it seems to me that you could add a filter step at the bottom of the first “fork” which would only continue if the index+1 = the size of the first set of values. The following step could retrieve the next array and process just as the last fork had done.

Again, I haven’t tried it, but it seems like a logical extension of the indexed array approach.

Userlevel 1
Badge

RisoSystems -  Thanks.  That seems like an excellent and workable solution. I can use the output record number from the trigger step to know what record I am on.

The problem is my input to the trigger step is a variable number of lines so I don’t have a number to compare it to. It would change every time the ZAP was run. The Only Continue IF trigger only allows an entered specific value in the if statement.

Userlevel 3
Badge +1

Chrisboat - I’m not sure I see that as a problem. When you perform the step that retrieves the variable number of items (presumably in an array), just add a variable to the output that reflects the size of the array:

var someArray = [‘John’, ‘Mary’, ‘Fred’, ‘pizza’];

var arraySize = someArray.length;

var arrayLimit = arraySize - 1;

output = [{someArray: someArray, arrayLimit: arrayLimit}];

I’m showing all my steps here, obviously this could be written more economically - but in the filter statement compare the arrayLimit variable to the index variable and you’re done.

Userlevel 1
Badge

I have been using your code very successfully but have found a problem. The input data is as shown below. The problem is that the last entry in Quantity and Flavour gets associated with the first none entry and not with the Product Foil as it should. Output is shown as well.

Your comments would be appreciated.

 

Userlevel 3
Badge +1

Chrisboat - 

Could you please give me a mockup of the desired output based on the data provided?

Is this what you’re expecting (less headers):
 

Need Update Quantity Flavour
none    
none    
Foil   vanilla
none 6  
Userlevel 1
Badge

Thank you very much for the quick reply.

What is happening is that if the value of the CSV line has nothing between the commas the code step takes the next value it can find.

So in this case the quantity and flavour were both moved to record 1 when they were actually associated with record 3. 

Sorry the 3rd image I provided above only made it more confusing.

What I need is:

 

 

Product Quantity Flavour
none    
none    
Foil 6 vanilla
none    

 

Thanks again

Chris

Userlevel 1
Badge

thought this may be even clearer of what I want.

logs

0

INFO [ { Product: '', recordNumber: 1, Quantity: '', Flavour: '' }, { Product: '', recordNumber: 2, Quantity: '', Flavour: '' }, { Product: 'Foil', recordNumber: 3, Quantity: '6', Flavour: 'Vanilla' }, { Product: '', recordNumber: 4, Quantity: '', Flavour: '' } ]

Userlevel 3
Badge +1

Chrisboat,

I think the problem is in your manipulation of the data in your code step. I suggest you copy your code to a safe place, and replace it with the code below.

This code creates an array of objects which should cause another fork. Each fork would receive one object with 3 properties, product, quantity, flavour. From these objects you should be able to produce the lines you want in your spreadsheet.

Obviously I can’t run this myself, but if you paste it in (with the substitutions at the top) it should work fine.

// these were for my syntax testing only. Use the inputData variables in your code:
// 
// Let Product = inputData.Product;
// Let Quantity = inputData.Quantity;
// Let Flavour = inputData.Flavour;
//
let Product = ['none', 'none', 'Foil', 'none'];
let Quantity = [null, null, 6, null];
let Flavour = [null, null, 'vanilla', null];
//
// You can see below where I'm going with this. It's not the only way to do it, but it should work.
//
var LineItem = {};
var LineOuts = [];

let LineCount = Product.length;

for (var i = 0; i < LineCount; i++){  
    LineItem.product = Product[i];
  LineItem.quantity = Quantity[i];
  LineItem.flavour = Flavour[i];
  LineOuts[i] = LineItem;
  LineItem = {};
    }

output = [{LineOuts : LineOuts}];

 

 

Userlevel 3
Badge +1

Chrisboat,

One other thing. If the subsequent step doesn’t handle the objects well, then JSON.stringify the object before placing it in the array:

for (var i = 0; i < LineCount; i++){  
    LineItem.product = Product[i];
  LineItem.quantity = Quantity[i];
  LineItem.flavour = Flavour[i];
  LineOuts[i] = JSON.stringify(LineItem);
  LineItem = {};
    }

The variable would have to be parsed before use in the spreadsheet:

let LineItemString = inputData.LineItem;
let LineItem = JSON.parse(LineItemString);

 

Userlevel 1
Badge

HI,

This message is for Tim. I think there is confusion that my most recent post was dealing with a fork within a fork. Sorry RicoSystems for causing the confusion. I need to get this issued with Tim’s fork code corrected first then I will get back to fork within a fork.

 

Tim’s fork code issue.

I have been using your code very successfully but have found a problem. The input data is as shown below. The problem is that the first non blank entry in Quantity and Flavour gets associated with the first record Product ‘none’ entry and not with the Product ‘Foil’ in the third record as it should. Output is shown as well.

What is happening is that if the value of the CSV line has nothing between the commas the code step takes the next value it can find.

So in this case the quantity and flavour were both moved to record 1 when they were actually associated with record 3. 

What I need is:

 

 

Product Quantity Flavour
none    
none    
Foil 6 vanilla
none  

 

 

what the output should look like:

logs

0

INFO [ { Product: '', recordNumber: 1, Quantity: '', Flavour: '' }, { Product: '', recordNumber: 2, Quantity: '', Flavour: '' }, { Product: 'Foil', recordNumber: 3, Quantity: '6', Flavour: 'Vanilla' }, { Product: '', recordNumber: 4, Quantity: '', Flavour: '' } ]

Input data, output data of what happens now:

Userlevel 3
Badge +1

Chrisboat,

Actually the same code approach I posted is applicable to both situations. But I’ll bow out until/unless I see my name at the top of a post.

 

Userlevel 1
Badge

RisoSystems,

I must apologize. My last post may not have been said correctly.

When I realized the recent responses to this current issue were from you (thought it was Tim I should have looked) and then saw that you were talking about fork within a fork I just assumed that your reply was about my earlier topic about double forks. I am very appreciative of the help you provided. Most of this javascript code is over my head.

So please don’t think I don’t want the help. I just thought I was confusing everyone and did not want to burden you. It seemed to me that my current problem with no values between the commas was with Tim’s code so that is why I specifically asked him. If you want to help I will truly appreciate it. 

This is what my input data actually looks like. I run this code step then I run yours.

When I tried your code with my data this is what I got. 

Thank you

Chris