{"id":13414036,"url":"https://github.com/k-capehart/go-salesforce","last_synced_at":"2025-10-11T20:41:27.563Z","repository":{"id":229223218,"uuid":"776165239","full_name":"k-capehart/go-salesforce","owner":"k-capehart","description":"Salesforce REST API client written in Go","archived":false,"fork":false,"pushed_at":"2025-10-02T21:35:37.000Z","size":412,"stargazers_count":45,"open_issues_count":9,"forks_count":16,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-10-02T23:24:42.389Z","etag":null,"topics":["go","go-module","go-package","go-salesforce-api-wrapper","golang","rest-api-client","rest-api-wrapper","salesforce","salesforce-api","salesforce-rest-api"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/k-capehart/go-salesforce/v2","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/k-capehart.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-03-22T20:13:58.000Z","updated_at":"2025-10-02T21:20:08.000Z","dependencies_parsed_at":"2024-04-01T16:30:29.471Z","dependency_job_id":"e031d180-e654-434b-b0ef-424f6489c904","html_url":"https://github.com/k-capehart/go-salesforce","commit_stats":null,"previous_names":["k-capehart/go-salesforce"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/k-capehart/go-salesforce","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k-capehart%2Fgo-salesforce","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k-capehart%2Fgo-salesforce/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k-capehart%2Fgo-salesforce/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k-capehart%2Fgo-salesforce/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/k-capehart","download_url":"https://codeload.github.com/k-capehart/go-salesforce/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k-capehart%2Fgo-salesforce/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279008623,"owners_count":26084480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","go-module","go-package","go-salesforce-api-wrapper","golang","rest-api-client","rest-api-wrapper","salesforce","salesforce-api","salesforce-rest-api"],"created_at":"2024-07-30T20:01:55.924Z","updated_at":"2025-10-11T20:41:27.555Z","avatar_url":"https://github.com/k-capehart.png","language":"Go","funding_links":[],"categories":["Third-party APIs","第三方api"],"sub_categories":["Utility/Miscellaneous","实用程序/Miscellaneous"],"readme":"# go-salesforce\n\nA REST API wrapper for interacting with Salesforce using the Go programming language.\n\n[![GoDoc](https://godoc.org/github.com/k-capehart/go-salesforce/v2?status.png)](https://pkg.go.dev/github.com/k-capehart/go-salesforce/v2)\n[![Go Report Card](https://goreportcard.com/badge/github.com/k-capehart/go-salesforce)](https://goreportcard.com/report/github.com/k-capehart/go-salesforce)\n[![codecov](https://codecov.io/gh/k-capehart/go-salesforce/graph/badge.svg?token=20V6A05GMH)](https://codecov.io/gh/k-capehart/go-salesforce)\n[![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/k-capehart/go-salesforce/blob/main/LICENSE)\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)\n\n- Read my [blog post](https://www.kylecapehart.com/posts/go-salesforce/) for an in-depth example\n- Check out [go-salesforce-examples](https://github.com/k-capehart/go-salesforce-examples) for more examples\n- Read the [Salesforce REST API documentation](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_list.htm)\n- Read the [Golang documentation](https://go.dev/doc/)\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Types](#types)\n- [Authentication](#authentication)\n- [Configuration](#configuration)\n- [SOQL](#soql)\n- [SObject Single Record Operations](#sobject-single-record-operations)\n- [SObject Collections](#sobject-collections)\n- [Composite Requests](#composite-requests)\n- [Bulk v2](#bulk-v2)\n- [Other](#other)\n\n## Installation\n\n```\ngo get github.com/k-capehart/go-salesforce/v3\n```\n\n## Types\n\n```go\ntype Salesforce struct {\n    auth   *authentication\n    config configuration\n}\n\ntype configuration struct {\n    compressionHeaders           bool\n    apiVersion                   string\n    batchSizeMax                 int\n    bulkBatchSizeMax             int\n    httpClient                   *http.Client\n    roundTripper                 http.RoundTripper\n    shouldValidateAuthentication bool\n    httpTimeout                  time.Duration\n}\n\ntype Creds struct {\n    Domain         string\n    Username       string\n    Password       string\n    SecurityToken  string\n    ConsumerKey    string\n    ConsumerSecret string\n    ConsumerRSAPem string\n    AccessToken    string\n}\n\ntype SalesforceResults struct {\n    Results             []SalesforceResult\n    HasSalesforceErrors bool\n}\n\ntype SalesforceResult struct {\n    Id      string\n    Errors  []SalesforceErrorMessage\n    Success bool\n}\n\ntype SalesforceErrorMessage struct {\n    Message    string\n    StatusCode string\n    Fields     []string\n}\n\ntype BulkJobResults struct {\n    Id                  string\n    State               string\n    NumberRecordsFailed int\n    ErrorMessage        string\n    SuccessfulRecords   []map[string]any\n    FailedRecords       []map[string]any\n}\n```\n\n## Authentication\n\n- To begin using, create an instance of the `Salesforce` type by calling `salesforce.Init()` and passing your credentials as arguments\n- Once authenticated, all other functions can be called as methods using the resulting `Salesforce` instance\n\n### Init\n\n`func Init(creds Creds, options ...Option) *Salesforce`\n\nReturns a new Salesforce instance given a user's credentials.\n\n- `creds`: a struct containing the necessary credentials to authenticate into a Salesforce org\n- `options`: optional configuration - see [Configuration](#configuration)\n- [Creating a Connected App in Salesforce](https://help.salesforce.com/s/articleView?id=sf.connected_app_create.htm\u0026type=5)\n- [Review Salesforce oauth flows](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_flows.htm\u0026type=5)\n- If an operation fails with the Error Code `INVALID_SESSION_ID`, go-salesforce will attempt to refresh the session by resubmitting the same credentials used during initialization\n- Configuration values are set to the defaults\n\n[Client Credentials Flow](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_client_credentials_flow.htm\u0026type=5)\n\n```go\nsf, err := salesforce.Init(salesforce.Creds{\n    Domain:         DOMAIN,\n    ConsumerKey:    CONSUMER_KEY,\n    ConsumerSecret: CONSUMER_SECRET,\n})\nif err != nil {\n    panic(err)\n}\n```\n\n[Username-Password Flow](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_username_password_flow.htm\u0026type=5)\n\n```go\nsf, err := salesforce.Init(salesforce.Creds{\n    Domain:         DOMAIN,\n    Username:       USERNAME,\n    Password:       PASSWORD,\n    SecurityToken:  SECURITY_TOKEN,\n    ConsumerKey:    CONSUMER_KEY,\n    ConsumerSecret: CONSUMER_SECRET,\n})\n```\n\n[JWT Bearer Flow](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_jwt_flow.htm\u0026type=5)\n\n```go\nsf, err := salesforce.Init(salesforce.Creds{\n    Domain:         DOMAIN,\n    Username:       USERNAME,\n    ConsumerKey:    CONSUMER_KEY,\n    ConsumerRSAPem: CONSUMER_RSA_PEM,\n})\n```\n\nAuthenticate with an Access Token\n\n- Implement your own OAuth flow and use the resulting `access_token` from the response to initialize go-salesforce\n\n```go\nsf, err := salesforce.Init(salesforce.Creds{\n    Domain:      DOMAIN,\n    AccessToken: ACCESS_TOKEN,\n})\n```\n\n### GetAccessToken\n\n`func (sf *Salesforce) GetAccessToken() string`\n\nReturns the current session's Access Token as a string.\n\n```go\ntoken := sf.GetAccessToken()\n```\n\n### GetInstanceUrl\n\n`func (sf *Salesforce) GetInstanceUrl() string`\n\nReturns the current session's Instance URL as a string.\n\n```go\nurl := sf.GetInstanceUrl()\n```\n\n### GetAuthFlow\n\n`func (sf *Salesforce) GetAuthFlow() AuthFlowType`\n\nReturns the auth flow used for authentication.\n\n## Configuration\n\nConfigure optional parameters for your Salesforce instance\n\nOptional configuration: \n- `func WithCompressionHeaders(compression bool) Option` - see [docs](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_rest_compression.htm)\n- `func WithAPIVersion(version string) Option` - set API version manually instead of using default\n- `func WithBatchSizeMax(size int)` - for collections API\n- `func WithBulkBatchSizeMax(size int) Option` - for Bulk API\n- `func WithRoundTripper(rt http.RoundTripper) Option` - for http requests\n- `func WithHTTPTimeout(timeout time.Duration) Option` - set custom timeout\n- `func WithValidateAuthentication(validate bool) Option` - optionally skip validation during certain auth flows\n\nGet configuration:\n- `func (sf *Salesforce) GetAPIVersion() string`\n- `func (sf *Salesforce) GetBatchSizeMax() int`\n- `func (sf *Salesforce) GetBulkBatchSizeMax() int`\n- `func (sf *Salesforce) GetCompressionHeaders() bool`\n- `func (sf *Salesforce) GetHTTPClient() *http.Client`\n\nSee [HTTP_CLIENT_CONFIG](https://github.com/k-capehart/go-salesforce/blob/main/HTTP_CLIENT_CONFIG.md) for additional documentation on round trippers\n\nSee [examples/functional-config](https://github.com/k-capehart/go-salesforce/blob/main/examples/functional-config/main.go) and [examples/http-config](https://github.com/k-capehart/go-salesforce/blob/main/examples/http-config/main.go) for usage\n\nSee [WithHeader](#withheader) for custom header options\n\n## SOQL\n\nQuery Salesforce records\n\n- [Review Salesforce REST API resources for queries](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_query.htm)\n\n### Query\n\n`func (sf *Salesforce) Query(query string, sObject any) error`\n\nPerforms a SOQL query given a query string and decodes the response into the given struct\n\n- `query`: a SOQL query\n- `sObject`: a slice of a custom struct type representing a Salesforce Object\n\n```go\ntype Contact struct {\n    Id       string\n    LastName string\n}\n```\n\n```go\ncontacts := []Contact{}\nerr := sf.Query(\"SELECT Id, LastName FROM Contact WHERE LastName = 'Lee'\", \u0026contacts)\n```\n\n### QueryStruct\n\n`func (sf *Salesforce) QueryStruct(soqlStruct any, sObject any) error`\n\nPerforms a SOQL query given a go-soql struct and decodes the response into the given struct\n\n- `soqlStruct`: a custom struct using `soql` tags\n- `sObject`: a slice of a custom struct type representing a Salesforce Object\n- Review [forcedotcom/go-soql](https://github.com/forcedotcom/go-soql)\n  - Eliminates need to separately maintain query string and struct\n  - Helps prevent SOQL injection\n\n```go\ntype Contact struct {\n    Id       string `soql:\"selectColumn,fieldName=Id\" json:\"Id\"`\n    LastName string `soql:\"selectColumn,fieldName=LastName\" json:\"LastName\"`\n}\n\ntype ContactQueryCriteria struct {\n    LastName string `soql:\"equalsOperator,fieldName=LastName\"`\n}\n\ntype ContactSoqlQuery struct {\n    SelectClause Contact              `soql:\"selectClause,tableName=Contact\"`\n    WhereClause  ContactQueryCriteria `soql:\"whereClause\"`\n}\n```\n\n```go\nsoqlStruct := ContactSoqlQuery{\n    SelectClause: Contact{},\n    WhereClause: ContactQueryCriteria{\n        LastName: \"Lee\",\n    },\n}\ncontacts := []Contact{}\nerr := sf.QueryStruct(soqlStruct, \u0026contacts)\n```\n\n### Handling Relationship Queries\n\nWhen querying Salesforce objects, it's common to access fields that are related through parent-child or lookup relationships. For instance, querying `Account.Name` with related `Contact` might look like this:\n\n```go\ntype Account struct {\n    Name string\n}\n\ntype Contact struct {\n    Id       string\n    Account Account\n}\n\ncontacts := []Contact{}\nsf.Query(\"SELECT Id, Account.Name FROM Contact\", \u0026contacts)\n```\n\n## SObject Single Record Operations\n\nInsert, Update, Upsert, or Delete one record at a time\n\n- [Review Salesforce REST API resources for working with records](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/using_resources_working_with_records.htm?q=update)\n- Only Insert and Upsert will return an instance of `SalesforceResult`, which contains the record ID\n- DML errors result in a status code of 400\n\n### InsertOne\n\n`func (sf *Salesforce) InsertOne(sObjectName string, record any) (SalesforceResult, error)`\n\nInsertOne inserts one salesforce record of the given type\n\n- `sObjectName`: API name of Salesforce object\n- `record`: a Salesforce object record\n\n```go\ntype Contact struct {\n    LastName string\n}\n```\n\n```go\ncontact := Contact{\n    LastName: \"Stark\",\n}\nresult, err := sf.InsertOne(\"Contact\", contact)\n```\n\n### UpdateOne\n\n`func (sf *Salesforce) UpdateOne(sObjectName string, record any) error`\n\nUpdates one salesforce record of the given type\n\n- `sObjectName`: API name of Salesforce object\n- `record`: a Salesforce object record\n  - An Id is required\n\n```go\ntype Contact struct {\n    Id       string\n    LastName string\n}\n```\n\n```go\ncontact := Contact{\n    Id:       \"003Dn00000pEYQSIA4\",\n    LastName: \"Banner\",\n}\nerr := sf.UpdateOne(\"Contact\", contact)\n```\n\n### UpsertOne\n\n`func (sf *Salesforce) UpsertOne(sObjectName string, externalIdFieldName string, record any) (SalesforceResult, error)`\n\nUpdates (or inserts) one salesforce record using the given external Id\n\n- `sObjectName`: API name of Salesforce object\n- `externalIdFieldName`: field API name for an external Id that exists on the given object\n- `record`: a Salesforce object record\n  - A value for the External Id is required\n\n```go\ntype Contact struct {\n    ContactExternalId__c string\n    LastName             string\n}\n```\n\n```go\ncontact := Contact{\n    ContactExternalId__c: \"Avng0\",\n    LastName:             \"Rogers\",\n}\nresult, err := sf.UpsertOne(\"Contact\", \"ContactExternalId__c\", contact)\n```\n\n### DeleteOne\n\n`func (sf *Salesforce) DeleteOne(sObjectName string, record any) error`\n\nDeletes a Salesforce record\n\n- `sObjectName`: API name of Salesforce object\n- `record`: a Salesforce object record\n  - Should only contain an Id\n\n```go\ntype Contact struct {\n    Id       string\n}\n```\n\n```go\ncontact := Contact{\n    Id: \"003Dn00000pEYQSIA4\",\n}\nerr := sf.DeleteOne(\"Contact\", contact)\n```\n\n## SObject Collections\n\nInsert, Update, Upsert, or Delete collections of records\n\n- [Review Salesforce REST API resources for working with collections](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_sobjects_collections.htm)\n- Perform operations in batches of up to 200 records at a time\n- Consider making a Bulk request for very large operations\n- Partial successes are enabled\n  - If a record fails then successes are still committed to the database\n- Will return an instance of `SalesforceResults` which contains information on each affected record and whether DML errors were encountered\n\n### InsertCollection\n\n`func (sf *Salesforce) InsertCollection(sObjectName string, records any, batchSize int) (SalesforceResults, error)`\n\nInserts a list of salesforce records of the given type\n\n- `sObjectName`: API name of Salesforce object\n- `records`: a slice of salesforce records\n- `batchSize`: `1 \u003c= batchSize \u003c= 200`\n\n```go\ntype Contact struct {\n    LastName string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        LastName: \"Barton\",\n    },\n    {\n        LastName: \"Romanoff\",\n    },\n}\nresults, err := sf.InsertCollection(\"Contact\", contacts, 200)\n```\n\n### UpdateCollection\n\n`func (sf *Salesforce) UpdateCollection(sObjectName string, records any, batchSize int) (SalesforceResults, error)`\n\nUpdates a list of salesforce records of the given type\n\n- `sObjectName`: API name of Salesforce object\n- `records`: a slice of salesforce records\n  - An Id is required\n- `batchSize`: `1 \u003c= batchSize \u003c= 200`\n\n```go\ntype Contact struct {\n    Id       string\n    LastName string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        Id:       \"003Dn00000pEfyAIAS\",\n        LastName: \"Fury\",\n    },\n    {\n        Id:       \"003Dn00000pEfy9IAC\",\n        LastName: \"Odinson\",\n    },\n}\nresults, err := sf.UpdateCollection(\"Contact\", contacts, 200)\n```\n\n### UpsertCollection\n\n`func (sf *Salesforce) UpsertCollection(sObjectName string, externalIdFieldName string, records any, batchSize int) (SalesforceResults, error)`\n\nUpdates (or inserts) a list of salesforce records using the given ExternalId\n\n- `sObjectName`: API name of Salesforce object\n- `externalIdFieldName`: field API name for an external Id that exists on the given object\n- `records`: a slice of salesforce records\n  - A value for the External Id is required\n- `batchSize`: `1 \u003c= batchSize \u003c= 200`\n\n```go\ntype Contact struct {\n    ContactExternalId__c string\n    LastName             string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        ContactExternalId__c: \"Avng1\",\n        LastName:             \"Danvers\",\n    },\n    {\n        ContactExternalId__c: \"Avng2\",\n        LastName:             \"Pym\",\n    },\n}\nresults, err := sf.UpsertCollection(\"Contact\", \"ContactExternalId__c\", contacts, 200)\n```\n\n### DeleteCollection\n\n`func (sf *Salesforce) DeleteCollection(sObjectName string, records any, batchSize int) (SalesforceResults, error)`\n\nDeletes a list of salesforce records\n\n- `sObjectName`: API name of Salesforce object\n- `records`: a slice of salesforce records\n  - Should only contain Ids\n- `batchSize`: `1 \u003c= batchSize \u003c= 200`\n\n```go\ntype Contact struct {\n    Id       string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        Id: \"003Dn00000pEfyAIAS\",\n    },\n    {\n        Id: \"003Dn00000pEfy9IAC\",\n    },\n}\nresults, err := sf.DeleteCollection(\"Contact\", contacts, 200)\n```\n\n## Composite Requests\n\nMake numerous 'subrequests' contained within a single 'composite request', reducing the overall number of calls to Salesforce\n\n- [Review Salesforce REST API resources for making composite requests](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_composite_post.htm)\n- Up to 25 subrequests may be included in a single composite request\n  - For DML operations, max number of records to be processed is determined by batch size (`25 * (batch size)`)\n  - So if batch size is 1, then max number of records to be included in request is 25\n  - If batch size is 200, then max is 5000\n- Can optionally allow partial successes by setting allOrNone parameter\n  - If true, then successes are still committed to the database even if a record fails\n- Will return an instance of SalesforceResults which contains information on each affected record and whether DML errors were encountered\n\n### InsertComposite\n\n`func (sf *Salesforce) InsertComposite(sObjectName string, records any, batchSize int, allOrNone bool) (SalesforceResults, error)`\n\nInserts a list of salesforce records in a single request\n\n- `sObjectName`: API name of Salesforce object\n- `records`: a slice of salesforce records\n- `batchSize`: `1 \u003c= batchSize \u003c= 200`\n- `allOrNone`: denotes whether to roll back entire operation if a record fails\n\n```go\ntype Contact struct {\n    LastName string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        LastName: \"Parker\",\n    },\n    {\n        LastName: \"Murdock\",\n    },\n}\nresults, err := sf.InsertComposite(\"Contact\", contacts, 200, true)\n```\n\n### UpdateComposite\n\n`func (sf *Salesforce) UpdateComposite(sObjectName string, records any, batchSize int, allOrNone bool) (SalesforceResults, error)`\n\nUpdates a list of salesforce records in a single request\n\n- `sObjectName`: API name of Salesforce object\n- `records`: a slice of salesforce records\n  - An Id is required\n- `batchSize`: `1 \u003c= batchSize \u003c= 200`\n- `allOrNone`: denotes whether to roll back entire operation if a record fails\n\n```go\ntype Contact struct {\n    Id       string\n    LastName string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        Id:       \"003Dn00000pEi32IAC\",\n        LastName: \"Richards\",\n    },\n    {\n        Id:       \"003Dn00000pEi31IAC\",\n        LastName: \"Storm\",\n    },\n}\nresults, err := sf.UpdateComposite(\"Contact\", contacts, 200, true)\n```\n\n### UpsertComposite\n\n`func (sf *Salesforce) UpsertComposite(sObjectName string, externalIdFieldName string, records any, batchSize int, allOrNone bool) (SalesforceResults, error)`\n\nUpdates (or inserts) a list of salesforce records using the given ExternalId in a single request\n\n- `sObjectName`: API name of Salesforce object\n- `externalIdFieldName`: field API name for an external Id that exists on the given object\n- `records`: a slice of salesforce records\n  - A value for the External Id is required\n- `batchSize`: `1 \u003c= batchSize \u003c= 200`\n- `allOrNone`: denotes whether to roll back entire operation if a record fails\n\n```go\ntype Contact struct {\n    ContactExternalId__c string\n    LastName             string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        ContactExternalId__c: \"Avng3\",\n        LastName:             \"Maximoff\",\n    },\n    {\n        ContactExternalId__c: \"Avng4\",\n        LastName:             \"Wilson\",\n    },\n}\nresults, err := sf.UpsertComposite(\"Contact\", \"ContactExternalId__c\", contacts, 200, true)\n```\n\n### DeleteComposite\n\n`func (sf *Salesforce) DeleteComposite(sObjectName string, records any, batchSize int, allOrNone bool) (SalesforceResults, error)`\n\nDeletes a list of salesforce records in a single request\n\n- `sObjectName`: API name of Salesforce object\n- `records`: a slice of salesforce records\n  - Should only contain Ids\n- `batchSize`: `1 \u003c= batchSize \u003c= 200`\n- `allOrNone`: denotes whether to roll back entire operation if a record fails\n\n```go\ntype Contact struct {\n    Id       string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        Id: \"003Dn00000pEi0OIAS\",\n    },\n    {\n        Id: \"003Dn00000pEi0NIAS\",\n    },\n}\nresults, err := sf.DeleteComposite(\"Contact\", contacts, 200, true)\n```\n\n## Bulk v2\n\nCreate Bulk API Jobs to query, insert, update, upsert, and delete large collections of records\n\n- [Review Salesforce REST API resources for Bulk v2](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/bulk_api_2_0.htm)\n- Work with large lists of records by passing either a slice or records or the path to a csv file\n- Jobs can run asynchronously or synchronously\n\n### QueryBulkExport\n\n`func (sf *Salesforce) QueryBulkExport(query string, filePath string) error`\n\nPerforms a query and exports the data to a csv file\n\n- `filePath`: name and path of a csv file to be created\n- `query`: a SOQL query\n\n```go\nerr := sf.QueryBulkExport(\"SELECT Id, FirstName, LastName FROM Contact\", \"data/export.csv\")\n```\n\n### QueryStructBulkExport\n\n`func (sf *Salesforce) QueryStructBulkExport(soqlStruct any, filePath string) error`\n\nPerforms a SOQL query given a go-soql struct and decodes the response into the given struct\n\n- `filePath`: name and path of a csv file to be created\n- `soqlStruct`: a custom struct using `soql` tags\n- Review [forcedotcom/go-soql](https://github.com/forcedotcom/go-soql)\n  - Eliminates need to separately maintain query string and struct\n  - Helps prevent SOQL injection\n\n```go\ntype ContactSoql struct {\n    Id        string `soql:\"selectColumn,fieldName=Id\" json:\"Id\"`\n    FirstName string `soql:\"selectColumn,fieldName=FirstName\" json:\"FirstName\"`\n    LastName  string `soql:\"selectColumn,fieldName=LastName\" json:\"LastName\"`\n}\n\ntype ContactSoqlQuery struct {\n    SelectClause ContactSoql          `soql:\"selectClause,tableName=Contact\"`\n}\n```\n\n```go\nsoqlStruct := ContactSoqlQuery{\n    SelectClause: ContactSoql{},\n}\nerr := sf.QueryStructBulkExport(soqlStruct, \"data/export2.csv\")\n```\n\n### QueryBulkIterator\n\n`func (sf *Salesforce) QueryBulkIterator(query string) (IteratorJob, error)`\n\nPerforms a query and return a IteratorJob to decode data\n\n- `query`: a SOQL query\n\n```go\ntype Contact struct {\n    Id        string `json:\"Id\" csv:\"Id\"`\n    FirstName string `json:\"FirstName\" csv:\"FirstName\"`\n    LastName  string `json:\"LastName\" csv:\"LastName\"`\n}\n\nit, err := sf.QueryBulkIterator(\"SELECT Id, FirstName, LastName FROM Contact\")\nif err != nil {\n    panic(err)\n}\n\nfor it.Next() {\n    var data []Contact\n    if err := it.Decode(\u0026data); err != nil {\n        panic(err)\n    }\n    fmt.Println(data)\n}\n\nif err := it.Error(); err != nil {\n    panic(err)\n}\n```\n\n#### Bulk with nested objects\n\n- Nested objects are supported using the `csv` tag\n- The `csv` tag should be formatted as `csv:\"APIFieldName.,inline\"`\n\n```go\ntype ContactWithAltOwner struct {\n    Id                 string\n    AlternateOwnerName User `csv:\"Alternate_Owner__r.,inline\"`\n}\n\nit, err = sf.QueryBulkIterator(\"SELECT Id, Alternate_Owner__r.Name FROM Contact\")\nif err != nil {\n    panic(err)\n}\n\nfor it.Next() {\n    var data []Contact\n    if err := it.Decode(\u0026data); err != nil {\n        panic(err)\n    }\n    fmt.Println(data)\n}\n\nif err := it.Error(); err != nil {\n    panic(err)\n}\n```\n\n### InsertBulk\n\n`func (sf *Salesforce) InsertBulk(sObjectName string, records any, batchSize int, waitForResults bool) ([]string, error)`\n\nInserts a list of salesforce records using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object\n- `records`: a slice of salesforce records\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n\n```go\ntype Contact struct {\n    LastName string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        LastName: \"Lang\",\n    },\n    {\n        LastName: \"Van Dyne\",\n    },\n}\njobIds, err := sf.InsertBulk(\"Contact\", contacts, 1000, false)\n```\n\n### InsertBulkFile\n\n`func (sf *Salesforce) InsertBulkFile(sObjectName string, filePath string, batchSize int, waitForResults bool) ([]string, error)`\n\nInserts a collection of salesforce records from a csv file using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object\n- `filePath`: path to a csv file containing salesforce data\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n\n`data/avengers.csv`\n\n```\nFirstName,LastName\nTony,Stark\nSteve,Rogers\nBruce,Banner\n```\n\n```go\njobIds, err := sf.InsertBulkFile(\"Contact\", \"data/avengers.csv\", 1000, false)\n```\n\n### InsertBulkAssign\n\n`func (sf *Salesforce) InsertBulkAssign(sObjectName string, records any, batchSize int, waitForResults bool, assignmentRuleId string) ([]string, error)`\n\nInserts a list of Lead or Case records to be assigned via an Assignment rule, using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object (must be Lead or Case)\n- `records`: a slice of Lead or Case records\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n- `assignmentRuleId`: the Salesforce Id of a Lead or Case Assignment Rule\n\n```go\ntype Lead struct {\n    LastName string\n    Company string\n}\n```\n\n```go\nleads := []Lead{\n    {\n        LastName: \"Spector\",\n        Company:  \"The Avengers\",\n    },\n}\njobIds, err := sf.InsertBulkAssign(\"Lead\", leads, 100, true, \"01QDn00000112FHMAY\")\n```\n\n### InsertBulkFileAssign\n\n`func (sf *Salesforce) InsertBulkFileAssign(sObjectName string, filePath string, batchSize int, waitForResults bool, assignmentRuleId string) ([]string, error)`\n\nInserts a list of Lead or Case records to be assigned via an Assignment rule, from a csv file using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object (must be Lead or Case)\n- `filePath`: path to a csv file containing Lead or Case data\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n- `assignmentRuleId`: the Salesforce Id of a Lead or Case Assignment Rule\n\n`data/avengers.csv`\n\n```\nFirstName,LastName,Company\nTony,Stark,The Avengers\nSteve,Rogers,The Avengers\nBruce,Banner,The Avengers\n```\n\n```go\njobIds, err := sf.InsertBulkFileAssign(\"Lead\", \"data/avengers.csv\", 1000, false, \"01QDn00000112FHMAY\")\n```\n\n### UpdateBulk\n\n`func (sf *Salesforce) UpdateBulk(sObjectName string, records any, batchSize int, waitForResults bool) ([]string, error)`\n\nUpdates a list of salesforce records using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object\n- `records`: a slice of salesforce records\n  - An Id is required\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n\n```go\ntype Contact struct {\n    Id       string\n    LastName string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        Id:       \"003Dn00000pEsoRIAS\",\n        LastName: \"Strange\",\n    },\n    {\n        Id:       \"003Dn00000pEsoSIAS\",\n        LastName: \"T'Challa\",\n    },\n}\njobIds, err := sf.UpdateBulk(\"Contact\", contacts, 1000, false)\n```\n\n### UpdateBulkFile\n\n`func (sf *Salesforce) UpdateBulkFile(sObjectName string, filePath string, batchSize int, waitForResults bool) ([]string, error)`\n\nUpdates a collection of salesforce records from a csv file using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object\n- `filePath`: path to a csv file containing salesforce data\n  - An Id is required within csv data\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n\n`data/update_avengers.csv`\n\n```\nId,FirstName,LastName\n003Dn00000pEwRuIAK,Rocket,Raccoon\n003Dn00000pEwQxIAK,Drax,The Destroyer\n003Dn00000pEwQyIAK,Peter,Quill\n003Dn00000pEwQzIAK,I Am,Groot\n003Dn00000pEwR0IAK,Gamora,Zen Whoberi Ben Titan\n003Dn00000pEwR1IAK,Mantis,Mantis\n```\n\n```go\njobIds, err := sf.UpdateBulkFile(\"Contact\", \"data/update_avengers.csv\", 1000, false)\n```\n\n### UpdateBulkAssign\n\n`func (sf *Salesforce) UpdateBulkAssign(sObjectName string, records any, batchSize int, waitForResults bool, assignmentRuleId string) ([]string, error)`\n\nUpdates a list of Lead or Case records to be assigned via an Assignment rule, using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object (must be Lead or Case)\n- `records`: a slice of Lead or Case records\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n- `assignmentRuleId`: the Salesforce Id of a Lead or Case Assignment Rule\n\n```go\ntype Lead struct {\n    Id        string\n    LastName  string\n    Company   string\n}\n```\n\n```go\nleads := []Lead{\n    {\n        Id:       \"00QDn0000024r6FMAQ\",\n        LastName: \"Grant\",\n        Company:  \"The Avengers\",\n    },\n}\njobIds, err := sf.UpdateBulkAssign(\"Lead\", leads, 100, true, \"01QDn00000112FHMAY\")\n```\n\n### UpdateBulkFileAssign\n\n`func (sf *Salesforce) UpdateBulkFileAssign(sObjectName string, filePath string, batchSize int, waitForResults bool, assignmentRuleId string) ([]string, error)`\n\nUpdates a list of Lead or Case records to be assigned via an Assignment rule, from a csv file using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object (must be Lead or Case)\n- `filePath`: path to a csv file containing Lead or Case data\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n- `assignmentRuleId`: the Salesforce Id of a Lead or Case Assignment Rule\n\n`data/update_avengers.csv`\n\n```\nId,FirstName,LastName,Company\n00QDn0000024r6WMAQ,Clint,Barton,The Avengers\n00QDn0000024r6VMAQ,Natasha,Romanoff,The Avengers\n00QDn0000024r6UMAQ,Hank,Pym,The Avengers\n```\n\n```go\njobIds, err := sf.UpdateBulkFileAssign(\"Lead\", \"data/update_avengers.csv\", 100, true, \"01QDn00000112FHMAY\")\n```\n\n### UpsertBulk\n\n`func (sf *Salesforce) UpsertBulk(sObjectName string, externalIdFieldName string, records any, batchSize int, waitForResults bool) ([]string, error)`\n\nUpdates (or inserts) a list of salesforce records using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object\n- `externalIdFieldName`: field API name for an external Id that exists on the given object\n- `records`: a slice of salesforce records\n  - A value for the External Id is required\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n\n```go\ntype Contact struct {\n    ContactExternalId__c string\n    LastName             string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        ContactExternalId__c: \"Avng5\",\n        LastName:             \"Rhodes\",\n    },\n    {\n        ContactExternalId__c: \"Avng6\",\n        LastName:             \"Quill\",\n    },\n}\njobIds, err := sf.UpsertBulk(\"Contact\", \"ContactExternalId__c\", contacts, 1000, false)\n```\n\n### UpsertBulkFile\n\n`func (sf *Salesforce) UpsertBulkFile(sObjectName string, externalIdFieldName string, filePath string, batchSize int, waitForResults bool) ([]string, error)`\n\nUpdates (or inserts) a collection of salesforce records from a csv file using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object\n- `externalIdFieldName`: field API name for an external Id that exists on the given object\n- `filePath`: path to a csv file containing salesforce data\n  - A value for the External Id is required within csv data\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n\n`data/upsert_avengers.csv`\n\n```\nContactExternalId__c,FirstName,LastName\nAvng7,Matt,Murdock\nAvng8,Luke,Cage\nAvng9,Jessica,Jones\nAvng10,Danny,Rand\n```\n\n```go\njobIds, err := sf.UpsertBulkFile(\"Contact\", \"ContactExternalId__c\", \"data/upsert_avengers.csv\", 1000, false)\n```\n\n### UpsertBulkAssign\n\n`func (sf *Salesforce) UpsertBulkAssign(sObjectName string, externalIdFieldName string, records any, batchSize int, waitForResults bool, assignmentRuleId string) ([]string, error)`\n\nUpdates (or inserts) a list of Lead or Case records to be assigned via an Assignment rule, using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object (must be Lead or Case)\n- `externalIdFieldName`: field API name for an external Id that exists on the given object\n- `records`: a slice of Lead or Case records\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n- `assignmentRuleId`: the Salesforce Id of a Lead or Case Assignment Rule\n\n```go\ntype Lead struct {\n    LeadExternalId__c string\n    LastName          string\n    Company           string\n}\n```\n\n```go\nleads := []Lead{\n    {\n        LeadExternalId__c: \"MK3\",\n        LastName:          \"Lockley\",\n        Company:           \"The Avengers\",\n    },\n}\njobIds, err := sf.UpsertBulkAssign(\"Lead\", \"LeadExternalId__c\", leads, 100, true, \"00QDn0000024r6FMAQ\")\n```\n\n### UpsertBulkFileAssign\n\n`func (sf *Salesforce) UpsertBulkFileAssign(sObjectName string, externalIdFieldName string, filePath string, batchSize int, waitForResults bool, assignmentRuleId string) ([]string, error)`\n\nUpdates (or inserts) a list of Lead or Case records to be assigned via an Assignment rule, from a csv file using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object (must be Lead or Case)\n- `externalIdFieldName`: field API name for an external Id that exists on the given object\n- `filePath`: path to a csv file containing salesforce data\n  - A value for the External Id is required within csv data\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n- `assignmentRuleId`: the Salesforce Id of a Lead or Case Assignment Rule\n\n`data/upsert_avengers`\n\n```\nLeadExternalId__c,FirstName,LastName,Company\nAvng11,Nick,Fury,The Avengers\nAvng12,Maria,Hill,The Avengers\nAvng13,Howard,Stark,The Avengers\n```\n\n```go\njobIds, err := sf.UpsertBulkFileAssign(\"Lead\", \"LeadExternalId__c\", \"data/upsert_avengers.csv\", 100, true, \"01QDn00000112FHMAY\")\n```\n\n### DeleteBulk\n\n`func (sf *Salesforce) DeleteBulk(sObjectName string, records any, batchSize int, waitForResults bool) ([]string, error)`\n\nDeletes a list of salesforce records using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object\n- `records`: a slice of salesforce records\n  - should only contain Ids\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n\n```go\ntype Contact struct {\n    Id       string\n}\n```\n\n```go\ncontacts := []ContactIds{\n    {\n        Id: \"003Dn00000pEsoRIAS\",\n    },\n    {\n        Id: \"003Dn00000pEsoSIAS\",\n    },\n}\njobIds, err := sf.DeleteBulk(\"Contact\", contacts, 1000, false)\n```\n\n### DeleteBulkFile\n\n`func (sf *Salesforce) DeleteBulkFile(sObjectName string, filePath string, batchSize int, waitForResults bool) ([]string, error)`\n\nDeletes a collection of salesforce records from a csv file using Bulk API v2, returning a list of Job IDs\n\n- `sObjectName`: API name of Salesforce object\n- `filePath`: path to a csv file containing salesforce data\n  - should only contain Ids\n- `batchSize`: `1 \u003c= batchSize \u003c= 10000`\n- `waitForResults`: denotes whether to wait for jobs to finish\n\n`data/delete_avengers.csv`\n\n```\nId\n003Dn00000pEwRuIAK\n003Dn00000pEwQxIAK\n003Dn00000pEwQyIAK\n003Dn00000pEwQzIAK\n003Dn00000pEwR0IAK\n003Dn00000pEwR1IAK\n```\n\n```go\njobIds, err := sf.DeleteBulkFile(\"Contact\", \"data/delete_avengers.csv\", 1000, false)\n```\n\n### GetJobResults\n\n`func (sf *Salesforce) GetJobResults(bulkJobId string) (BulkJobResults, error)`\n\nReturns an instance of BulkJobResults given a Job Id\n\n- `bulkJobId`: the Id for a bulk API job\n- Use to check results of Bulk Job, including successful and failed records\n\n```go\ntype Contact struct {\n    LastName string\n}\n```\n\n```go\ncontacts := []Contact{\n    {\n        LastName: \"Grimm\",\n    },\n}\njobIds, err := sf.InsertBulk(\"Contact\", contacts, 1000, true)\nif err != nil {\n    panic(err)\n}\nfor _, id := range jobIds {\n    results, err := sf.GetJobResults(id) // returns an instance of BulkJobResults\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(results)\n}\n```\n\n## Other\n\n### DoRequest\n\n`func (sf *Salesforce) DoRequest(method string, uri string, body []byte, opts ...RequestOption) (*http.Response, error)`\n\nMake a http call to Salesforce, returning a response to be parsed by the client\n\n- `method`: request method (\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\")\n- `uri`: uniform resource identifier (include everything after `/services/data/apiVersion`)\n- `body`: json encoded body to be included in request\n- `opts`: optional request options (currently supports custom headers via `WithHeader`)\n\nExample to call the `/limits` endpoint\n\n```go\nresp, err := sf.DoRequest(http.MethodGet, \"/limits\", nil)\nif err != nil {\n    panic(err)\n}\nrespBody, err := io.ReadAll(resp.Body)\nif err != nil {\n    panic(err)\n}\nfmt.Println(string(respBody))\n```\n\n### WithHeader\n\n`func WithHeader(key, value string) RequestOption`\n\nCreates a request option that sets a custom header on the HTTP request\n\n- `key`: the header name\n- `value`: the header value\n\n```go\n// Use If-Modified-Since for efficient caching\nresp, err := sf.DoRequest(\"GET\", \"/sobjects/Account/describe\", nil,\n    salesforce.WithHeader(\"If-Modified-Since\", \"Wed, 21 Oct 2015 07:28:00 GMT\"))\nif err != nil {\n    panic(err)\n}\n\nif resp.StatusCode == 304 {\n    fmt.Println(\"Data not modified, using cached version\")\n}\n```\n\n```go\n// Multiple custom headers\nresp, err := sf.DoRequest(\"GET\", \"/sobjects\", nil,\n    salesforce.WithHeader(\"If-Modified-Since\", \"Wed, 21 Oct 2015 07:28:00 GMT\"),\n    salesforce.WithHeader(\"Accept-Language\", \"en-US\"))\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk-capehart%2Fgo-salesforce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fk-capehart%2Fgo-salesforce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk-capehart%2Fgo-salesforce/lists"}