Google Sheets Connector
Documentation
Version: 6
Documentation

Google Sheets Connector - Source Code


<?xml version="1.0" encoding="utf-8"?>
<ApiConfig Name="Google Sheets"
           Desc="Read / write Google Sheets data inside your app, perform many Google Sheets operations without coding using easy to use high performance API Connector for Google Sheets"
           Slug="google-sheets-connector"
           Id="7895d782-e54c-4624-aa96-437cd3e92fa8"
           Logo="data:image/png;base64,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"
           Version="6"
           EngineVersion="9"
           >
  <VersionHistory>
    <Change Date="2024-06-05" Type="New">Added Redirect URL property to support Web Application credentials (visible when show advanced option checked)</Change>
	<Change Date="2023-09-07" Type="Modified">Made SpreadSheetId required on connection UI</Change>
    <Change Date="2022-10-13" Type="Modified">Added retry logic on error code 429 (Too many requests).</Change>
    <Change Date="2020-05-21" Type="New">Added examples and support for Tables (Sheet Tab name can be used as Table)</Change>
    <Change Date="2020-05-15" Type="New">Added support for Service Account Credentials (Using P12 key file)</Change>
    <Change Date="2020-05-15" Type="Fix">UseCustomApp option doesnt work</Change>
    <Change Date="2020-12-01">Initial version</Change>
  </VersionHistory>

  <ServiceUrls>
    <ServiceUrl Name="https://sheets.googleapis.com/v4/spreadsheets" Url="https://sheets.googleapis.com/v4/spreadsheets" />
  </ServiceUrls>
  <Auths>
    <Auth Name="OAuth" Label="User Account" HelpLink="https://zappysys.com/links/?id=10002"
          Type="OAuth" ConnStr="Provider=GoogleSheets;ScopeSeparator={space};"
          TestEndPoint="get_sheet_properties"
          TestParameters="SpreadSheetId=1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
      >
      <Notes>
        <![CDATA[This connection can be configured in two ways. Use Default App (Created by ZappySys) OR Use Custom App created by you.<br />
To use minimum settings you can start with the ZappySys created App. Just change UseCustomApp=false on the properties grid so you dont need
ClientID / Secret. When you click <b>Generate Token</b> you might see a warning about App is not trusted (Simply Click Advanced Link to
expand hidden section and then click Go to App link to Proceed).
<p />
To register a custom app, perform the following steps (Detailed steps found in the help link at the end):
<ol>
  <li>Go to <a target="_blank" href="https://console.developers.google.com/">Google API Console</a>.</li>
  <li>From the Project Dropdown (usually found at the top bar) click <b>Select Project</b>.</li>
  <li>On the Project Popup click <b>CREATE PROJECT</b>.</li>
  <li>Once the project is created you can click <b>Select Project</b> to switch the context (You can click on Notification link or Choose from Top Dropdown)</b>.</li>
  <li>Click <b>ENABLE APIS AND SERVICES</b>.</li>
  <li>Now we need to Enable two APIs one by one (Sheets API and Drive API).</li>
  <li>Search <b>Sheets</b>. Select and click <b>ENABLE</b>.</li>
  <li>Search <b>Drive</b>. Select and click <b>ENABLE</b>.</li>
  <li>Go back to the main screen of the <a target="_blank" href="https://console.developers.google.com/">Google API Console</a></li>
  <li>Click the <b>OAuth Consent Screen</b> tab. Enter necessary details and Save.</li>
  <li>Click the <b>Credentials</b> tab.</li>
  <li>Click <b>CREATE CREDENTIALS</b> (some where in topbar) and select <b>OAuth Client ID</b> option.</li>
  <li>When prompted Select Application Type as <b>Desktop App</b> and click Create to receive your ClientID and Secret. Later on you can use this information now to configure Connection with UseCustomApp=true.
    <div style="background-color: #f5f2c4; border-style: solid; border-color: #000000; border-width: 1px; margin-top: 3px; margin-bottom: 6px; padding: 6px;">
		<b>NOTE:</b> If you are planning to use your current data connection/token for automated processes, we recommend that you use a generic account for token generation when the login box appears (e.g. sales_automation@mycompany.com instead of bob_smith@mycompany.com). When you use a personal account which is tied to a specific employee profile and that employee leaves the company, the token may become invalid and any automated processes using that token will fail. Another potentially unwanted effect of using a personal token is incorrect logging; the API calls (e.g. Read, Edit, Delete, Upload) made with that token will record the specific user as performing the calls instead of an automated process.
    </div>
  </li>
  <li>Go to <b>OAuth Consent Screen</b> tab. Under Publishing Status click <b>PUBLISH APP</b> to ensure your refresh token doesnt expire often. If you planning to use App for Private use then do not have to worry about Verification Status after Publish.</li>
</ol>
]]>
      </Notes>
      <Params>
        <Param Name="UseCustomApp" Value="True" Required="True" Hidden="False" Desc="Use your own app credentials or inbuilt app provided by ZappySys for ease of use. If you choose UseCustomApp=true then make sure to obtain your own ClientId and Secret using steps provided (Click [Steps to Configure] link found next to Authentication Type dropdown)" />
        <Param Name="ClientId" VisibleIf="UseCustomApp" RequiredIf="UseCustomApp" />
        <Param Name="ClientSecret" Secret="True" VisibleIf="UseCustomApp" RequiredIf="UseCustomApp" />
        <Param Name="Scope" Value="https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/spreadsheets" />
        <Param Name="SpreadSheetId" Required="True" Label="Default SpreadSheetId" Desc="Enter default spreadsheet Id which should be used if user do not supply when calling various operations supported by this connector. To obtain sheet Id. Open spreadsheet in browser and check URL. You will see example value like 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms that is your sheet id." Options="Enter Your Sheet Id=;Example Sheet Id=1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"/>
        <Param Name="TabName" Label="Default Tab Name (i.e. Sheet1)" Desc="Enter default tab name for spreadsheet Id you supplied. If you keep it blank then it will use first tab by default." />
        <Param Name="RetryMode" Value="RetryWhenStatusCodeMatch" Hidden="True" />
        <Param Name="RetryStatusCodeList" Value="403|429" Hidden="True" />
        <Param Name="RetryCountMax" Value="5" Hidden="False" />
        <Param Name="ReturnUrl" Label="Redirect URL (Only for Web App)" Options=";https://zappysys.com/oauth/" VisibleIf="UseCustomApp" Desc="Only specifiy this if you have created Credential as Web Application rather than Desktop. In Desktop App you dont have to supply Return URL (its always localhost). When you keep this blank it uses default value http://localhost:[some_random_port_each_time] for redirect_url)"  />
      </Params>
    </Auth>

    <Auth Name="ServiceAccount" Label="Service Account (Using Private Key File)" HelpLink="https://cloud.google.com/docs/authentication/production#create_service_account"
        Type="OAuth" ConnStr="Provider=GoogleSheets;UseCustomApp=True;UseClientCertificate=True;CertificateStorageType=PfxFile;CertificatePath=[$PrivateKeyPath$];GrantType=ClientCredentials;"
        TestEndPoint="get_sheet_properties"
        TestParameters="SpreadSheetId=1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
  >
      <Params>
        <Param Name="ClientId" Label="Service Account Email" Desc="This is service account email ID (e.g. some_name@my_project.iam.gserviceaccount.com)" Required="True" />
        <Param Name="PrivateKeyPath" Label="Service Account Private Key Path (i.e. *.p12)" Desc="File path for p12 file (i.e. Private Key file for service account). Keep this key file secure" Required="True" />
        <Param Name="Scope" Value="https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/spreadsheets" />
        <Param Name="SpreadSheetId" Required="True" Label="Default SpreadSheetId" Desc="Enter default spreadsheet Id which should be used if user do not supply when calling various operations supported by this connector. To obtain sheet Id. Open spreadsheet in browser and check URL. You will example value like 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms that is your sheet id." Options="Enter Your Sheet Id=;Example Sheet Id=1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"/>
        <Param Name="TabName" Label="Default Tab Name (i.e. Sheet1)" Desc="Enter default tab name for spreadsheet Id you supplied. If you keep it blank then it will use first tab by default." />
        <Param Name="RetryMode" Value="RetryWhenStatusCodeMatch" Hidden="True" />
        <Param Name="RetryStatusCodeList" Value="403|429" Hidden="True" />
        <Param Name="RetryCountMax" Value="5" Hidden="True" />
      </Params>

      <Notes>
        <![CDATA[Use these steps to authenticate as service account rather than Google / GSuite User. Learn more about service account <a href="https://cloud.google.com/iam/docs/service-accounts" target="_blank">here</a><p />
Basically to call Google API as Service account we need to perform following steps listed in 3 sections (Detailed steps found in the help link at the end)<p />
<h3>Create Project</h3>
First thing is create a Project so we can call Google API. Skip this section if you already have Project (Go to next section)
<ol>
  <li>Go to <a target="_blank" href="https://console.developers.google.com/">Google API Console</a>
  <li>From the Project Dropdown (usually found at the top bar) click Select Project</li>
  <li>On Project Propup click <b>CREATE PROJECT</b></li>
  <li>Once project is created you can click Select Project to switch the context (You can click on Notification link or Choose from Top Dropdown)</b></li>
  <li>Click <b>ENABLE APIS AND SERVICES</b></li>
  <li>Now we need to Enable two APIs one by one (Sheets API and Drive API).</li>
  <li>Search <b>Sheets</b>. Select and click <b>ENABLE</b></li>
  <li>Search <b>Drive</b>. Select and click <b>ENABLE</b></li>
</ol>

<h3>Create Service Account</h3> 
Once Project is created and APIs are enabled we can now create a service account under that project. Service account has its ID which looks like some email ID (not to confuse with Google /Gmail email ID)
<ol>
  <li>Go to <a target="_blank" href="https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?supportedpurview=project/">Create Service Account</a>
  <li>From the Project Dropdown (usually found at the top bar) click Select Project</li>
  <li>Enter <b>Service account name</b> and <b>Service account description</b></li>
  <li>For Role, do not select anything for now and Click <b>Continue</b> and then click <b>Done</b>. Next we will create Key.</li>
</ol>

<h3>Create Key</h3>
Once service account is created we need to create key file (i.e. credentials).
<ol>
  <li>In the Cloud Console, click the email address for the service account that you created.</li>
  <li>Click <b>Keys</b>.</li>
  <li>Click <b>Add key</b>, then click <b>Create new key</b>.</li>
  <li>Click <b>Create</b> and select <b>P12</b> format. A <b>P12 key file</b> is downloaded to your computer. We will use this file in our API connection.</li>
  <li>Click <b>Close</b>.</li>
  <li>Now you may use downloaded *.p12 key file as secret file and Service Account Email as Client ID (e.g. <b>some-service-account-name@your-project-id.iam.gserviceaccount.com</b> ).</li>
</ol>

<h3>Add Permission</h3>
Now last thing is give read/write permission to Service Account. Basically you can create or open Google Sheet and add the Service Account as an editor to it as below.
<ol>
  <li>Copy the email address of your service account we created in previous step (its usually like this some-service-account-name@your-project-id.iam.gserviceaccount.com).</li>
  <li>Create or select an existing Google Sheet.</li>
  <li>Navigate to Sheet for which you like to give read/write access to Service Account.</li>
  <li>Click on the Share button in the top right, and add the email address of the service account as an editor. Here is how to <a href="https://support.google.com/drive/answer/2494822?hl=en&ref_topic=7000947" target="_blank">share file(s) with specific people</a>. Juse share with Service Account (use Service Account Email found on previous section)</li>
</ol>

]]>
      </Notes>
    </Auth>

  </Auths>
  <Template>

    <EndPoint>
      <Params>
        <Param Name="SpreadSheetId" Desc="Enter unique spreadsheet Id. To obtain sheet Id. Open spreadsheet in browser and check URL. You will example value like 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms that is your sheet id." Options="Enter Your Sheet Id=;Example Sheet Id=1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"/>
      </Params>
    </EndPoint>

    <EndPoint Name="tp_sheet">
      <Params>
        <Param Name="TabName" Desc="If you have multiple tabs on your spreadsheet then you can specify here. By default first tab is used if you do not enter." OptionsEndPoint="get_sheet_properties" OptionEndPointParameters="SpreadSheetId=[$SpreadSheetId$]" OptionsEndPointValueColumn="properties.title" OptionsEndPointLabelColumn="properties.title" Value=""/>
        <Param Name="valueRenderOption" Value="FORMATTED_VALUE" Options="FORMATTED_VALUE;UNFORMATTED_VALUE;FORMULA"/>
        <Param Name="valueInputOption" Value="USER_ENTERED" Options="USER_ENTERED;RAW" Optional="True"/>
        <Param Name="majorDimension" Value="DIMENSION_UNSPECIFIED" Options="DIMENSION_UNSPECIFIED;COLUMNS;ROWS"/>
        <Param Name="dateTimeRenderOption" Value="SERIAL_NUMBER" Options="SERIAL_NUMBER;FORMATTED_STRING"/>
      </Params>
    </EndPoint>
  </Template>

  <EndPoints>
    <EndPoint Name="read_sheet_data" Template="tp_sheet" Label="Read Sheet Data" Desc="Read google spreadsheet data from desired tab / cell location (i.e. start/end range)"
	HelpLink="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get"
	Url="/[$SpreadSheetId$]/values/'[$TabName$]'![$Range$]?valueRenderOption=[$valueRenderOption$]&amp;majorDimension=[$majorDimension$]&amp;dateTimeRenderOption=[$dateTimeRenderOption$]" Method="GET"
              Filter="$.values[*]"
        >
      <Params>
        <Param Name="Range" Required="True" Value="A1:GR"/>
        <Param Name="IncludeParentColumns" Type="Property" Value="False" />
        <Param Name="ArrayTransformType" Type="Property" Value="TransformColumnslessArray" Options="Auto Detect Columns Mode=TransformColumnslessArray;Use Custom Columns Mode=TransformSimpleTwoDimensionalArray" />
        <Param Name="ArrayTransEnableCustomColumns" Label="First Row Has Column Names" Type="Property" Value="True" Desc="Detect column names based on first row in supplied range." />
        <Param Name="ArrayTransCustomColumns" Type="Property" Value="Col1,Col2,Col3" Desc="If auto detection of columns is not possible then use ArrayTransformType=TransformSimpleTwoDimensionalArray and supply custom column list (comma seperated here) here (i.e. Col1,Col2,Col3)." VisibleIf="ArrayTransformType==TransformSimpleTwoDimensionalArray &amp; ArrayTransEnableCustomColumns" />
      </Params>
    </EndPoint>

    <EndPoint Name="clear_sheet_range" Template="tp_sheet" Label="Clear Sheet Values" Desc="Clears values from a spreadsheet. The caller must specify the spreadsheet ID and range. Only values are cleared -- all other properties of the cell (such as formatting, data validation, etc..) are kept."
	HelpLink="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/clear"
	Url="/[$SpreadSheetId$]/values/'[$TabName$]'![$Range$]:clear" Method="POST" >
      <Params>
        <Param Name="Range" Required="True" Value="A2:GR100"/>
      </Params>
      <OutputColumns>
        <Column Name="spreadsheetId" DataType="DT_WSTR" Length="100"/>
        <Column Name="clearedRange" DataType="DT_WSTR" Length="255"/>
      </OutputColumns>
    </EndPoint>

    <EndPoint Name="delete_sheet_rows_columns" Label="Delete Sheet Rows / Columns" Desc="Deletes rows / columns based on startIndex / endIndex range (Index is zero based). For example to delete First 5 rows specify startIndex=0 and endIndex=4"
    HelpLink="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate"
    Url="/[$SpreadSheetId$]:batchUpdate" Method="POST" ContentType="application/json">
      <Body>
        <![CDATA[{
  "requests": [
    {
      "deleteDimension": {
        "range": {
          "sheetId": [$TabId$],
          "dimension": "[$Dimension$]",
          "startIndex": [$StartIndex$],
          "endIndex": [$EndIndex$]
        }
      }
    }
  ],
  "includeSpreadsheetInResponse": false,
  "responseIncludeGridData": false
}]]>
      </Body>
      <Params>
        <Param Name="Dimension" Label="Range Type" Required="True" Value="ROWS" Desc="Specifies what would you like to delete (i.e. Rows or Columns)" Options="ROWS;COLUMNS"/>
        <Param Name="StartIndex" Label="Range Start Index (starts from 0)" Required="True" Value="0" Desc="Range Start Index (Zero based) for operation. For Column Operation translate Alpha value to zero based index. Example: Column D = Index 3. Column AA=26" />
        <Param Name="EndIndex" Label="Range End Index (starts from 0)" Required="True" Value="1" Desc="Range End Index (Zero based) for operation. For Column Operation translate Alpha value to zero based index. Example: Column D = Index 3. Column AA=26"  />
        <Param Name="TabId" Required="True" Value="0" Desc="Numeric ID of SpreadSheet Tab for which you like to delete rows/columns. For first tab its 0. You can find this ID from Sheet URL in browser. Get Number after edit#gid= (thats your Tab ID)" OptionsEndPoint="get_sheet_properties" OptionEndPointParameters="SpreadSheetId=[$SpreadSheetId$]" OptionsEndPointValueColumn="properties.sheetId" OptionsEndPointLabelColumn="properties.title" />
      </Params>
      <OutputColumns>
        <Column Name="spreadsheetId" DataType="DT_WSTR" Length="100"/>
      </OutputColumns>
    </EndPoint>

    <EndPoint Name="delete_sheet_tab" Label="Delete SpreadSheet Tab" Desc="Deletes specified tab from spreadsheet"
