function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
LeadFootLeadFoot 

Patch / payload question in Google Script (REST API)

I'm attempting to use a Google Apps spreadsheet to display SF data, collect user input, and update SF records. It obtains data just fine, and executes the code that should update the SF data without error, but nothing changes in SF. Here's the code block that tries to update data in SF. Google's UrlFetchApp.fetch function does not accept the 'PATCH' method, so I use X-HTTP-Method-Override in the header. Any insight into why this silently fails? All of the authorization, SF Object Id, etc. is correct and works in a previous code block in the same function (not shown). 

 

My suspicion is that the X-HTTP-Method-Override doesn't work - it appears to behave as if the 'GET' method was specified, and if I add an explicit 'method : DELETE' to the options, it will delete the account record in SF even though X-HTTP-Method-Override still specifies 'PATCH".

 

  var queryUrl = "https://na9.salesforce.com/services/data/v20.0/sobjects/Account/001E000000L8FDGIA3";
  var payload = "{\"BillingState\":\"MO\"}";
  
  var headers = 
      { 
        "Content-type" : "application/json",
        "Authorization" : "OAuth "+accessToken,
        "X-HTTP-Method-Override" : "PATCH"
      };
    
  var options =
      {
        "headers" : headers,  
        "payload" : payload
      };
  
  var response = UrlFetchApp.fetch(queryUrl, options);

 

 

 

Best Answer chosen by Admin (Salesforce Developers) 
SuperfellSuperfell

The way to override the http method is to add a URL paramater of _HttpMethod=PATCH, and not via a header.

All Answers

SuperfellSuperfell

The way to override the http method is to add a URL paramater of _HttpMethod=PATCH, and not via a header.

This was selected as the best answer
LeadFootLeadFoot

Thanks - works perfectly! It turns out that you also need to specify 'method : "POST"' in the options, presumably since GET (the default) doesn't accept a payload.

 

By the way - I spent a LOT of time looking for the solution for this before I posted. Is this in some secret documentation accessible only to the secret inner circle, or have my search skills atrophied to the point of irrelevance? In either case, thanks again for solving this. Now I can use Google spreadsheets as data entry/edit forms. Happiness. For anyone else struggling with this, here's the working code snippet (authorization is handled elsewhere):

 

  var queryUrl = instanceUrl + "/services/data/v25.0/sobjects/Account/"+MyId+"?_HttpMethod=PATCH";

  var payload = "{\"BillingState\":\"MO\"}";

  var headers = 
      { 
        "Authorization" : "OAuth "+accessToken,
        "Content-type" : "application/json"
      };
    
  var options =
      {
        headers : headers,
        method : "POST",
        payload : payload
      };
  
  var response = UrlFetchApp.fetch(queryUrl, options);

 

LeadFootLeadFoot

What an idiot I am! Since I was trying to update a record, I foolishly looked in the Update example:

 

http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_update_fields.htm

 

I'm glad you know where the answers are. I spent WAY too long thinking it was a Google problem since the original issue was that Google doesn't allow PATCH as a method.

DvlDvl

Hi there perhaps you can assist me, I am trying to do u much simplified version ofwhat you are doing,

 

Simply importing data from salesforce to a google spreadsheet live, no need for the reverse.

 

Any advice,? I have attemoted to do this using the =importhtml functioning google spreadsheet

 

Regards

LeadFootLeadFoot

This really should be a new topic....

 

I do it using two functions that I shamelessly appropriated from somewhere (Edit: here, actually). The first runs when the spreadsheett is opened, and it handles all of the security stuff:

 

function onOpen() {


  // Read OAuth consumer key / secret of this client app from script properties, 
  // which can be issued from Salesforce's remote access setting in advance.
  
  // Keys need to be set once, then can be commented out.
  //ScriptProperties.setProperty('sfConsumerKey','<your key>');
  //ScriptProperties.setProperty('sfConsumerSecret','<your secret>');
  

  var sfConsumerKey = ScriptProperties.getProperty("sfConsumerKey");
  var sfConsumerSecret = ScriptProperties.getProperty("sfConsumerSecret");

 
  if (!sfConsumerKey || !sfConsumerSecret) {
    Browser.msgBox("Register Salesforce OAuth Consumer Key and Secret in Script Properties");
    return;
  }

  // Register new OAuth service, named "salesforce"
  // For OAuth endpoint information, see help doc in Salesforce.
  // https://na7.salesforce.com/help/doc/en/remoteaccess_oauth_1_flows.htm
  var oauth = UrlFetchApp.addOAuthService("salesforce");
  oauth.setAccessTokenUrl("https://login.salesforce.com/_nc_external/system/security/oauth/AccessTokenHandler");
  oauth.setRequestTokenUrl("https://login.salesforce.com/_nc_external/system/security/oauth/RequestTokenHandler");
  oauth.setAuthorizationUrl("https://login.salesforce.com/setup/secur/RemoteAccessAuthorizationPage.apexp?oauth_consumer_key="+encodeURIComponent(sfConsumerKey));
  oauth.setConsumerKey(sfConsumerKey);
  oauth.setConsumerSecret(sfConsumerSecret);

  // Convert OAuth1 access token to Salesforce sessionId (mostly equivalent to OAuth2 access token)
  var sessionLoginUrl = "https://login.salesforce.com/services/OAuth/u/21.0";
  var options = {
    method : "POST", 
    oAuthServiceName : "salesforce", 
    oAuthUseToken : "always" 
  };
  var result = UrlFetchApp.fetch(sessionLoginUrl, options);
  var txt = result.getContentText();
  var accessToken = txt.match(/<sessionId>([^<]+)/)[1];
  var serverUrl = txt.match(/<serverUrl>([^<]+)/)[1];
  var instanceUrl = serverUrl.match(/^https?:\/\/[^\/]+/)[0];
  ScriptProperties.setProperty('accessToken',accessToken);
  ScriptProperties.setProperty('serverUrl',serverUrl);
  ScriptProperties.setProperty('instanceUrl',instanceUrl);
    
};

 The second function is invoked by the user and in this case reads some SalesForce data and populates the spreadsheet with it:

 

function GetProjectData() {
  var accessToken = ScriptProperties.getProperty("accessToken");
  var serverUrl = ScriptProperties.getProperty("serverUrl");
  var instanceUrl = ScriptProperties.getProperty("instanceUrl");

  if (!accessToken || !serverUrl || !instanceUrl) {
    Browser.msgBox("Reload spreadsheet to refresh authorization");
    return;
  }
  
  // Get open projects.
  var fields = "Id,Name,Opportunity__r.Id,Opportunity__r.Name,Opportunity__r.Account.Name,Opportunity__r.MEPOL_Task_Number__c"; 
  var soql = "SELECT "+fields+" FROM Line_Item__c where Opportunity__r.stagename='Project Open' order by Opportunity__r.Account.Name,Opportunity__r.Name,Name";
  var queryUrl = instanceUrl + "/services/data/v21.0/query?q="+encodeURIComponent(soql);
  var options = {
    method : "GET",
    headers : { "Authorization" : "OAuth "+accessToken }
  };
  var response = UrlFetchApp.fetch(queryUrl, options);
  var queryResult = Utilities.jsonParse(response.getContentText());

  // Clear and set up destination page
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("OpenProjects");
  sheet.clear();
  sheet.setFrozenRows(1);
  var cell = sheet.getRange('a1');

  // Populate project data
  queryResult.records.forEach(function(record, i) {
    cell.offset(i+1, 0).setValue(record["Opportunity__r"].Account.Name + ": " + record["Opportunity__r"].Name);
    cell.offset(i+1, 1).setValue("  --" + record["Opportunity__r"].MEPOL_Task_Number__c + " " +record["Name"]);
    cell.offset(i+1, 2).setValue(record["Id"]);
    cell.offset(i+1, 3).setValue(record["Opportunity__r"].Id);
    cell.offset(i+1, 4).setValue(record["Opportunity__r"].Account.Name + ": " + record["Opportunity__r"].Name);
  });

  // Get users
  var fields = "Id,Name,IsActive";
  var soql = "SELECT "+fields+" FROM User order by Name";
  var queryUrl = instanceUrl + "/services/data/v21.0/query?q="+encodeURIComponent(soql);
  var options = {
    method : "GET",
    headers : { "Authorization" : "OAuth "+accessToken }
  };
  var response = UrlFetchApp.fetch(queryUrl, options);
  var queryResult = Utilities.jsonParse(response.getContentText());

  // Populate user data
  queryResult.records.forEach(function(record, i) {
    cell.offset(i+1, 7).setValue(record["Name"]);
    cell.offset(i+1, 8).setValue(record["Id"]);
  });

}

 If you're like me, you'll spend a fair amount of time dealing with missing or incoherent error messages before you get it to work. I actually test my SOQL queries in a PHP script first - I think there's also a script explorer tool out there somewhere for that purpose.