Skip to content

Document how to use sf_upsert() with Salesforce objects containing lookup fields with external IDs #136

@mccarthy-m-g

Description

@mccarthy-m-g

Issue description

Thanks for this package---I'm using it for a work project and it's been a great benefit to my team!

We ran into some difficulties where we would get a "MALFORMED_ID" status code when using sf_upsert() with Salesforce objects containing lookup fields that contain external IDs. We were able to resolve this after contacting Salesforce's technical support, but the solution doesn't seem to be documented anywhere, and it's something most users would be unlikely to discover on their own. The solution is also API-specific; we were able to solve it for the REST API, but not the SOAP API.

I'm mainly filing this issue to document this somewhere, but if the solution is generalizable maybe it's worth turning into a feature in salesforcer?

REST API Solution

I can't (or am not not sure how to) share a reprex for this since it's private data, but I'll describe the solution we found, which will hopefully be generalizable and help others who run into this issue. Here's the response from technical support to our issue:

Yes, it is possible to use external IDs in lookup fields on custom objects in Salesforce. This approach can simplify your data integration processes by avoiding the need to maintain a separate table linking external IDs with Salesforce IDs.

Create External ID Fields: Ensure that your custom objects have fields marked as External IDs. These fields should be unique and can be used to match records during the upsert operation.

API Integration: When performing an upsert operation via API, you can reference the external ID fields directly in your lookup fields. This allows Salesforce to match and relate records based on the external ID values.

Assume you have two custom objects, CustomObjectA and CustomObjectB. CustomObjectB has a lookup field to CustomObjectA. Here’s how you can structure your API request (JSON):

{
  "CustomObjectB__c": {
    "External_Id__c": "external_id_value",
    "LookupField__r": {
      "External_Id__c": "related_external_id_value"
    }
  }
}

For the MALFORMED ID error typically occurs when the ID format is incorrect so verify that your API request correctly maps the external ID fields and that the values provided are valid and unique. Please ensure that the external IDs are unique across all records and correctly formatted according to Salesforce external ID requirements.

There are two important things of note here:

  • The lookup field column needs to have a __r suffix, not a __c suffix.
  • The values in the lookup field have a key and a value.

For the latter point, the solution is to transform the data in this column from raw values into named lists. Here's an example of how you might make that transformation. Note that setting verbose = TRUE in sf_upsert() was helpful for troubleshooting the transformation I needed to make, and getting it to match what technical support shared above.

dplyr::mutate(
  my_data,
  LookupField__r = unlist(
    purrr::map(LookupField__r, \(.x) list(External_Id__c = .x)),
    recursive = FALSE
  )
)

After making this transformation, I was able to successfully sf_upsert() with the REST API.

SOAP API

As noted, the above solution is specific to the REST API. If you try this with the SOAP API you will get the following error: "Missing entity type information. sObject requires a separate 'type' field be sent."

We were able to get another example from technical support for this, but the relationship between their example and the verbose output of sf_upsert() was less obvious, so I wasn't sure how to proceed. Here's technical support's response:

The error you are experiencing ("Missing entity type information. sObject requires a separate 'type' field be sent.") is because in the SOAP API, Salesforce demands that you explicitly declare the type of each object being referenced.

In your case, the lookup field that uses an external ID needs to include both the type of the referenced object and the external ID.

Example:-
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com">
soapenv:Header
urn:SessionHeader
urn:sessionId[YOUR_SESSION_ID]</urn:sessionId>
</urn:SessionHeader>
</soapenv:Header>
soapenv:Body
urn:upsert
urn:externalIDFieldNameExternal_Id__c</urn:externalIDFieldName>
urn:sObjects

urn:typeCustomObject__c</urn:type>
urn:NameTest Record</urn:Name>
urn:LookupField__r

urn:typeReferencedObject__c</urn:type>
urn:External_Id__crelated_object_external_id</urn:External_Id__c>
</urn:LookupField__r>
urn:Custom_Field__cValue</urn:Custom_Field__c>
</urn:sObjects>
</urn:upsert>
</soapenv:Body>
</soapenv:Envelope>

Always include the urn:type field for both the main object and the referenced object. Also verify that LookupField__r and External_Id__c are the correct API names for the fields.

Metadata

Metadata

Labels

enhance documentationResolve by editing documentationquestion or helpClarification or help may suffice to resolve

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions