<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dataful.Tech</title>
    <description>The latest articles on DEV Community by Dataful.Tech (@dataful).</description>
    <link>https://dev.to/dataful</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1137854%2F8b21bd05-f465-4bab-a541-766cac66843e.png</url>
      <title>DEV Community: Dataful.Tech</title>
      <link>https://dev.to/dataful</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dataful"/>
    <language>en</language>
    <item>
      <title>Access Google Cloud Secret Manager via Google Apps Script</title>
      <dc:creator>Dataful.Tech</dc:creator>
      <pubDate>Wed, 01 May 2024 21:04:32 +0000</pubDate>
      <link>https://dev.to/dataful/access-google-cloud-secret-manager-via-google-apps-script-4mgk</link>
      <guid>https://dev.to/dataful/access-google-cloud-secret-manager-via-google-apps-script-4mgk</guid>
      <description>&lt;p&gt;There are &lt;a href="https://dataful.tech/google-apps-script/security/how-to-store-secrets/"&gt;many ways&lt;/a&gt; to store secrets, such as tokens, passwords, and API keys, in Google Apps Script, but they are not created equal. Some are safer than others. One way to deal with this challenge is to store the secrets externally and access them on demand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GCSecretManager&lt;/strong&gt; (&lt;a href="https://github.com/dataful-tech/GCSecretManager"&gt;GitHub&lt;/a&gt;) is a Google Apps Script library that allows you to store secrets in &lt;a href="https://cloud.google.com/security/products/secret-manager"&gt;Google Cloud Secret Manager&lt;/a&gt;. The library also works as a storage for &lt;a href="https://dataful.tech/google-apps-script/secret-service/"&gt;SecretService&lt;/a&gt; library. Let's look at three ways to use it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you find this library useful, please give the &lt;a href="https://github.com/dataful-tech/GCSecretManager"&gt;repository&lt;/a&gt; a star and share the link with others.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Library Directly
&lt;/h2&gt;

&lt;p&gt;You can use the library directly without initializing an instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get the latest version of the secret&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretLatest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GCSecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Get the latest version of the secret&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretV2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GCSecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Instead of the config, specify project via chaining:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;anotherSecretV3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GCSecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;another-secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Set secret. A new one will be created if it doesn't exist&lt;/span&gt;
&lt;span class="c1"&gt;// or, if it does, a new version for the existing one.&lt;/span&gt;
&lt;span class="nx"&gt;GCSecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret-value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Directly call the Secret Manager API&lt;/span&gt;

&lt;span class="c1"&gt;// Get the latest version of the secret&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oneMoreSecretLatest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GCSecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;one-more-secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create a new secret&lt;/span&gt;
&lt;span class="nx"&gt;GCSecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Create a new version of a secret&lt;/span&gt;
&lt;span class="nx"&gt;GCSecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSecretVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-secret-value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create an Instance
&lt;/h2&gt;

&lt;p&gt;You can create an instance to provide the configuration only once and use it multiple times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Initialize&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MANAGER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GCSecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// You can also use chaining to initialize the manager&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MANAGER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GCSecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Retrieve the latest secret version&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;MANAGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Set a secret&lt;/span&gt;
&lt;span class="nx"&gt;MANAGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret-value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The direct methods will work the same way as in the examples above&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oneMoreSecretLatest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;MANAGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;one-more-secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  As a SecretService Storage
&lt;/h2&gt;

&lt;p&gt;GCSecretManager can also work as a storage layer for the SecretService library, combining their benefits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GCSecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SECRETS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SecretService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SECRETS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Contributions are welcome. Feel free to submit pull requests or issues on &lt;a href="https://github.com/dataful-tech/secret-service"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>googleappsscript</category>
      <category>gas</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>SecretService: Library for Storing Secrets in Google Apps Script</title>
      <dc:creator>Dataful.Tech</dc:creator>
      <pubDate>Tue, 12 Mar 2024 12:11:16 +0000</pubDate>
      <link>https://dev.to/dataful/secretservice-library-for-storing-secrets-in-google-apps-script-4cah</link>
      <guid>https://dev.to/dataful/secretservice-library-for-storing-secrets-in-google-apps-script-4cah</guid>
      <description>&lt;p&gt;Google Apps Script is an excellent tool for connecting various services via API. Access to API requires storing secrets: tokens, passwords, and other secrets. There are &lt;a href="https://dataful.tech/apps-script/security/how-to-store-secrets/"&gt;many ways to store secrets in Google Apps Script&lt;/a&gt;, but most of them are challenging and have drawbacks. The most popular way to store secrets is still saving them directly in the code: it might be okay for a small one-off script that only you have access to, but it doesn’t cut it when we are talking about real-life implementations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;We created &lt;code&gt;SecretService&lt;/code&gt; (&lt;a href="https://github.com/dataful-tech/secret-service"&gt;GitHub&lt;/a&gt;), an open-source Google Apps Script library, to solve this problem. It is designed to simplify storing secrets and add various quality-of-life improvements.&lt;/p&gt;

&lt;p&gt;These are the main features of &lt;code&gt;SecretService&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choose a storage for the secrets:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Any Properties instance&lt;/em&gt; from your script&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Custom secret storage&lt;/em&gt;, like Google Cloud Secret Manager&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different modes in case of a missing secret:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Silent:&lt;/em&gt; do nothing, return null.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Strict:&lt;/em&gt; throw an error.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Interactive:&lt;/em&gt; prompt the user for a missing secret.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;If you find this library useful, please give the &lt;a href="https://github.com/dataful-tech/secret-service"&gt;repository&lt;/a&gt; a star and share the link with others.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;There are several ways to use SecretService. Check out the &lt;a href="https://github.com/dataful-tech/secret-service"&gt;repository&lt;/a&gt; for the complete documentation and more examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Store Secrets in UserProperties
&lt;/h3&gt;

