Return Values in Apps Script Tasks

We're happy to announce an enhancement to Apps Script Tasks that will allow you to use the return value of a Google Apps Script function within your AppSheet automation and pass it to a later step in the same automation process.

Example uses:

  • Write the return value of a UrlFetchApp.fetch call back to one of your Google Sheets so that APIs responses can be incorporated in your app
  • Update / Add a new table row or value based on the output of an Apps Script function so that complicated data manipulations can be performed
  • Write a multi-step conditional automation flow based on the result on an Apps Script function so that more expressive data flows are possible in AppSheet

This feature will be rolling out gradually starting today. When available, you will see a new option appear at the bottom of the Apps Script task configuration for an optional return value:

nico_0-1658426891307.png

What should I try out?

You can try returning values of different types from Apps Script. For example, a simple boolean return value:

function isPastDue(date) {
    return new Date() > new Date(date);
}

Or more complex objects:

function returnASampleObject(userId) {
    return {
        "userId": userId,
        "username": "jsmith",
        "email": "john.smith@example.com",
        "creationDate": new Date(2021, 11, 25),
        "isAdmin": false,
    };
}

How can I use the return value from an Apps Script function?

See how to configure return values for Apps Script tasks for more details. A quick summary is below:

  1. Configure an Apps Script task as normal with a function that has a return value
  2. Enable the Return Value toggle at the bottom of the panel
  3. Set the AppSheet Return type that is returned from your Apps Script function
  • For example, if your function returns an array of strings, the corresponding AppSheet type will be a list of text elements

nico_1-1658426891291.png

  1. Click the return type of your Apps Script function.
  2. Select the AppSheet type it should convert to in the drop-down.

Use the Return Value in your automation

  1. To reference the return value in an expression in a subsequent unlinked step in the process, you can refer to the output using the following syntax:
    1. `[StepName].[Output]` for strings, booleans, numbers, arrays, and dates
    2. `[StepName].[Key]` for objects where `Key` is the key of your returned object

nico_2-1658426891308.png

Limitations

  • Return values consisting of arrays of mixed types are unsupported (e.g. cannot mix strings and numbers like [1, โ€œaโ€])
  • Return values consisting of deeply nested objects are unsupported (e.g. objects like {a: โ€œexampleโ€, b: [1, 2, 3]} are fine but objects like {a: {b: 1}} are not supported)

How do I give feedback?

To send us feedback on this feature, you can reply or comment on this post.

16 95 11.8K
95 REPLIES 95

Awesome! So now we can effectively handle responses from API calls, by going through a custom built GAS before passing a value back to Appsheet, without having to force the GAS itself to interact directly with the data source.

Use-cases that I can think of immediately:

  1. 64-bit encoding of images in order to pass them to other APIs
  2. URL shortening for SMS messages
  3. Weather services
  4. Geo-encoding lat-longs to addresses (or vice versa?)

I'm sure there are others...

 

I'm glad to hear that! That's very good news!

I was hoping to do some API calls to a adress form auto fill with GAS since i could get return i made a Spreadsheet formula, but now i will give it a try!! 

Previously, unless you had enterprise/API access, scripts or integrations were limited to silently posting/updating data to the underlying spreadsheet. This effectively removes that limitation whenever the request is initiated from AppSheet.

Hi @nico 

This is an exciting feature.

The column name of the return value is [Output], but if there is a reserved word, I think it would be good to have an underscore at the beginning, like other column names.

I think following this.

[Your StepName].[_Output]

Thanks for the input. Unlike table refs like `[_THISROW]` in the  top-level namespace there is no namespace collision with `[Output]` because it's in the step name table ref so it's guaranteed to be unique.

Hi @nico 

isPastDue may need to be changed.
Passing "2000/1/1" did not yield a return value.

2022-07-22_07h02_23.png

The following changes gave the expected return value.

function isPastDue(date) {
  // return new Date() > new Date(date);

  if(new Date() > new Date(date)){
    return new Date();
  }
}

