The {salesforcer} package is the R implementation of the Salesforce platform APIs. I created the package to build upon the functionality provided in the {RForcecom} package by including calls from more APIs (REST, SOAP, Bulk, and Metadata) along with following more tidy data principles when wrangling data from the APIs.
View source code on GitHub at: https://github.com/StevenMMortimer/salesforcer
This R package supports a variety of Salesforce API operations and more will be added. 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 version from CRAN
install.packages("salesforcer")
# or get the development version on GitHub
# install.packages("devtools")
devtools::install_github("StevenMMortimer/salesforcer")
The {salesforcer} package provides two methods to authenticate:
It is recommended to use OAuth 2.0 so that passwords do not have to be shared or embedded within scripts. User credentials will be stored in locally cached file entitled “.httr-oauth-salesforcer” in the current working directory. These credentials will be passed to the API for each call and refreshed if necessary.
library(tidyverse)
library(salesforcer)
# Using OAuth 2.0 authentication
sf_auth()
# Using Basic Username-Password authentication
sf_auth(username = "test@gmail.com",
password = "{PASSWORD_HERE}",
security_token = "{SECURITY_TOKEN_HERE}")
The {salesforcer} package is unique in that it provides a common interface to
all of the REST, SOAP, and Bulk APIs. If you would like to create a set of records,
then you would use the same R function, sf_create()
, in the same way and just
change the function argument called api_type
.
# create a dataset of 2 new contacts to create
n <- 2
new_contacts <- tibble(FirstName = rep("Test", n),
LastName = paste0("Contact-Create-", 1:n))
# using the REST API
rest_records <- sf_create(new_contacts, object_name="Contact", api_type="REST")
rest_records
#> # A tibble: 2 x 2
#> id success
#> <chr> <lgl>
#> 1 0033s000013YPkFAAW TRUE
#> 2 0033s000013YPkGAAW TRUE
# using the REST API
soap_records <- sf_create(new_contacts, object_name="Contact", api_type="SOAP")
soap_records
#> # A tibble: 2 x 2
#> id success
#> <chr> <lgl>
#> 1 0033s000013YPiAAAW TRUE
#> 2 0033s000013YPiBAAW TRUE
# using the Bulk 1.0 API
bulk_records <- sf_create(new_contacts, object_name="Contact", api_type="Bulk 1.0")
bulk_records
#> # A tibble: 2 x 4
#> Id Success Created Error
#> <chr> <lgl> <lgl> <lgl>
#> 1 0033s000013YPkKAAW TRUE TRUE NA
#> 2 0033s000013YPkLAAW TRUE TRUE NA
This form is especially useful when switching between the REST and Bulk APIs where if you start dealing with larger and larger datasets, then you can switch code easily to the Bulk API. Note: The Bulk 2.0 API does NOT guarantee the order of the data submitted is preserved in the output that is returned. 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.
The {salesforcer} package also supports the creating, editing, and deleting of
Salesforce metadata. Metadata refers to the configuration of “objects” within Salesforce.
An object could be a standard or new type of record. For example, the Metadata API can
configuring which fields are captured on an Account or whether they should be displayed on the page.
Even more complex configurations, such as, workflows and triggers can be configured
using the Metadata API. The support for the Metadata API in {salesforcer} is experimental,
so please use at your own risk and consult the appropriate Salesforce resources
when trying to use. 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.
read_obj_result <- sf_read_metadata(metadata_type='CustomObject',
object_names=c('Account', 'Contact'))
read_obj_result[[1]][c('fullName', 'label', 'sharingModel', 'enableHistory')]
#> $fullName
#> [1] "Account"
#>
#> $label
#> [1] "Account"
#>
#> $sharingModel
#> [1] "ReadWrite"
#>
#> $enableHistory
#> [1] "false"
first_two_fields_idx <- head(which(names(read_obj_result[[1]]) == 'fields'), 2)
# show the first two returned fields of the Account object
read_obj_result[[1]][first_two_fields_idx]
#> $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:
acct_fields <- sf_describe_object_fields('Account')
acct_fields %>% select(name, label, length, soapType, type)
#> # A tibble: 69 x 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 40 xsd:string picklist
#> # … with 64 more rows
# show the picklist selection options for the Account Type field
acct_fields %>%
filter(label == "Account Type") %>%
.$picklistValues
#> [[1]]
#> # A tibble: 7 x 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
The {salesforcer} package can be seen as an extension of the popular {RForcecom}
package that originally introduced R users to the Salesforce APIs. However, there are
a few key differences. First, {salesforcer} supports more APIs. It allows for the same
methods to utilize either the REST or SOAP APIs. It also supports the newer version of the
Bulk API called “Bulk 2.0” and a limited set of functionality from the Metadata API.
There are future plans to incorporate the Reporting and Analytics APIs. Second,
{salesforcer} makes it easier to submit multiple records at once by supplying a
data.frame
instead of a named vector.
n <- 2
new_contacts <- tibble(FirstName = rep("Test", n),
LastName = paste0("Contact-Create-", 1:n))
# the RForcecom way
rforcecom_results <- NULL
for(i in 1:nrow(new_contacts)){
temp <- RForcecom::rforcecom.create(session,
objectName = "Contact",
fields = unlist(slice(new_contacts,i)))
rforcecom_results <- bind_rows(rforcecom_results, temp)
}
rforcecom_results
#> id success
#> 1 0033s000013YPkPAAW true
#> 2 0033s000013YPkUAAW true
# the salesforcer way
salesforcer_results <- salesforcer::sf_create(new_contacts, object_name="Contact")
salesforcer_results
#> # A tibble: 2 x 2
#> id success
#> <chr> <lgl>
#> 1 0033s000013YPkZAAW TRUE
#> 2 0033s000013YPkaAAG TRUE
Finally, the package is meant to adhere to principles outlined by the tidyverse
package. Function names are snake case and data is returned as tbl_df
objects.
Do not worry if you are fully utilizing the {RForcecom} package. Many of those
functions are included in the {salesforcer} package so you can drop the {RForcecom}
dependency but not have to change all of your code at once.
The {salesforcer} package has quite a bit of unit test coverage to track any code changes and ensure package reliabilty. These tests are an excellent source of examples because they cover most all cases of utilizing the package functions.
For example, if you are not sure on how to upsert records using the Bulk API just check out the tests at https://github.com/StevenMMortimer/salesforcer/blob/master/tests/testthat/test-bulk.R.
The test-bulk.R
script walks through a process of creating, searching, querying,
updating, upserting, and deleting the same set of records, so it is a concise set
of code for manipulating records via the Bulk API. Similar test scripts exist for the
REST and SOAP APIs as well.
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.
The {salesforcer} package is licensed under the MIT License (http://choosealicense.com/licenses/mit/)
If you have further questions please submit them via email or issue on GitHub at https://github.com/StevenMMortimer/salesforcer/issues. Thank you!
This project was made with love and coffee. Studies show that I write R code 3x faster after drinking one cup coffee and that productivity scales linearly. Imagine what I could accomplish if you bought me 10 cups of coffee…
Thank you for your support!