&lt;p&gt;Generally, the safest out of the box place to store secrets is &lt;code&gt;UserProperties&lt;/code&gt;: they are accessible only to the user who runs the script, and only in the script you mean.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; regardless of what the &lt;a href="https://developers.google.com/apps-script/reference/properties/properties-service#getuserproperties"&gt;official documentation&lt;/a&gt;, says the user properties of the owner of a Google Sheets document are accessible to anyone via a custom function. (why Google, why?)&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SECRETS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SecretService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropertiesService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserProperties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Only once:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SECRETS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SECRETS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Interactive Mode
&lt;/h3&gt;

&lt;p&gt;If your Google Apps Script is attached to a spreadsheet/document/form/slides, you can use interactive mode that will prompt the user to enter the secret if it was not set before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SECRETS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SecretService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropertiesService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserProperties&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;scriptContainer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interactive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SECRETS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;User prompt:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3bo8lcgd8o529vk031z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3bo8lcgd8o529vk031z.png" alt="SecretService: Prompting user for a secret" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the user clicks &lt;code&gt;Cancel&lt;/code&gt; or closes the prompt, SecretService will throw an error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flubqltkxysb2na382atj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flubqltkxysb2na382atj.png" alt="SecretService: Error if the secret was not provided" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Contributions are welcome. Feel free to submit pull requests or issues on &lt;a href="https://github.com/dataful-tech/secret-service"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>googleappsscript</category>
      <category>gas</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>FetchApp: UrlFetchApp with Retries</title>
      <dc:creator>Dataful.Tech</dc:creator>
      <pubDate>Thu, 04 Jan 2024 22:56:14 +0000</pubDate>
      <link>https://dev.to/dataful/fetchapp-urlfetchapp-with-retries-5845</link>
      <guid>https://dev.to/dataful/fetchapp-urlfetchapp-with-retries-5845</guid>
      <description>&lt;p&gt;Google Apps Script is often used to pull data from various services via HTTP requests.  However, these requests sometimes fail due to network or service issues. The default behavior of &lt;code&gt;UrlFetchApp&lt;/code&gt; is to throw an exception, which you have to catch. Otherwise, the script execution will be interrupted.&lt;/p&gt;

&lt;p&gt;We often need more: send the request again instead of failing. There is no built-in way to do retries in Apps Script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;To solve this problem and not copy-and-paste code snippets from project to project, I created &lt;code&gt;FetchApp&lt;/code&gt; (&lt;a href="https://github.com/dataful-tech/fetch-app"&gt;GitHub&lt;/a&gt;) -- an open-source Google Apps Script library. &lt;/p&gt;

&lt;p&gt;Its main features are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Optional Retries:&lt;/strong&gt; Depending on the response code received.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delay Strategies:&lt;/strong&gt; Choose between linear or exponential delay between retries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Callbacks:&lt;/strong&gt; Implement callbacks on failed attempts for tailored actions and logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Type Hints:&lt;/strong&gt; Improved hints for UrlFetchApp's &lt;code&gt;params&lt;/code&gt; argument.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Logging:&lt;/strong&gt; Logs failed attempts automatically.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I use this library in many projects in production, especially where I have to communicate with unreliable third-party APIs, or there is a chain of related requests that I do not want to repeat if one of them randomly fails.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;If you find this library useful, please give the &lt;a href="https://github.com/dataful-tech/fetch-app"&gt;repository&lt;/a&gt; a star and share the link with others.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Features, Use Cases, Examples
&lt;/h2&gt;

&lt;p&gt;There are multiple ways and use cases to use &lt;code&gt;FetchApp&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drop-in Replacement for UrlFetchApp
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;FetchApp&lt;/code&gt; is designed to work as a drop-in replacement for &lt;code&gt;UrlFetchApp&lt;/code&gt;. Caveat: &lt;code&gt;FetchApp&lt;/code&gt; sets &lt;code&gt;muteHttpExceptions: true&lt;/code&gt; in &lt;code&gt;params&lt;/code&gt; unless explicitly specified otherwise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// `url` and `params` are defined elsewhere&lt;/span&gt;

&lt;span class="c1"&gt;// regular UrlFetchApp&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UrlFetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// FetchApp without configuration is a pass-through to UrlFetchApp&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// FetchApp with retries and delay enabled&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;successCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// If there are no `params`, pass an empty object&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configurable Client
&lt;/h3&gt;

&lt;p&gt;If you need to use FetchApp multiple times, you can initiate a client to reuse the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// FetchApp with retries and delay enabled&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retryCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// All client's fetch calls will use this config&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Partially modify the config for a specific request&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;successCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Success or Retry Response Codes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;FetchApp&lt;/code&gt; retries requests depending on the response code received in two different modes: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;successCodes&lt;/code&gt;: deem responses with these codes successful and return the response. If provided, &lt;code&gt;retryCodes&lt;/code&gt; are ignored.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retryCodes&lt;/code&gt;: requests with these codes are not successful; retry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;successCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// Everything else leads to retries&lt;/span&gt;
    &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;retryCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// Everything else is deemed successful&lt;/span&gt;
    &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;successCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// Takes priority over retryCodes&lt;/span&gt;
    &lt;span class="na"&gt;retryCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// Ignored&lt;/span&gt;
    &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delay Between Requests
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;FetchApp&lt;/code&gt; supports constant or exponential delay between retries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;successCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// Everything else leads to retries&lt;/span&gt;
    &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Constant delay of 300ms after each request&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;successCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// Everything else is deemed successful&lt;/span&gt;
    &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Exponential delay of 1, 2, 4, 8, etc. seconds&lt;/span&gt;
    &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;delayFactor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;successCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// Everything else is deemed successful&lt;/span&gt;
    &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Exponential delay of 1, 2, 4, 8, 10 seconds.&lt;/span&gt;
    &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;delayFactor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Limit delay to maximum 10 seconds&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Throw Exceptions via Callbacks