Takuya, the example in the post should be correct but the return value in AppSheet should be configured to be "Boolean" instead of a "Date" because it's just returning `true` if the current date is past the provided date "the due date" and `false` otherwise.

nico_0-1658443172972.png

Of course you can modify it to return a date instead if you'd like for example:

 

function currentDate() {
    return new Date();
}

 

These are both "toy" examples because you can do both of these function in AppSheet's expression system but hopefully it gives an idea of how you'd use the return value. I expect real-world use-cases to be more complex than this.

EDIT: One more thing, you should notice a conversion error in the automation monitor that says the conversion failed to help for debugging purposes.

Thanks @nico .
And I'm sorry. I was mistaken.๐Ÿ™

I'll try something that AppSheet's features alone can't do.๐Ÿ˜„

This is really exciting!

One seeming clarification regarding the bold text in the following part of the announcement is that the subsequent step's task needs to be an unlinkable "custom action"--not just a linkable task with linking turned off.


Use the Return Value in your automation
  1. To reference the return value in an expression in a subsequent unlinked step in the process, you can refer to the output using the following syntax:

That's right, it needs to be an unlinked action or task. The reason is because the return value is scoped to the process so you can only access the return value in later steps in the process. The Apps Script Task itself with the return value can either be linked or not.

Understood. What initially confused me is probably the phrasing of the requirement from your original post for "subsequent unlinked step". I believe "step" components are not themselves custom/reusable/unlinked/linked, but rather that status applies to the action or task that a step includes. 

I created an automation by compiling independently created reusable components with linking turned on, including a step referencing a data action in my app, then realized the requirement from your original post for "subsequent unlinked step", then turned linking off for various components of the automation (the step/action referencing the returned value, the automation's other step/task, its process, and its event components) each one by one, and it still didn't work throughout. I may have made some mistake along the way that I don't realize, but I ended up starting over with a new step and new data change action defined as a custom action from within the step, and that was finally what worked. 

If anyone wants a quick-and-dirty script to try out this new functionality for a nonetheless real use case, here's one I put together. If you have a country code from a phone number, you can use a script (which in turn relies on an API) to get (and now return!) a corresponding flag emoji. I'm sure the script could be optimized in all sorts of ways, including trapping for errors and variations in what's considered a country code, but I confirmed it works at least in straightforward cases.

 

function getFlagEmojiForCountryCode(countryCode) {
   
  // Call REST Countries API (https://restcountries.com/)
  const response = UrlFetchApp.fetch("https://restcountries.com/v2/callingcode/" + countryCode + "?fields=alpha2Code");

  // Parse response
  const responseJSON = response.getContentText();
  const responseData = JSON.parse(responseJSON);
  let flagEmoji;
  let countryAlphaCode;
  if (responseData.length > 1) {
    flagEmoji = "N/A (multiple)";
  } else {
    const objCountryAlphaCode = JSON.parse(responseJSON)[0];
    countryAlphaCode = objCountryAlphaCode.alpha2Code

  // From https://dev.to/jorik/country-code-to-flag-emoji-a21
  const codePoints = countryAlphaCode
    .toUpperCase()
    .split('')
    .map(char =>  127397 + char.charCodeAt());
  flagEmoji = String.fromCodePoint(...codePoints);
  };

  return flagEmoji;
}

 

 

Hi @nico 
I'm currently trying to achieve geocode functionality using return values.

It is a simple Apps Script that will return Lat and Long if the address is passed as text.

function geocode(address) {
 var response = Maps.newGeocoder().setLanguage('ja').geocode(address);
 for (var i = 0; i < response.results.length; i++) {
   var result = response.results[i];
   Logger.log('%s: %s, %s', result.formatted_address, result.geometry.location.lat,
       result.geometry.location.lng);
  //  return result.geometry.location.lat;
   return {
        "lat": result.geometry.location.lat,
        "lng": result.geometry.location.lng
    };
 }
}

At this time, if I set the data type of the Key I receive in AppSheet to TEXT, the return value is Decimal, so I cannot get it and it will be NULL.

2022-07-26_07h58_24.png

If I set it to Decimal, it works as expected.

There is nothing wrong with the functionality, but it is difficult to find the wrong data type in question because the error is not displayed in the Automation monitor nor in the Audit log.
I would like to see an error somewhere if the data type received is different.

2022-07-26_07h51_27.png

โ€ƒ

2022-07-26_07h54_34.png

โ€ƒThanks.




Takuya, thanks for trying out the new feature!

A few things I wanted to mention. If you want a LatLong translation, one easy wait to do it is to return it as a string like `"-28.05722, -51.75104"` then you can just configure it as returning a string with "LatLong" as the AppSheet type and you get it directly as a pin on the map etc. 

nico_0-1658880453084.png

And

function geocode(address) {
   ...
   return result.geometry.location.lat + ", " + result.geometry.location.lng;
}

But there are good reasons to return it like an object if you want (e.g. to do calculations). In your first screenshot notice there is an error even the execution is successful: `Failed Apps Script Type Translation: Type 'Number' failed to translate to 'Text'`

I made the decision that the automation would still complete even if the type translation fails because some users may return different variable types on errors and I wanted to support that as well as not break existing workflows that might return different type.

nico_1-1658879867507.png

There are two ways to fix that, either change Apps Script side to be strings:

1) Explicitly convert it in Apps Script (notice the `.toString()` at the end:

 

function geocode(address) {
   ...
   return {
      "lat": result.geometry.location.lat.toString(),
      "lng": result.geometry.location.lng.toString()
   };
}

 

2) Just configure it to accept a decimal on AppSheet (as you noted)

