{salesforcer} is an R package that connects to Salesforce Platform APIs using tidy principles. The package implements actions from the REST, SOAP, Bulk 1.0, Bulk 2.0, Reports and Dashboards, and Metadata APIs.
Package features include:
sf_auth()
)sf_query()
sf_list_reports()
, sf_create_report()
,
sf_run_report()
, and moresf_describe_objects()
,
sf_create_metadata()
, sf_update_metadata()
,
and morerforcecom.login()
,
rforcecom.getObjectDescription()
,
rforcecom.query()
, rforcecom.create()
sf_user_info()
,
sf_server_timestamp()
, sf_list_objects()
)sf_find_duplicates()
,
sf_find_duplicates_by_id()
), merging records
(sf_merge()
), and converting leads
(sf_convert_lead()
)sf_undelete()
) or delete from the Recycle Bin
(sf_empty_recycle_bin()
) and list ids of records deleted
(sf_get_deleted()
) or updated
(sf_get_updated()
) within a specific timeframe# install the current CRAN version (1.0.1)
install.packages("salesforcer")
# or get the development version on GitHub
# install.packages("remotes")
::install_github("StevenMMortimer/salesforcer") remotes
If you encounter an issue while using this package, please file a minimal reproducible example on GitHub.
The README below outlines the basic package functionality. For more information please feel free to browse the {salesforcer} website at https://stevenmmortimer.github.io/salesforcer/ which contains the following vignettes:
First, load the {salesforcer} package and log in. There are two ways to authenticate:
NOTE: Beginning February 1, 2022 authentication via
a username and password will not work in most Salesforce organizations.
On that date Salesforce will begin requiring customers to enable
multi-factor authentication (MFA). The function sf_auth()
will return the error message:
INVALID_LOGIN: Invalid username, password, security token; or user locked out.
It has always been recommended to use OAuth 2.0 so that passwords do not have to be shared or embedded within scripts. For more information on how OAuth 2.0 works within the {salesforcer} package, please read the Getting Started vignette.
library(dplyr, warn.conflicts = FALSE)
library(salesforcer)
# Using OAuth 2.0 authentication
sf_auth()
After logging in with sf_auth()
, you can check your
connectivity by looking at the information returned about the current
user. It should be information about you!
# pull down information of person logged in
# it's a simple easy call to get started
# and confirm a connection to the APIs
<- sf_user_info()
user_info sprintf("Organization Id: %s", user_info$organizationId)
#> [1] "Organization Id: 00D6A0000003dN3UAI"
sprintf("User Id: %s", user_info$userId)
#> [1] "User Id: 0056A000000MPRjQAO"
Salesforce has objects and those objects contain records. One default object is the “Contact” object. This example shows how to create two records in the Contact object.
<- 2
n <- tibble(FirstName = rep("Test", n),
new_contacts LastName = paste0("Contact-Create-", 1:n))
<- sf_create(new_contacts, object_name = "Contact")
created_records
created_records#> # A tibble: 2 × 2
#> id success
#> <chr> <lgl>
#> 1 0033s00001BXHqaAAH TRUE
#> 2 0033s00001BXHqbAAH TRUE
Salesforce has proprietary form of SQL called SOQL (Salesforce Object Query Language). SOQL is a powerful tool that allows you to return the attributes of records on almost any object in Salesforce including Accounts, Contacts, Tasks, Opportunities, even Attachments! Below is an example where we grab the data we just created including Account object information for which the Contact record is associated with.
<- sprintf("SELECT Id,
my_soql Account.Name,
FirstName,
LastName
FROM Contact
WHERE Id in ('%s')",
paste0(created_records$id , collapse = "','"))
<- sf_query(my_soql)
queried_records
queried_records#> # A tibble: 2 × 3
#> Id FirstName LastName
#> <chr> <chr> <chr>
#> 1 0033s00001BXHqaAAH Test Contact-Create-1
#> 2 0033s00001BXHqbAAH Test Contact-Create-2
NOTE: In the example above, you’ll notice that the
"Account.Name"
column does not appear in the results. This
is because the SOAP and REST APIs only return an empty Account object
for the record if there is no relationship to an account (see
#78).
There is no reliable way to extract and rebuild the empty columns based
on the query string. If there were Account information, an additional
column titled "Account.Name"
would appear in the results.
Note, that the Bulk 1.0 and Bulk 2.0 APIs will return
"Account.Name"
as a column of all NA
values
for this query because they return results differently.
After creating records you can update them using
sf_update()
. Updating a record requires you to pass the
Salesforce Id
of the record. Salesforce creates a unique
18-character identifier on each record and uses that to know which
record to attach the update information you provide. Simply include a
field or column in your update dataset called “Id” and the information
will be matched. Here is an example where we update each of the records
we created earlier with a new first name called “TestTest”.
# Update some of those records
<- queried_records %>%
queried_records mutate(FirstName = "TestTest")
<- sf_update(queried_records, object_name = "Contact")
updated_records
updated_records#> # A tibble: 2 × 2
#> id success
#> <chr> <lgl>
#> 1 0033s00001BXHqaAAH TRUE
#> 2 0033s00001BXHqbAAH TRUE
For really large operations (inserts, updates, upserts, deletes, and
queries) Salesforce provides the Bulk
1.0 and Bulk
2.0 APIs. In order to use the Bulk APIs in {salesforcer} you can
just add api_type = "Bulk 1.0"
or
api_type = "Bulk 2.0"
to your functions and the operation
will be executed using the Bulk APIs. It’s that simple.
The benefits of using the Bulk API for larger datasets is that the operation will reduce the number of individual API calls (organization usually have a limit on total calls) and batching the requests in bulk is usually quicker than running thousands of individuals calls when your data is large. Note: the Bulk 2.0 API does NOT guarantee the order of the data submitted is preserved in the output. This means that you must join on other data columns to match up the Ids that are returned in the output with the data you submitted. For this reason, Bulk 2.0 may not be a good solution for creating, updating, or upserting records where you need to keep track of the created Ids. The Bulk 2.0 API would be fine for deleting records where you only need to know which Ids were successfully deleted.
# create contacts using the Bulk API
<- 2
n <- tibble(FirstName = rep("Test", n),
new_contacts LastName = paste0("Contact-Create-", 1:n))
<- sf_create(new_contacts, "Contact", api_type = "Bulk 1.0")
created_records
created_records#> # A tibble: 2 × 4
#> Id Success Created Error
#> <chr> <lgl> <lgl> <lgl>
#> 1 0033s00001BXHqfAAH TRUE TRUE NA
#> 2 0033s00001BXHqgAAH TRUE TRUE NA
# query large recordsets using the Bulk API
<- sprintf("SELECT Id,
my_soql FirstName,
LastName
FROM Contact
WHERE Id in ('%s')",
paste0(created_records$Id , collapse = "','"))
<- sf_query(my_soql, "Contact", api_type = "Bulk 1.0")
queried_records
queried_records#> # A tibble: 2 × 3
#> Id FirstName LastName
#> <chr> <chr> <chr>
#> 1 0033s00001BXHqfAAH Test Contact-Create-1
#> 2 0033s00001BXHqgAAH Test Contact-Create-2
# delete these records using the Bulk 2.0 API
<- sf_delete(queried_records$Id, "Contact", api_type = "Bulk 2.0")
deleted_records
deleted_records#> # A tibble: 2 × 4
#> Id sf__Id sf__Created sf__Error
#> <chr> <chr> <lgl> <lgl>
#> 1 0033s00001BXHqfAAH 0033s00001BXHqfAAH FALSE NA
#> 2 0033s00001BXHqgAAH 0033s00001BXHqgAAH FALSE NA
Salesforce is a very flexible platform in that it provides the Metadata
API for users to create, read, update and delete their entire
Salesforce environment from objects to page layouts and more. This makes
it very easy to programmatically setup and teardown the Salesforce
environment. One common use case for the Metadata API is retrieving
information about an object (fields, permissions, etc.). You can use the
sf_read_metadata()
function to return a list of objects and
their metadata. In the example below we retrieve the metadata for the
Account and Contact objects. Note that the metadata_type
argument is “CustomObject”. Standard Objects are an implementation of
CustomObjects, so they are returned using that metadata type.
<- sf_read_metadata(metadata_type='CustomObject',
read_obj_result object_names=c('Account', 'Contact'))
1]][c('fullName', 'label', 'sharingModel', 'enableHistory')]
read_obj_result[[#> $fullName
#> [1] "Account"
#>
#> $label
#> [1] "Account"
#>
#> $sharingModel
#> [1] "ReadWrite"
#>
#> $enableHistory
#> [1] "false"
<- head(which(names(read_obj_result[[1]]) == 'fields'), 2)
first_two_fields_idx # show the first two returned fields of the Account object
1]][first_two_fields_idx]
read_obj_result[[#> $fields
#> $fields$fullName
#> [1] "AccountNumber"
#>
#> $fields$trackFeedHistory
#> [1] "false"
#>
#>
#> $fields
#> $fields$fullName
#> [1] "AccountSource"
#>
#> $fields$trackFeedHistory
#> [1] "false"
#>
#> $fields$type
#> [1] "Picklist"
The data is returned as a list because object definitions are highly
nested representations. You may notice that we are missing some really
specific details, such as, the picklist values of a field with type
“Picklist”. You can get that information using
sf_describe_object_fields()
. Here is an example using
sf_describe_object_fields()
where we get a
tbl_df
with one row for each field on the Account
object:
<- sf_describe_object_fields('Account')
acct_fields %>% select(name, label, length, soapType, type)
acct_fields #> # A tibble: 68 × 5
#> name label length soapType type
#> <chr> <chr> <chr> <chr> <chr>
#> 1 Id Account ID 18 tns:ID id
#> 2 IsDeleted Deleted 0 xsd:boolean boolean
#> 3 MasterRecordId Master Record ID 18 tns:ID reference
#> 4 Name Account Name 255 xsd:string string
#> 5 Type Account Type 255 xsd:string picklist
#> # … with 63 more rows
# show the picklist selection options for the Account Type field
%>%
acct_fields filter(label == "Account Type") %>%
$picklistValues
.#> [[1]]
#> # A tibble: 7 × 4
#> active defaultValue label value
#> <chr> <chr> <chr> <chr>
#> 1 true false Prospect Prospect
#> 2 true false Customer - Direct Customer - Direct
#> 3 true false Customer - Channel Customer - Channel
#> 4 true false Channel Partner / Reseller Channel Partner / Reseller
#> 5 true false Installation Partner Installation Partner
#> # … with 2 more rows
Future APIs to support (roughly in priority order):
This application uses other open source software components. The authentication components are mostly verbatim copies of the routines established in the {googlesheets} package (https://github.com/jennybc/googlesheets). Methods are inspired by the {RForcecom} package (https://github.com/hiratake55/RForcecom). We acknowledge and are grateful to these developers for their contributions to open source.
Salesforce provides client libraries and examples in many programming languages (Java, Python, Ruby, and PhP) but unfortunately R is not a supported language. However, most all operations supported by the Salesforce APIs are available via this package. This package makes requests best formatted to match what the APIs require as input. This articulation is not perfect and continued progress will be made to add and improve functionality. For details on formatting, attributes, and methods please refer to Salesforce’s documentation as they are explained better there. More information is also available on the {salesforcer} pkgdown website at https://stevenmmortimer.github.io/salesforcer/.
Get supported salesforcer with the Tidelift Subscription
Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.