&lt;/h3&gt;

&lt;p&gt;If you need to throw an exception if all attempts fail, you can do it via a &lt;code&gt;onAllRequestsFailure&lt;/code&gt; callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Throw an exception&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;throwException&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`All &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; requests to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;successCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onAllRequestsFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;throwException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Send Notifications via Callbacks
&lt;/h3&gt;

&lt;p&gt;One of the difficulties with Apps Script is that it functions as a black box unless you add logging and notifications to the script. With the &lt;code&gt;onRequestFailure&lt;/code&gt; and &lt;code&gt;onAllRequestsFailure&lt;/code&gt; callbacks, you can send notifications about failed requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Define the `sendNotification` function elsewhere&lt;/span&gt;

&lt;span class="c1"&gt;// Send notification if access is denied, maybe because the credentials expired&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessDenied&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getResponseCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseCode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Received &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;responseCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; when accessing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Send a notification if all attempts failed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allAttemptsFailed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`All &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; requests to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;successCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onRequestFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accessDenied&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onAllRequestsFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;allAttemptsFailed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Autocomplete and Type Hints
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;FetchApp&lt;/code&gt; comes with JSDoc declarations that enable type hints and autocomplete in Google Apps Script IDE, both for &lt;code&gt;UrlFetchApp&lt;/code&gt;'s &lt;code&gt;params&lt;/code&gt; and for &lt;code&gt;FetchApp&lt;/code&gt;'s &lt;code&gt;config&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BtMMm8Re--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ftijuieucxhwpz8guxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BtMMm8Re--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ftijuieucxhwpz8guxl.png" alt="FetchApp: Autocomplete params and config" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, due to the IDE limitations, rich autocomplete doesn't work when you attach &lt;code&gt;FetchApp&lt;/code&gt; as a library (as opposed to copying the code).  Nevertheless, you still have the description of possible options in the type hints:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2H8EaPQs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kjbff81g8qym4xdmvqqb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2H8EaPQs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kjbff81g8qym4xdmvqqb.png" alt="FetchApp: Type hints for arguments" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Contributions are welcome. Feel free to submit pull requests or issues on &lt;a href="https://github.com/dataful-tech/fetch-app"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>gas</category>
      <category>googleappsscript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Append Multiple Rows in Google Sheets with Apps Script</title>
      <dc:creator>Dataful.Tech</dc:creator>
      <pubDate>Wed, 03 Jan 2024 23:27:30 +0000</pubDate>
      <link>https://dev.to/dataful/how-to-append-multiple-rows-in-google-sheets-with-apps-script-11ga</link>
      <guid>https://dev.to/dataful/how-to-append-multiple-rows-in-google-sheets-with-apps-script-11ga</guid>
      <description>&lt;p&gt;It is often necessary to append multiple rows to a sheet in Google Sheets via Apps Script. Unfortunately, the built-in function &lt;code&gt;Sheet.appendRow()&lt;/code&gt; can append only one row at a time. &lt;/p&gt;

&lt;p&gt;Of course, you could loop through the rows and append them one by one like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSpreadsheet&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getSheetByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Some Sheet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt;

&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this approach has several drawbacks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It is slow&lt;/strong&gt;: each append operation needs time, which is manageable when appending 2-3 rows but becomes inefficient with 100 or more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Each append will trigger formulas that use this range&lt;/strong&gt; making it even slower and delaying next append operations.  further slowing down the process and delaying subsequent append operations. In cases with abundant data/formulas, the delay can stretch into tens of seconds or even minutes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;There's no guarantee that the script will successfully append all rows&lt;/strong&gt;. Each Apps Script operation, especially interacting with external resources, is prone to random failures.  It's crucial to consider: if the script fails on line X, what will be the state of the data.  Reducing the number of operations enhances reliability.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Better Solution: Custom Function
&lt;/h3&gt;

&lt;p&gt;The following function implements a more efficient solution, which appends all rows in a single operation—drastically improving speed and reliability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Appends rows to the given sheet with the specified values.
 *
 * @param {SpreadsheetApp.Sheet} sheet - The sheet to which rows will be appended.
 * @param {Array&amp;lt;Array&amp;lt;*&amp;gt;&amp;gt;} rows - A 2D array containing the values to be appended. Each inner array represents a row, and each element within an inner array represents a cell value.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appendRows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Early exit if there are no values to append&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="c1"&gt;// Get the range where the new rows should be appended:&lt;/span&gt;
  &lt;span class="c1"&gt;// starting from the row following the last row with data, column 1,&lt;/span&gt;
  &lt;span class="c1"&gt;// with dimensions matching the size of 'values'&lt;/span&gt;
  &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLastRow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;         
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Set the values in the specified range&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Utilizing this function is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSpreadsheet&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getSheetByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Some Sheet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt;

&lt;span class="nf"&gt;appendRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The preceding JSDoc annotation provides a helpful message and type hints (albeit without IDE validation) when utilizing the function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M-ys5Eb5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9kskb578nb3izt9eeat4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M-ys5Eb5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9kskb578nb3izt9eeat4.png" alt="appendRows function with a helpful message and type hints" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Optimizing the process of appending multiple rows in Google Sheets is crucial for maintaining efficient and reliable script operations. While the built-in &lt;code&gt;Sheet.appendRow()&lt;/code&gt; function serves its purpose for appending single rows, the &lt;code&gt;appendRows&lt;/code&gt; function shared above vastly improves performance and reliability when dealing with multiple rows. &lt;/p&gt;

&lt;p&gt;By reducing the number of operations and avoiding formula triggers on each append, this function significantly speeds up the process, especially in sheets with a large amount of data or formulas. This example demonstrates the importance of fine-tuning Google Apps Script operations to better handle larger datasets, ensuring your scripts run smoothly and reliably.&lt;/p&gt;

</description>
      <category>googlesheets</category>
      <category>gas</category>
      <category>googleappsscript</category>
    </item>
    <item>
      <title>How to Store Secrets in Google Apps Script</title>
      <dc:creator>Dataful.Tech</dc:creator>
      <pubDate>Thu, 21 Dec 2023 20:15:41 +0000</pubDate>
      <link>https://dev.to/dataful/how-to-store-secrets-in-google-apps-script-215f</link>
      <guid>https://dev.to/dataful/how-to-store-secrets-in-google-apps-script-215f</guid>
      <description>&lt;p&gt;Security of any IT system relies on many factors, including the security of credentials, API keys, and other essential details collectively known as "secrets." If leaked, these secrets can lead to the system being compromised, resulting in data breaches, deletion, scams, and other adverse effects.&lt;/p&gt;

&lt;p&gt;This concern is particularly relevant for Google Apps Script. First, these scripts often need access to external systems, requiring you to store secrets within the script itself or somewhere close. Second, Apps Scripts are frequently linked to shared documents, automatically granting access to the script. Without proper care, these secrets may be exposed.&lt;/p&gt;

&lt;p&gt;Storing secrets safely in Apps Script is complex. It relies on various surrounding factors, such as how the script is run, who has access to the document, and the volume of data updated. There's no universal solution; a comprehensive approach is required.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore different options for storing secrets in Apps Script, ranging from the least to most secure. We'll review the pros and cons of each approach and guide you in selecting the best method for your situation. To highlight the vulnerabilities of each option, we'll assume that any user with access to the credentials could be malicious. While this may seem drastic, it's a vital consideration, as even trustworthy users' accounts can be hacked, leading to unauthorized access to other systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Options for Storing Secrets in Apps Script
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Secrets Directly in the Code
&lt;/h3&gt;

&lt;p&gt;The simplest way to store secrets is to do it in the code as plain text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not really a secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quick and simple.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No security. Anyone with read permissions to the script or document the script is attached to, can access the secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While convenient, this method is only suitable if you alone have access to the script. Even then, it's risky.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encrypted Secrets in the Code
&lt;/h3&gt;

&lt;p&gt;Encrypting secrets and prompting the user for a "password" to decrypt them is another option. However, Apps Script lacks built-in cryptographic functionality, so external libraries like &lt;a href="https://github.com/brix/crypto-js"&gt;crypto-js&lt;/a&gt; are needed. Here is an &lt;a href="https://tanaikech.github.io/2022/12/21/encrypting-and-decrypting-with-aes-using-crypto-js-with-google-apps-script/"&gt;example&lt;/a&gt; of how to use this library in Apps Script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More secure than plain text storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The script must decrypt the secret at runtime, where it can be logged or otherwise compromised by anybody who has edit permissions.&lt;/li&gt;
&lt;li&gt;Since the encrypted secret is accessible to anyone with read permissions, the perpetrator could save it and brute-force the password.&lt;/li&gt;
&lt;li&gt;Since you are asking the user the password every time, you cannot schedule the script to run automatically.&lt;/li&gt;
&lt;li&gt;Prompting the user for a password every time will encourage the user to store it less securely. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, this method is complex and has many drawbacks. Plus there are more appealing alternatives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Script Properties
&lt;/h3&gt;

&lt;p&gt;Google Apps Script provides a &lt;code&gt;PropertiesService&lt;/code&gt; that allows you to store values attached to a user, script, or document (if there is an attached document).  In the &lt;a href="https://developers.google.com/apps-script/guides/properties"&gt;documentation&lt;/a&gt; Google even mentions that Script Properties are typically used to store "the username and password for the developer's external database".  Let's see how it can work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Retrieves secret from the Script Properties or throws an error&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getScriptSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PropertiesService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getScriptProperties&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Secret &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is empty`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;// Example&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getScriptSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getScriptSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You (and everybody else with edit access) can see and manage the script properties in the settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IceRh3Zj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aml7jjxwki8g0k8czq1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IceRh3Zj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aml7jjxwki8g0k8czq1m.png" alt="Script Properties interface in Apps Script settings" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only script editors will have access to the secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All script editors will have access to the secrets.  Editors include all accounts that have edit access to the document where this script is attached to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a reasonably good option if you absolutely trust the people who have edit access.  If a script is attached to a document, you could also use document properties, although they do not offer any additional benefits and will not be visible in the settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Properties
&lt;/h3&gt;

&lt;p&gt;Similar to the Script Properties, you could store secrets in User Properties.  These properties are accessible only to the user who created them.  There is no interface to edit them: you will need either to manually save them the first time or prompt the user for secrets, if they are not there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Retrieves or prompts and stores a secret in UserProperties&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userProperties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PropertiesService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserProperties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userProperties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;

    &lt;span class="c1"&gt;// If secret is not set, prompt the user to enter it&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Secret Management&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`Please enter &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ButtonSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OK_CANCEL&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// If user clicked "OK", save the secret to User Properties&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSelectedButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getResponseText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nx"&gt;userProperties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// User clicked "CANCEL" or closed the prompt&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User has not entered the secret &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Clears a secret from UserProperties&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;clearSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userProperties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PropertiesService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserProperties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;userProperties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Clearing secrets&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;clearSecrets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;clearSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;clearSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;// Example&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prompting user for the script:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xLbaIVdM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zn2gvg967e3t65ykh0hb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xLbaIVdM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zn2gvg967e3t65ykh0hb.png" alt="Prompting user for a secret when running the script for the first time" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only you will have access to the secret unless the script logs it at the runtime.&lt;/li&gt;