You've observed we're stricter on translating types from Apps Script. One "trick" I'll mention is that all types can be converted from a string to any type but not the other way around so a string like `"123.42"` to decimal is fine but a number like `123.42` to text will not work.

Hopefully that all makes sense.

Hi @nico 
Thanks for the detailed follow-up.

The handling of the Geo code is Apps Script's forte and is also information that AppSheet can make great use of.
Your suggestions will expand my handling of Geo codes. Thank you very much.๐Ÿค—

One thing I would like to discuss with you is that when it contains a wrong type error as we mentioned, I would like it to be clear in the Automation error.
Specifically, when similar error messages are included, I think it would be a feature that would be easier to use if we could display something in the log to draw attention to it, such as Warning instead of Complete.

Hi @nico 
Sorry, same content.
I think it would be a more user-friendly feature for citizen developers if Status could be expressed in more detail regarding Automation Monitor.

For example, this is the log when I forgot to set the Return value in my Apps Script.

2022-07-29_05h09_09.png

It is true that the Error message shows the contents of the message, but the Status is Complete, so it is not highly visible.
Especially for non-English speaking users, it may take time to identify the problem if the Status information is green.

Since most citizen developers do not have good debugging skills, I think this feature will be more widely accepted by a wider range of users if Monitor is made more user-friendly.๐Ÿค—

Just want to validate your suggestion, I think showing a `Warning` here instead of a success or `Error` is a good idea since it'll be easier to find but we currently don't have a Warning state and adding one requires a wider Automation change. I've posted a bug internally but due to low severity it's unlikely it'll be prioritized in the near future.

 

Thanks @nico 
That's right.
I thought the request seemed to have a large scope of impact, so I will wait patiently, hoping that it will be modified.

Until then, I'll keep an eye on the Error message!๐Ÿ˜ƒ

In my AppSheet application I am using this:

function geocode(address) {
   ...
   return result.geometry.location.lat + ", " + result.geometry.location.lng;
}

But there is a problem with the return value.
I am trying to return the value into the Colum 'GPS', but I get an error.

Output 'GPS' is invalid: Error in expression '[geocode].[Output]' : Unable to find table 'geocode Output'