HelpLink="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate"
Url="/[$SpreadSheetId$]:batchUpdate" Method="POST" ContentType="application/json">
      <Body>
        <![CDATA[{
  "requests": [
    {
      "deleteSheet": {
          "sheetId": [$TabId$]
      }
    }
  ],
  "includeSpreadsheetInResponse": false,
  "responseIncludeGridData": false
}]]>
      </Body>
      <Params>
        <Param Name="TabId" Required="True" Value="0" Desc="Numeric ID of SpreadSheet Tab for which you like to delete rows/columns. For first tab its 0. You can find this ID from Sheet URL in browser. Get Number after edit#gid= (thats your Tab ID)" OptionsEndPoint="get_sheet_properties" OptionEndPointParameters="SpreadSheetId=[$SpreadSheetId$]" OptionsEndPointValueColumn="properties.sheetId" OptionsEndPointLabelColumn="properties.title" />
      </Params>
      <OutputColumns>
        <Column Name="spreadsheetId" DataType="DT_WSTR" Length="100"/>
      </OutputColumns>
    </EndPoint>

    <EndPoint Name="create_sheet_tab" Label="Create SpreadSheet Tab" Desc="Creates new tab for specified spreadsheet."
HelpLink="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate"
Url="/[$SpreadSheetId$]:batchUpdate" Method="POST" ContentType="application/json">
      <Body>
        <![CDATA[{
  "requests": [
    {
      "addSheet": {
        "properties": {
          "title": "[$NewTabName$]",
          "gridProperties": {
            "rowCount": [$InitialRowCount$],
            "columnCount": [$InitialColumnCount$]
          },
          "tabColor": {
            "red": [$TabColorRedValue$],
            "green": [$TabColorGreenValue$],
            "blue": [$TabColorBlueValue$]
          }
        }
      }
    }
  ]
}]]>
      </Body>
      <Params>
        <Param Name="NewTabName" Required="True" Value="My New Tab" Desc="Name for new Tab you like to add"  />
        <Param Name="InitialRowCount" Required="True" Value="100" Desc="Initial row count for new sheet"  />
        <Param Name="InitialColumnCount" Required="True" Value="27" Desc="Initial column count for new sheet"  />
        <Param Name="TabColorRedValue" Required="True" Value="0.0" Desc="Tab Color - Red Value for RGB"  />
        <Param Name="TabColorGreenValue" Required="True" Value="0.0" Desc="Tab Color - Green Value for RGB"  />
        <Param Name="TabColorBlueValue" Required="True" Value="0.0" Desc="Tab Color - Blue Value for RGB"  />
      </Params>
      <OutputColumns>
        <Column Name="spreadsheetId" DataType="DT_WSTR" Length="100"/>
      </OutputColumns>
    </EndPoint>

    <EndPoint Name="batch_update_request" Label="Execute Command(s)" Desc="Perform various operations available in online editor such as copy, paste, format, merge. Add multiple JSON command by adding comma after first command (e.g. requests : [ {..cmd1..} , {..cmd2..}, {..cmd3..} ] ) Refer to example requests on help link."
    HelpLink="https://developers.google.com/sheets/api/samples/formatting"
    Url="/[$SpreadSheetId$]:batchUpdate" Method="POST" ContentType="application/json" Body="[$Body$]" >

      <Params>
        <Param Name="Body" Editor="MultiLine" Label="Request Body" Required="True" Value="{
  &quot;requests&quot;: [
    {
      &quot;deleteDimension&quot;: {
        &quot;range&quot;: {
          &quot;sheetId&quot;: 0,
          &quot;dimension&quot;: &quot;ROWS&quot;,
          &quot;startIndex&quot;: 0,
          &quot;endIndex&quot;: 3
        }
      }
    }    
  ]
}"/>
        <Param Name="TabId" Required="True" Value="0" Desc="Numeric ID of SpreadSheet Tab for which you like to delete rows/columns. For first tab its 0. You can find this ID from Sheet URL in browser. Get Number after edit#gid= (thats your Tab ID)" OptionsEndPoint="get_sheet_properties" OptionEndPointParameters="SpreadSheetId=[$SpreadSheetId$]" OptionsEndPointValueColumn="properties.sheetId" OptionsEndPointLabelColumn="properties.title" />
      </Params>

      <OutputColumns>
        <Column Name="spreadsheetId" DataType="DT_WSTR" Length="100"/>
      </OutputColumns>
    </EndPoint>

    <EndPoint Name="update_sheet_data" Template="tp_sheet" Label="Update Sheet Data (Overwrite Cells)"
      Desc="Update data to spreadsheet on desired tab / cell location (i.e. start/end range)"
      HelpLink="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update"
      Url="/[$SpreadSheetId$]/values/'[$TabName$]'![$WriteRange$]?valueInputOption=[$valueInputOption$]"
      Method="PUT"
      MetaDetectionOrder="StaticDynamicVirtual"
      BatchSize="100000"
      >
      <Body>
        <![CDATA[{
  "majorDimension": "[$majorDimension$]",
  "values": {$rows$}
}]]>
      </Body>
      <Params>
        <Param Name="Range" Label="Range for Table Boundary (Including Header)" Desc="Enter Range to detect Table column names and datatypes. Example: enter A1:C if you have table of 3 columns starting from Cell A1" Required="True" Value="A1:GR"/>
        <Param Name="WriteRange" Label="Range for Data Cells" Desc="Cell Range which needs to be update. Example: Enter A2 if you like to start update from cell A2" Required="True" Value="A2:GR"/>

        <Param Name="JsonOutputFormat" Required="True" Label="Update Mode" Desc="This mode is used to include or exclude column names in first row. If you include column names then adjust for [Range for Data Cells]" Type="Property" Value="Array2D" Options="Update Values Only=Array2D;Update Values And Header Row=Array2DWithHeader;" />

        <Param ForMeta="True" Name="ArrayTransformType" Type="Property" Value="TransformColumnslessArray" Options="Auto Detect Columns Mode=TransformColumnslessArray;Use Custom Columns Mode=TransformSimpleTwoDimensionalArray" />
        <Param ForMeta="True" Name="ArrayTransEnableCustomColumns" Type="Property" Value="True" />
        <Param ForMeta="True" Name="ArrayTransCustomColumns" Type="Property" Value="Col1,Col2,Col3" Desc="If auto detection of columns is not possible then use ArrayTransformType=TransformSimpleTwoDimensionalArray and supply custom column list (comma seperated here) here (i.e. Col1,Col2,Col3)." VisibleIf="ArrayTransformType==TransformSimpleTwoDimensionalArray &amp; ArrayTransEnableCustomColumns" />

      </Params>
      <OutputColumns>
        <Column Name="spreadsheetId" DataType="DT_WSTR" Length="100"/>
        <Column Name="updatedRange" DataType="DT_WSTR" Length="255"/>
        <Column Name="updatedRows" DataType="DT_I4" />
        <Column Name="updatedColumns" DataType="DT_I4"/>
        <Column Name="updatedCells" DataType="DT_I4"/>
      </OutputColumns>
    </EndPoint>

    <EndPoint Name="insert_sheet_data" Template="tp_sheet" Label="Insert Sheet Data (Append at the end)"
      Desc="Append data to spreadsheet on desired tab. Data is appended after last non-empty row/cell location. You can supply cell offset to start scan to find non-empty cell."
      HelpLink="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append"
      Url="/[$SpreadSheetId$]/values/'[$TabName$]'![$Range$]:append?valueInputOption=[$valueInputOption$]"
      Method="POST" ContentType="application/json"
      MetaDetectionOrder="StaticDynamicVirtual"
      JsonRowFormat="Array2D"
      BatchSize="10000"
      >
      <Body>
        <![CDATA[{
  "majorDimension": "[$majorDimension$]",
  "values": {$rows$}
}]]>
      </Body>
      <Params>
        <Param Name="Range" Required="True" Value="A1:GR" Label="Start Range" Desc="This is the range in which next row location is detected based on first empty cell and row (Examples: A1, A1:GR, A1:GR200). This range is also used to detect metadata. Values are appended after the last row of the table."/>

        <Param ForMeta="True" Name="ArrayTransformType" Type="Property" Value="TransformColumnslessArray" Options="Auto Detect Columns Mode=TransformColumnslessArray;Use Custom Columns Mode=TransformSimpleTwoDimensionalArray" />
        <Param ForMeta="True" Name="ArrayTransEnableCustomColumns" Type="Property" Value="True" />
        <Param ForMeta="True" Name="ArrayTransCustomColumns" Type="Property" Value="Col1,Col2,Col3" Desc="If auto detection of columns is not possible then use ArrayTransformType=TransformSimpleTwoDimensionalArray and supply custom column list (comma seperated here) here (i.e. Col1,Col2,Col3)." VisibleIf="ArrayTransformType==TransformSimpleTwoDimensionalArray &amp; ArrayTransEnableCustomColumns" />
      </Params>
      <OutputColumns>
        <Column Name="spreadsheetId" DataType="DT_WSTR" Length="100"/>
        <Column Name="tableRange" DataType="DT_WSTR" Length="255"/>
        <Column Name="updates.updatedRange" Label="updatedRange" DataType="DT_WSTR" Length="255"/>
        <Column Name="updates.updatedRows"  Label="updatedRows" DataType="DT_I4" />
        <Column Name="updates.updatedColumns"  Label="updatedColumns" DataType="DT_I4"/>
        <Column Name="updates.updatedCells"  Label="updatedCells" DataType="DT_I4"/>
      </OutputColumns>
    </EndPoint>

    <EndPoint Name="get_sheet_properties" Label="Get Sheet Properties / Tab information" Desc="Read read sheet properties (e.g. list of tabs on sheet and its properties)" HelpLink="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/get" Url="/[$SpreadSheetId$]" Method="GET"
              Filter="$.sheets[*]">
      <Params>
        <Param Name="SpreadSheetId" Required="True" Value="" Options="Enter SpreadSheet Id=;Example SpreadSheet Id=1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"/>
        <Param Name="IncludeParentColumns" Type="Property" Value="False" />
      </Params>
    </EndPoint>
  </EndPoints>

  <Tables>
    <Table Name="Sheets" Desc="Read, insert, update sheet data for a specified SpreadSheetId and Tab"
           LookupEndPoint="read_sheet_data"
           SelectEndPoint="read_sheet_data"
           InsertEndPoint="insert_sheet_data"
           UpdateEndPoint="update_sheet_data"
           />
    <Table Name="SheetDetails" Desc="Read sheet information (i.e. tabs on a sheet) for a specified SpreadSheetId"
           LookupEndPoint="get_sheet_properties"
           SelectEndPoint="get_sheet_properties" />

    <Table Name="[$parent.properties.title$]"
           Expand="True"
           LookupEndPoint="read_sheet_data"
           SelectEndPoint="read_sheet_data"
           InsertEndPoint="insert_sheet_data"
           UpdateEndPoint="update_sheet_data"
           DataEndPoint="get_sheet_properties" >
      <Params>

        <Param Name="TabName" Value="[$parent.properties.title$]" Hidden="True"/>
      </Params>
    </Table>

  </Tables>
  
  <Examples>
    <Example Group="ODBC" Label="Query from default Spreadsheet" Code="SELECT * FROM [Sheet1]" Desc="Gets data from Tab name Sheet1 from SpreadSheet Id defined in the connection" ></Example>
    <Example Group="ODBC" Label="Query from User defined Spreadsheet" Code="SELECT * FROM [Class Data] WITH(SpreadSheetId='1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms')" Desc="Gets data from Tab name 'Class Data' from user defined SpreadSheet Id" ></Example>

    <Example Group="ODBC" Label="Query from custom cell range" Desc="In this example we query Tab name 'Class Data' and we are reading Range 'A4:GR'.">
      <Code>
        <![CDATA[SELECT * FROM [Class Data]
WITH(
	  Range='A4:GR'  --cell range you like to query
	, ArrayTransEnableCustomColumns='False' --do not treat first row in range as Column Names
	, SpreadSheetId='1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms'  --enter sheet id you like to query. Comment this if you like to use default ID defined in the connection
)]]>
      </Code>
    </Example>
    <Example Group="ODBC" Label="Query from custom cell range" Desc="In this example we query Tab name 'Class Data' and we are reading Range 'A4:GR'.">
      <Code>
        <![CDATA[
UPDATE [Sheet1]
SET Col1='data-1', Col2=100, Col3='2020-01-31' --column names are ignored. Values are sent in the same order you supply and writtern to start cell specified by WriteRange
WITH(
	, WriteRange='G9' --start writing from here
	, SpreadSheetId='1az2H8ZYk7BvjddVTqPR-LfDjX9IRpIpjCDpFPe9EzkU' --comment this to use default Sheet Id from connection setting
)        
        ]]>
      </Code>
    </Example>
    <Example Group="ODBC" Label="Update Multiple Rows in Sheet from CSV file" Desc="In this example we query CSV file as Source (Using ZapyySys CSV ODBC Driver) and updating Google Sheet in BULK.">
      <Code>
        <![CDATA[
UPDATE [Sheet1]
SOURCE(
 'ODBC',  --driver type ODBC | MSSQL | OLEDB
 'Driver={ZappySys CSV Driver};', --connection string for driver
 'SELECT * FROM $ WITH (SRC=''c:\data.csv'') ' --sql query for source data
)
WITH(
  WriteRange='G9', SpreadSheetId='1az2H8ZYk7BvjddVTqPR-LfDjX9IRpIpjCDpFPe9EzkU' --comment this to use default Sheet Id from connection setting 
)
        ]]>
      </Code>
    </Example>
    <Example Group="ODBC" Label="Update Values Vertically (Column Mode)" Desc="In this example we will write value as columns rather than row.">
      <Code>
        <![CDATA[
UPDATE [Sheet1]
SET Col1='Jan',Col2='Feb',Col3='Mar'
WITH(
  WriteRange='G9', 
  SpreadSheetId='1az2H8ZYk7BvjddVTqPR-LfDjX9IRpIpjCDpFPe9EzkU', --comment this to use default Sheet Id from connection setting
  MajorDimension='COLUMNS' --write values vertical rather horizontal
)
        ]]>
      </Code>
    </Example>    
    <Example Group="ODBC" Label="Insert Data in Sheet1" Code="INSERT INTO &quot;Sheet1&quot;(&quot;MyStringCol&quot;, &quot;MyIntegerCol&quot;, &quot;MyDateCol&quot;, &quot;MyDecimalCol&quot;) VALUES('AAA',100,'2020-01-01',150.33)" Desc="Insert row to tab name Sheet1 in SheetId defined in connection" ></Example>
    <Example Group="ODBC" Label="Insert Multiple Rows in Sheet1 from CSV file" Desc="In this example we query CSV file as Source (Using ZapyySys CSV ODBC Driver) and updating Google Sheet in BULK.">
      <Code>
        <![CDATA[
INSERT INTO [Sheet1]
SOURCE(
 'ODBC', --driver type ODBC | MSSQL | OLEDB
 'Driver={ZappySys CSV Driver};', --connection string for driver
 'SELECT * FROM $ WITH (SRC=''c:\temp\dump.txt'') ' --sql query for source data
)
WITH(
  Range='G9', SpreadSheetId='1az2H8ZYk7BvjddVTqPR-LfDjX9IRpIpjCDpFPe9EzkU' --comment this to use default Sheet Id from connection setting 
)
        ]]>
      </Code>
    </Example>  
    <Example Group="ODBC" Label="Write Values Vertically (Column Mode)" Desc="In this example we will write value as columns rather than row.">
      <Code>
        <![CDATA[
INSERT INTO [Sheet1](Col1,Col2,Col3) 
VALUES('Jan','Feb','Mar')
WITH(
  Range='G9', --starting cell to write data
  SpreadSheetId='1az2H8ZYk7BvjddVTqPR-LfDjX9IRpIpjCDpFPe9EzkU', --comment this to use default Sheet Id from connection setting
  MajorDimension='COLUMNS' --write values vertical rather horizontal
)
        ]]>
      </Code>
    </Example> 
    <Example Group="ODBC" Label="Execute Action (i.e. Delete Rows / Columns)" Desc="This example shows how to execute various commands for sheet (i.e. copy, paste, formatting, delete, merge etc). In this example we are executing delete comamnds (i.e. deleteDimension commands). Notice we called same command twice becuase we want to delete two ranges (index 10-20 and 50-60). You can execute any valid command available by Google Sheets API. Here are some good examples of formatting commands https://developers.google.com/sheets/api/samples/formatting">
      <Code>
        <![CDATA[
SELECT * FROM batch_update_request
WITH(
	  Body='{
  "requests": [
    {
      "deleteDimension": {
        "range": {
          "sheetId": 0,
          "dimension": "ROWS",
          "startIndex": 10,
          "endIndex": 20
        }
      }
    } ,
    
   {
      "deleteDimension": {
        "range": {
          "sheetId": 0,
          "dimension": "ROWS",
          "startIndex": 50,
          "endIndex": 60
        }
      }
    }
    
  ]
}'
	, TabId='0' -- tab internal id (use UI to get this). 0 means first tab. Or check URL in browser and see at the end of URL #gid=xxxxxxx where xxxxxx is your tab id
	, SpreadSheetId='1az2H8ZYk7BvjddVTqPR-LfDjX9IRpIpjCDpFPe9EzkU' --comment this to use default Sheet Id from connection setting
)
        ]]>
      </Code>
    </Example>   
  
  </Examples>
</ApiConfig>