&lt;li&gt;You can schedule the script to run periodically under your account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If other users can edit the script, they can log the secret when you run the script from your account (either manually or on trigger).  Even if you catch it, the secrets will be compromised.&lt;/li&gt;
&lt;li&gt;If you need other people to run the script on their own, you will have to provide them with the secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Google Cloud’s Secret Manager
&lt;/h3&gt;

&lt;p&gt;You could use Google Cloud Secret Manager to store the secrets and retrieve them from the Apps Script.  The setup is more involved and deserves its own post.  Here we will look at its pros and cons.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can re-use the secrets stored in the Manager in many different scripts.  And, if you need to change them, you can do it in one place.&lt;/li&gt;
&lt;li&gt;Same level of security as storing with User Properties.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires &lt;code&gt;https://www.googleapis.com/auth/cloud-platform&lt;/code&gt; scope which is very broad.  More about &lt;a href="https://dataful.tech/apps-script/scopes/how-to-set/"&gt;permission scopes and their security implications&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Other users will not be able to run the script unless you grant them access to the secrets storage which lessens security.&lt;/li&gt;
&lt;li&gt;This method is more difficult to set up both on the script and Google Cloud sides.&lt;/li&gt;
&lt;li&gt;Google Cloud Secret Manager is a paid product (&lt;a href="https://cloud.google.com/secret-manager/pricing"&gt;pricing&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A Detached Script
&lt;/h3&gt;

&lt;p&gt;If many people need access to a document that needs to be updated with a script, you could use a completely detached script so only you or a very limited group will have access to it.  In that script you could store the credentials either in the User or Script properties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is one of the most secure options: only people who have edit access to the script can compromise the secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To update a document from a detached script, you will have to grant the script very wide permissions scopes like &lt;code&gt;https://www.googleapis.com/auth/spreadsheets&lt;/code&gt; which would allow the script to do anything with your documents.  This is not the safest approach.  You can read about security scopes &lt;a href="https://dataful.tech/apps-script/scopes/how-to-set/"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;From the script you cannot manipulate the user interface of the document: show alerts, custom menus, etc.&lt;/li&gt;
&lt;li&gt;You will have to rely mostly on time scheduled script triggers.  You cannot easily run the remote script from the document, for example, to do a manual update.  You could still do it by using a script attached to a document and running the remote script via API or &lt;a href="https://developers.google.com/apps-script/api/how-tos/execute"&gt;script.run&lt;/a&gt;. However, both things require a rather involved setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Two Documents, Attached Script, and IMPORTRANGE
&lt;/h3&gt;

&lt;p&gt;Similar to the previous method, you could separate presentation of the data from its update.  To achieve it, create two documents: one for presentation and another for storing raw data.  They will be connected via an &lt;code&gt;IMPORTRANGE&lt;/code&gt; function so Google will manage updating data between the documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is one of the most secure options: only people with edit access to the second document/script could compromise the secrets.&lt;/li&gt;
&lt;li&gt;The update script does not need extra permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will still have to rely mostly on scheduled runs of the script.&lt;/li&gt;
&lt;li&gt;You will be subject to &lt;code&gt;IMPORTRANGE&lt;/code&gt; limitations: there is a limit on volume of data that can be transferred in one go.  According to the &lt;a href="https://support.google.com/docs/answer/3093340?hl=en&amp;amp;sjid=12693158709126288208-NA"&gt;documentation&lt;/a&gt; it is 10 MB per update.  In practice it usually means that you can import up to 200,000 cells in one &lt;code&gt;IMPORTRANGE&lt;/code&gt;.  Plus, if the source sheet is updated too often, your updates can be throttled. Read more on &lt;a href="https://dataful.tech/google-sheets/formulas/importrange/result-too-large/"&gt;how to deal with IMPORTRANGE limitations&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Sometimes &lt;code&gt;IMPORTRANGE&lt;/code&gt; errors out due to random factors.  To mitigate it you will need to wrap it in &lt;code&gt;IFERROR&lt;/code&gt; function that will retry if an error occurs:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;IFERROR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;IMPORTRANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;document&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;range&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;IMPORTRANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;document&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;range&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Do Not Store Secrets, Prompt User Every Time
&lt;/h3&gt;

&lt;p&gt;The safest way to store secrets in Apps Script is not to store them at all.  Each time the user runs a script, you could ask them for the secret.  The code is similar to the one above where we were storing the secrets in User Properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;promptUserForSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Prompt the user to enter it&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Secret Management&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`Please enter &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ButtonSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OK_CANCEL&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// If user clicked "OK"&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSelectedButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getResponseText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// User clicked "CANCEL" or closed the prompt&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User has not entered the secret &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;// Example&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promptUserForSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promptUserForSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The secret is not stored anywhere and can only leak if the script logs or otherwise records the it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will be limited to running the script manually: it will not have the secrets when run on time trigger.&lt;/li&gt;
&lt;li&gt;This method is still vulnerable to script edits: anybody who has edit access can modify the script and intercept the secret.&lt;/li&gt;
&lt;li&gt;Forcing users to enter the secret every time, nudges them to store it somewhere accessible which can be less secure than doing it with other options.&lt;/li&gt;
&lt;li&gt;Every user who you needs to run the script has to explicitly know the secrets. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Choose Optimal Way to Store Secrets
&lt;/h2&gt;

&lt;p&gt;As you can see, there are multiple ways to store secrets in Apps Script.  There is no one-size-fits-all approach.  When choosing a way to store secrets in Apps Script, it's essential to take into account various factors. Here's a breakdown to help guide your decision:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Considerations About Other People:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read Access:&lt;/strong&gt; If others have read access, avoid storing secrets in plain text.  Anyway, it is better to avoid it altogether.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit Access:&lt;/strong&gt; If others can edit the document or script, assess trust levels. Even trusted individuals can have their accounts compromised.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Script Use Case:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run Type:&lt;/strong&gt; Whether the script runs on schedule or manually will impact your options for storing secrets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Interaction:&lt;/strong&gt; If the script needs to be run manually, will it be only you or other users as well?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Functionality of the Script:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much and what kind of data will the script transfer?  Can it be imported into a separate document and transferred with an &lt;code&gt;IMPORTRANGE&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Does the script need to interact with the user interface: alerts, prompts, custom menus, etc.?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consequences of Compromised Secrets:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The more damaging the result, the more paranoid you should be when storing the secrets.&lt;/li&gt;
&lt;li&gt;What data will the leaked secrets grant access to?&lt;/li&gt;
&lt;li&gt;Do you track access with those secrets?&lt;/li&gt;
&lt;li&gt;Can you revoke or invalidate those secrets?&lt;/li&gt;
&lt;li&gt;Can user update or delete data using those secrets?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a chart that illustrates common scenarios and questions when choosing a way to store secrets in Apps Script.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u7oYcQwR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k5hi7lboqshj2gy8gwbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u7oYcQwR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k5hi7lboqshj2gy8gwbf.png" alt="Diagram: How to choose the method of storing secrets in Google Apps Script" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;It is important to always keep security of your Apps Script projects in mind and storing secrets is an integral part of this issue.  Hopefully, this guide has provided you with valuable insights into the various methods available for storing secrets and how to choose the best for your situation. &lt;/p&gt;

</description>
      <category>gas</category>
      <category>googleappsscript</category>
      <category>security</category>
    </item>
    <item>
      <title>Secure Your Data: Understand and Manage Authorization Scopes in Google Apps Scripts</title>
      <dc:creator>Dataful.Tech</dc:creator>
      <pubDate>Tue, 19 Dec 2023 14:57:38 +0000</pubDate>
      <link>https://dev.to/dataful/secure-your-data-understand-and-manage-authorization-scopes-in-google-apps-scripts-55ko</link>
      <guid>https://dev.to/dataful/secure-your-data-understand-and-manage-authorization-scopes-in-google-apps-scripts-55ko</guid>
      <description>&lt;p&gt;If you ever used Apps Scripts or add-ons for Google Drive/Docs/Sheets, let alone developed them, you have engaged with authorization scopes, whether you're familiar with the term or not.  For instance, the first time you run a new script you have to grant it specific permissions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JYCetHHt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q9vng6rxypkva22jiwig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JYCetHHt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q9vng6rxypkva22jiwig.png" alt="App asking for full access to all your spreadsheets" width="800" height="1119"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes you may even need to ignore Google's security warnings indicating the potential risks associated with running a script:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L6S4mwPd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/emcazm93btu5m62twh9n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L6S4mwPd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/emcazm93btu5m62twh9n.png" alt="Security warning before running scripts" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, we'll delve into understanding what authorization scopes are, their importance, and how to audit and revoke them. This information is particularly crucial if you frequently use scripts: you might wish to revoke permissions that are no longer relevant.  In another post we will see &lt;a href="https://dataful.tech/apps-script/scopes/how-to-set/"&gt;how to properly set scopes a script needs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Authorization Scopes and Their Importance
&lt;/h2&gt;

&lt;p&gt;Authorization scopes define what a script can do on your behalf concerning your data and account on Google. Security is the primary reason to be concerned about them. To illustrate, let's compare two different scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"See, edit, create, and delete all your Google Sheets spreadsheets"&lt;/em&gt; (scope &lt;code&gt;https://www.googleapis.com/auth/spreadsheets&lt;/code&gt;) - This allows the script full access to all spreadsheets you can access. Any action it performs is as though you did it yourself, including creating, reading, modifying, or deleting anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Fy8AcBc9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipxylntwa9plhxugg0he.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Fy8AcBc9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipxylntwa9plhxugg0he.png" alt="App asking for full access to all your spreadsheets" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"View and manage spreadsheets that this application has been installed in"&lt;/em&gt; (scope &lt;code&gt;https://www.googleapis.com/auth/spreadsheets.currentonly&lt;/code&gt;) - The script will only have access to the document where it was installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MZ8EVVie--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iljtd23l56a18hhxxw8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MZ8EVVie--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iljtd23l56a18hhxxw8l.png" alt="App asking for access only to the spreadsheet where it is installed" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you work alone and don't share your documents and scripts with anyone, the distinction might not seem significant, and it may seem reasonable to grant broader permissions. However, even in this scenario, it's advisable to use a more restrictive scope.&lt;/p&gt;

&lt;p&gt;Suppose you shared a spreadsheet that includes an attached script with another editor. The script is also shared, and the editor can modify it, possibly to copy data or entire documents when you execute the script. Furthermore, if the script is set to run automatically (e.g., periodically via a time trigger), it could execute under your name unexpectedly.&lt;/p&gt;

&lt;p&gt;Such instances can lead to data leaks and losses, with potential business and legal implications.  A compromised account is all it takes for a security breach, and with the rise of cybercrime, it's essential to stay vigilant and minimize risks early on.&lt;/p&gt;

&lt;p&gt;Another challenging aspect of scopes is that it's not straightforward to view the permissions you've granted and revoke them. We'll explore the different options in the following section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auditing and Revoking Scopes
&lt;/h2&gt;

&lt;p&gt;Several tools allow you to see the permissions you've granted and potentially detect any unusual activities. Google continually enhances these mechanisms, so the actual details may differ slightly from you we will see below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Checkup
&lt;/h3&gt;

&lt;p&gt;In your account's Security section, you can find the Security Checkup. This tool highlights potential issues, such as scripts from unverified developers or scripts with dangerously permissive authorization scopes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PvC3J7IA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpa4lbvnnniezyqopx0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PvC3J7IA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpa4lbvnnniezyqopx0i.png" alt="Security Review: applications that might cause issues" width="800" height="986"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see the permissions you've granted to a given script:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9I_4fksp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9e78gv15i3vl0dphoygu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9I_4fksp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9e78gv15i3vl0dphoygu.png" alt="Details of the application in the Security Review" width="800" height="923"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Third-party Apps &amp;amp; Services
&lt;/h3&gt;

&lt;p&gt;Again in the &lt;a href="https://myaccount.google.com/security"&gt;Security&lt;/a&gt; section, you can find &lt;a href="https://myaccount.google.com/connections"&gt;Third-party apps &amp;amp; services&lt;/a&gt;.  It is similar to the Security Checkup, lets you filter by permission type or application (script) name:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xKyx7ODt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpug9am85a64r7opgr18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xKyx7ODt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpug9am85a64r7opgr18.png" alt="Third party apps that were granted permissions" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you can review permissions for each of the apps and decide whether to revoke them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CQBK3GfM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8gwh7t7s5g7hggdbp3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CQBK3GfM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8gwh7t7s5g7hggdbp3k.png" alt="Details of and permissions granted to a third-party app" width="800" height="325"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Review Triggers
&lt;/h3&gt;

&lt;p&gt;Triggers enable scripts to execute without your direct involvement, such as at specific times. However, this feature presents a risk: if someone else can modify the script, they can make it do anything within the script's authorization scopes. You can review all your account's triggers &lt;a href="https://script.google.com/home/triggers"&gt;here&lt;/a&gt; and make necessary adjustments or deletions.&lt;/p&gt;

&lt;p&gt;While removing triggers doesn't change the scopes, it reduces the risk that the scope can be misused. &lt;/p&gt;

&lt;h3&gt;
  
  
  Going Through Scripts Manually
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://script.google.com/"&gt;script.google.com&lt;/a&gt; portal displays all the scripts/projects you've created or have been given access to. This method is a bit more tedious but helpful for in-depth reviews or identifying specific scripts.&lt;/p&gt;

&lt;p&gt;Each Apps Script project has an overview section where you can see scopes requested by the scripts.  However, there you cannot see whether you granted those permissions.  For that you need to go to either Security Checkup or Third-party apps &amp;amp; services in the account settings (more on that below).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pzirhume--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hgycmbetb0i4syzwnfjb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pzirhume--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hgycmbetb0i4syzwnfjb.png" alt="Scopes in project settings" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another place where you might be able to see permissions is the manifest file.  However, by default it is hidden and doesn't contain the permissions.  More on the manifest file &lt;a href="https://dataful.tech/apps-script/scopes/how-to-set/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Review Script Executions
&lt;/h3&gt;

&lt;p&gt;This &lt;a href="https://script.google.com/home/executions"&gt;section&lt;/a&gt; allows you to see recent script executions under your account, access the relevant project, view logs, and more.  This can be useful for a more in-depth review.&lt;/p&gt;




&lt;p&gt;Remember, scopes are a vital aspect of your data security. While Google provides several tools for auditing and revoking them, it's ultimately up to you to use them wisely and minimize the risk of breaches. Stay vigilant, regularly review your permissions, and remain proactive in maintaining your data's safety.&lt;/p&gt;

</description>
      <category>gas</category>
      <category>googleappsscript</category>
      <category>security</category>
    </item>
    <item>
      <title>Authorization Scopes in Google Apps Script: Convenience vs. Security</title>
      <dc:creator>Dataful.Tech</dc:creator>
      <pubDate>Sun, 17 Dec 2023 14:16:05 +0000</pubDate>
      <link>https://dev.to/dataful/authorization-scopes-in-google-apps-script-convenience-vs-security-p45</link>
      <guid>https://dev.to/dataful/authorization-scopes-in-google-apps-script-convenience-vs-security-p45</guid>
      <description>&lt;p&gt;Authorization scopes are a crucial element of any Apps Script, as they determine what your script is allowed to do. More importantly, they set the boundaries for the script, providing security (more on that &lt;a href="https://dataful.tech/apps-script/scopes/how-to-manage-and-audit/"&gt;here&lt;/a&gt;). That is why it is vital to understand how to properly set the required scopes and which scopes to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set scopes in Apps Script
&lt;/h2&gt;

&lt;p&gt;There are two ways to identify the permission scopes required for a script: automatically and manually (explicitly).&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Scopes Automatically
&lt;/h3&gt;

&lt;p&gt;In automatic mode, when you run the script, Google checks if the script uses functions that require specific permissions, such as &lt;code&gt;UrlFetchApp&lt;/code&gt;, &lt;code&gt;SpreadsheetApp&lt;/code&gt; (often they end with "App"). If it does, Google will ask you for the permissions required to work with those functions. This is very convenient for the developer, yet it does compromise security.&lt;/p&gt;

&lt;p&gt;In the example above, Google will ask you to grant the script two permissions: full access to all your spreadsheets (&lt;code&gt;SpreadsheetApp&lt;/code&gt;) and access to external services (&lt;code&gt;UrlFetchApp&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0VZ7qS6v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pi413if9dux3zd3rcaj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0VZ7qS6v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pi413if9dux3zd3rcaj3.png" alt="App asking for full access to all your spreadsheets" width="800" height="1119"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Access to external services is acceptable -- there is no other way to achieve the same result, and on its own, it is difficult to misuse. However, full access to all spreadsheets is too broad. In most cases, you want to write data to the sheet the script is attached to. Thus, you only need access there, which would be much safer. There is a special permission for that which needs to be set manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Scopes Manually (Explicitly)
&lt;/h3&gt;

&lt;p&gt;The best security practice is to request as narrow permissions as possible, which would still be enough to do the job. In this case, if anything goes wrong (whether it's a bug in the code or a security breach), the scope of the problem will be limited. To do this with Google Apps Script, you need to identify the required permissions explicitly.&lt;/p&gt;

&lt;p&gt;Every Apps Script project has an &lt;code&gt;appscript.json&lt;/code&gt; manifest file, which is hidden by default (I guess this is to promote bad security practices). To make it accessible, you need to open the project's settings and enable &lt;code&gt;Show "appsscript.json" manifest file in editor&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Et92UCBp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x2sr4u6xgyhpql7oxj5e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Et92UCBp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x2sr4u6xgyhpql7oxj5e.png" alt="Enable access to appsscript.json manifest file in the project settings" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that, the manifest file will appear in the list of files and will always be at the top. By default, it will contain only basic details of the project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ETMaVlN8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/csxsbowvv6lspuicbbtw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ETMaVlN8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/csxsbowvv6lspuicbbtw.png" alt="Default appsscirpt.json manifest file" width="800" height="356"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;To set the proper authorization scopes, you need to add the &lt;code&gt;oauthScopes&lt;/code&gt; property with the values. Following the example above, to require permission to access external services and the spreadsheet the script is attached to, the file should look as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timeZone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/Toronto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exceptionLogging"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STACKDRIVER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"runtimeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"V8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"oauthScopes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.googleapis.com/auth/spreadsheets.currentonly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.googleapis.com/auth/script.external_request"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; If you set permissions explicitly, you must provide an exhaustive list of permissions the script needs. Otherwise, performing an action that you do not have permission for will cause an error.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Scopes to Use
&lt;/h2&gt;

&lt;p&gt;There are three types of authorization scopes: regular, sensitive (containing user data like email, name, etc.), and restricted (&lt;a href="https://support.google.com/cloud/answer/9110914#restricted-scopes"&gt;examples&lt;/a&gt;). Depending on your use case, you might need to go through a special authorization process to use sensitive or restricted scopes. If you have fewer than 100 users of a given script, you usually do not have to do this, but you will be subject to the unverified app screen.&lt;/p&gt;

&lt;p&gt;You can find a list of most of the scopes for many Google services &lt;a href="https://developers.google.com/identity/protocols/oauth2/scopes"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's look at the most commonly used scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Access only to the document the script is attached to&lt;/strong&gt; -- a must-have and the easiest way to make your sheets safer. These scopes are so safe that on their own, they do not require a security warning (and with this trick you can &lt;a href="https://dataful.tech/apps-script/security/how-to-store-secrets/#two-documents-attached-script-and-importrange"&gt;work with the data from other sheets&lt;/a&gt; without more extensive permissions):

&lt;ul&gt;
&lt;li&gt;Sheets: &lt;code&gt;https://www.googleapis.com/auth/spreadsheets.currentonly&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;code&gt;https://www.googleapis.com/auth/documents.currentonly&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Slides: &lt;code&gt;https://www.googleapis.com/auth/presentations.currentonly&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only scopes.&lt;/strong&gt; These are the next best choice if you need to read data from documents other than the one where the script is installed.

&lt;ul&gt;
&lt;li&gt;Sheets: &lt;code&gt;https://www.googleapis.com/auth/spreadsheets.readonly&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;code&gt;https://www.googleapis.com/auth/documents.readonly&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Slides: &lt;code&gt;https://www.googleapis.com/auth/presentations.readonly&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Drive: &lt;code&gt;https://www.googleapis.com/auth/drive.readonly&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete access to all documents of a given type.&lt;/strong&gt; These are some of the most dangerous scopes possible and are highly discouraged unless absolutely necessary. Even then, use them with security precautions.

&lt;ul&gt;
&lt;li&gt;Sheets: &lt;code&gt;https://www.googleapis.com/auth/spreadsheets&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;code&gt;https://www.googleapis.com/auth/documents&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Slides: &lt;code&gt;https://www.googleapis.com/auth/presentations&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Drive: &lt;code&gt;https://www.googleapis.com/auth/drive&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Making requests to external services&lt;/strong&gt; via &lt;code&gt;UrlFetchApp&lt;/code&gt;: &lt;code&gt;&lt;a href="https://www.googleapis.com/auth/script.external_request"&gt;https://www.googleapis.com/auth/script.external_request&lt;/a&gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Getting the email of the user&lt;/strong&gt; running the script via &lt;code&gt;Session.getActiveUser().getEmail()&lt;/code&gt;: &lt;code&gt;&lt;a href="https://www.googleapis.com/auth/userinfo.email"&gt;https://www.googleapis.com/auth/userinfo.email&lt;/a&gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessing the user interface (UI):&lt;/strong&gt; &lt;code&gt;&lt;a href="https://www.googleapis.com/auth/script.container.ui"&gt;https://www.googleapis.com/auth/script.container.ui&lt;/a&gt;&lt;/code&gt;. This is mostly for complex UI interactions when creating custom interfaces. It does not include simple alerts and custom menus -- you can do those without this permission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creating and deleting triggers, working with user's permissions via ScriptApp:&lt;/strong&gt; &lt;code&gt;&lt;a href="https://www.googleapis.com/auth/script.scriptapp"&gt;https://www.googleapis.com/auth/script.scriptapp&lt;/a&gt;&lt;/code&gt;. Use this scope with caution, as it allows the creation of triggers, and triggers can run the script in your name without your knowledge. You can manage existing triggers here.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;We have reviewed two ways to set permission scopes.  &lt;strong&gt;The main takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Always set scopes explicitly, without relying on auto-detection which is often too permissive.  It takes a minute to set up in the very beginning but makes our lives easier and safer later.&lt;/li&gt;
&lt;li&gt;Use as narrow scopes as possible. &lt;/li&gt;
&lt;li&gt;Bonus point: learn &lt;a href="https://dataful.tech/apps-script/scopes/how-to-manage-and-audit/"&gt;how to audit scopes you have already granted&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>gas</category>
      <category>googleappsscript</category>
      <category>security</category>
    </item>
  </channel>
</rss>