What am I doing wrong?

Is "geocode" the name of your call a script step?

Looks like this:
RensD_0-1711960949382.png

 

Are you confusing return values from the call a script task with a process returning a value?

How/where are you attempting to push the script return value to a column? Typically there'd be a data change action step in that bot for that.

Yes.....it is a data change action.

RensD_0-1712157303755.png

 

Hey @RensD I just saw this little bit of exchange and I can see what the problem is for you.  It's just a misunderstanding between what you are naming your appscript function (geocode) and the name of the Step that is calling it.

When you return values from Appscript and want to write them into a column, the system is expecting you to give the name of the Step, not the name of your script.

In this case, [geocode].[Output] is the name of your function and not the name of your step (task).

Here is a bot that calls a script

scott192_0-1712828601032.png

On the following task (StoreValues) the data that is returned from the script is stored in columns using the name of the Step.
[CreateNote].[Output]  NOT  [CreateNewNote].[Output]

Apologies if you have already solved this and I missed it!

 

Two ideas:

1. is the Task configured as a "string" return type in the Apps Script Task? Should look like this:

nico_0-1712073272192.png

2. Like Marc is alluding to, you can only use this return value in the same process as you're calling the script in (but you can write it back to a table if you'd like to persist the value).

RensD_1-1712158003463.png

This is what I am using.
It gets me the following error:
Output 'GPS' is invalid: Error in expression '[geocode].[Output]' : Unable to find table 'geocode Output'

Any insights @nico ?
Every advice would be great.

Your Apps Script Task configuration looks fine, can you also send a screenshot of the bot you're trying to run as well as the task/action that's reporting the error? It appears from the error message that it's looking for the `geocode Output` table when it should just be looking for the `geocode` table with the `Output` column (which contains your return value). The Apps Script Task also needs to be run in the same process in which you use the return value.

Has this not yet been rolled out to everyone? I do not see any Return Value toggle or section in the Task configuration panel.

It will finish rolling out to everyone tomorrow at roughly 10am PST (assuming monitoring looks okay).

Also note you'll have to select an Apps Script project before the option to select a use a return value will appear (at the bottom of the task configuration panel).

That's great news. I have been banging my head against the wall trying to figure out why I don't see it. And I do have an Apps Script project selected, that returns a value.

Did this get rolled out for everyone today? I don't see it.

I just made the last rollout bump just now. If you refresh the editor page and select an Apps Script task you should see it after you select a script (but let me know if you don't).

๐Ÿ˜€Hooray!!!

dbaum_0-1659052449115.png

 

I see it! Thanks very much.

As I prepare to upgrade a production app to include new automations I developed in a non-production app, I have some questions regarding  potential constraints and hiccups related to Call a script tasks. I'm sure I can test these details if necessary, but it would also be helpful to get the answers documented here for everyone's awareness. Thanks, @nico or community friends who may have already worked through these details, for any guidance.

1. Shared projects

Is it possible for a Call a script task to reference an Apps Script project that is shared to the app owner? I don't see a way to select a project that is only shared to the owner. Maybe by adding a shortcut to My Drive?

I do see that it's possible to transfer ownership of a project. If an app task already references a project whose ownership changes but is still shared back to the app owner, will the task continue to function?

Screenshot: Seemingly no way to select a project shared to the app owner

dbaum_0-1659208357842.png

Screenshot: Option to transfer ownership of project

dbaum_1-1659208541025.png

2. Project versions

Does a Call a script task always call a project's head deployment? Does it depend on whether the project has any versioned deployments? Is it possible to specify in the task a specific deployment version? Flexibility here would facilitate maintaining production and non-production versions of a script within the same project. Without flexibility, it's presumably necessary to maintain separate projects and point production and non-production apps to distinct projects.

3. Task properties persistence

If I change the project referenced by a task (e.g., to reference a production project instead of a development project from an upgraded app), do the task's other project-related settings (e.g., Function Name, Function Parameters, Return Value) persist or do they reset to blank and need to be re-entered even though they're the exact same values compatible with both projects?

Screenshot: Project-related task properties

dbaum_2-1659209427190.png

 

Update: I did some basic testing regarding some of my points.

1. Shared projects: If a GAS project is shared to you, add a shortcut to your Google Drive. Then you can select it from an AppSheet task and it functions fine.

If you transfer ownership of a GAS project that is already used in an existing AppSheet automation task, the task seemingly continues to function seamlessly with no changes necessary within the app. Upon transfer, it looks like a shortcut is automatically added to the previous owner's My Drive; I did not test whether there's a dependency on retaining that shortcut.

Note that it's not possible to transfer project ownership into/out of a Google Workspace organization (although maybe there's some workspace admin setting that could allow this). Nonetheless, it is still possible to share a project into/out of a Google Workspace.

dbaum_0-1659276491680.png

 

3. Task properties persistence: If you change an existing task's Apps Script Project, all the other project-related task properties sufficiently persist:

  • Function Name: This property persists as long as the function is available in the new project. Even if it isn't available, the other properties do persist.
  • Function Parameters: These properties persist, and are helpfully flagged if they're not present in the new project's selected function.
  • Return Value: These properties persist; just like when they're originally defined, I think AppSheet doesn't validate they're actually returned by the function (e.g., the key names if the function returns an object).

Thanks for the questions and answering your own questions ๐Ÿ™‚

1) I'm not an expert on Drive itself but so long as the app owner  (which remember is the entity executing the Apps Script Task) has authority over the script it should be able to run. There are lots of different ways to organize: Shared Drive, shared authorship over the project, and (as you discovered) making the app as a shortcut might be required to see that in the file selector.

2) Yes, for configuration simplicity scripts are always executed at HEAD (as opposed to Apps Script web executable which allows you to version a script). A separate development project seems like a good way to mostly support development flow.

3) Yes, we try to maintain as many properties as we can so we don't clear configured properties especially if you have a function with the same name and number of parameters. We just keep the Return Value as is if a project is swapped.

Why Automatic Return Value is Hard:

This is probably more detail than you care about but I wanted to explain why we don't do any automatic computation of the return value you'll need to do this manually. Note that in general, it is impossible to determine the return type can only be determined at runtime (in the general case), consider a function that is just:

function simpleUnknownType(x) {
    if (x > 0) {
        return "some string"; // a string
     }
     return 42; // a number
}

 In this case, the return type is either a string or a number depending on the input `x`. So by setting a return type you are saying that your function has a consistent return type. We could execute the function and then set that automatically as an enhancement but hopefully it's not too hard to manually select the return type.

It's certainly not a problem to have to manually define the return values--especially the value type when a single value is returned. My comment was mostly with regard to the "key" names if the function returns an object since those are explicitly defined with the object. Nonetheless, as your example represents, even that's not fully reliable since different branches of the function might return objects with different keys.

I've since encountered some additional questions:

  1. Project name changes don't sync to the list of projects that appear on my account's Integrations page. As explained in the feature's instructions, projects are automatically added there when you add a project to a task in any of the account's apps. However, the project names on that page never update when you edit the project's name in Apps Script even though within the task the updated project name does appear. After adding some projects, then appending "OLD" to their names in Apps Script since I created and added new parallel projects from a different Google Workspace, I now have multiple projects with duplicate names in the AppSheet account page. I don't know which is the "OLD" one in each pair, so I don't know which to remove from AppSheet using the trash icon.
  2. Multiple project authorization requests. As explained in the feature's instructions, when the app owner adds a project to a task, they're asked to authorize access. Even if they do so, when an app co-author is later editing the app, they're also asked to authorize the project. If the co-author doesn't authorize, the app and the script still seem to work for users. The purpose--and potential conflict or override--of requiring separate authorization by app co-authors is unclear. Is it intentional? Required?