Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Salesforce Laravel Package

jeffersongoncalves/laravel-salesforce

A Laravel package that brings Salesforce into your app with OAuth authentication, a fluent REST API client, and Eloquent-style models for querying, creating, and syncing Salesforce objects via SOQL, all with simple configuration and Laravel-native conventions.

View on GitHub
Deep Wiki
Context7

Getting Started

This package wraps the Salesforce REST API behind a Laravel-native interface: SOQL queries, SOSL search, and per-object CRUD/describe, authenticated via OAuth2 password grant with a cached access token and transparent re-authentication.

Minimal setup:

  1. composer require the package, then publish its config: php artisan vendor:publish --tag=salesforce-config (tag name will vary — check the package's service provider).
  2. Populate credentials in .env rather than the config file, since the config is committed. Expect keys along the lines of:
SALESFORCE_CLIENT_ID=...          # Connected App consumer key
SALESFORCE_CLIENT_SECRET=...      # Connected App consumer secret
[email protected]
SALESFORCE_PASSWORD=...
SALESFORCE_SECURITY_TOKEN=...     # omit only if the user's IP is whitelisted
SALESFORCE_LOGIN_URL=https://login.salesforce.com   # test.salesforce.com for sandboxes
  1. Confirm your cache store is persistent. The access token is cached, and the array driver (Laravel's default in phpunit.xml and often in local .env) will re-authenticate on every request — and break tests that assert a single login.

Where to look first: the config file for connection settings, then the facade/entry class to see the actual method signatures (query, search, sobject, or a repository-style equivalent) — these are the contract you'll code against.

First use case: a read-only SOQL query feeding a dashboard. Keep the first integration read-only so you can validate credentials, sandbox vs. production URLs, and field-level permissions before touching write operations.

$accounts = Salesforce::query('SELECT Id, Name, Industry FROM Account WHERE Industry = :industry LIMIT 50', [
    'industry' => 'Technology',
]);

Implementation Patterns

Wrap the package in your own service/repository. The biggest day-to-day risk is coupling controllers and Blade views to a vendor's method names and Salesforce's response envelope. Introduce a thin SalesforceRepository or per-object client (AccountRepository, ContactRepository) that returns your own DTOs or Eloquent-like collections. When the package changes, or when you outgrow it and move to a generated Salesforce SDK, you edit one class instead of fifty.

Lean on the fluent sobject API for CRUD, raw SOQL for reporting. The sobject('Account')->find($id) / ->create([...]) / ->update($id, [...]) / ->delete($id) style is the ergonomic path for record operations in form handlers. Reserve raw query() for list/report screens where you need joins, aggregates, or WHERE clauses. Mixing both is normal; just be consistent per feature.

Use bindings, never string interpolation, in SOQL. SOQL has no prepared statements, so user input must go through the package's binding mechanism (or be escaped manually if it doesn't support bindings for a given clause). Anything user-supplied inside a query string is an injection vector — including ORDER BY fields and LIMIT values.

Treat writes as async where latency matters. Salesforce round-trips are 200–800ms from most regions. ->create()/->update() inside a web request during checkout or onboarding is a UX problem and a failure-mode problem. Push writes to a queued job with retries and a dead-letter path; keep reads synchronous only when the UI genuinely needs the data inline.

Cache aggressively at your layer, not just the token. Describe calls (->describe()) are expensive and near-static — cache field metadata for hours or days, keyed by sobject and org. Query results for reference data (picklist values, country lists, price books) should be cached with a short TTL and an explicit invalidation trigger. The package caches the token for you; it does not cache your data.

Build a sync boundary you can reason about. When Salesforce records mirror local models, decide the source of truth per field and store the Salesforce Id plus a SystemModstamp locally. That timestamp is your change-detection primitive for delta pulls (WHERE SystemModstamp > :lastSync), which is cheaper and far safer than full re-pulls.

Testing. Use the package's fake/mock hook if it ships one (an Http::fake()-based swap is common); otherwise bind a fake implementation of your own repository interface in the container and test your application logic against that. Never let the suite hit a real org — password-grant logins count against the org's API limits and will lock you out under CI load.

Gotchas and Tips

  • Password grant is on borrowed time. Salesforce has been steering integrations toward JWT bearer or authorization-code flows for years, and orgs with MFA enforced (which is now the default posture) can block username-password authentication outright. If this is a new integration, confirm with the Salesforce admin that password grant is still permitted, and design your auth behind your repository interface so a swap to JWT is contained. This is the single biggest longevity risk in the package.

  • The security token is not optional in practice. The password sent is password + security_token unless the running IP is in the org's trusted IP ranges. Symptom: works locally, fails in production (or vice versa) with INVALID_LOGIN. Whitelisting a dynamic cloud IP is usually worse than just setting the token.

  • Sandbox vs. production is a single env var away from a disaster. https://test.salesforce.com for sandboxes, https://login.salesforce.com for production. Consider asserting in a service provider that the login URL and the username's org match expectations for app()->environment(), so a copy-pasted .env can't write production records from a staging app.

  • Re-authentication won't cover every failure. Automatic re-auth handles expired/revoked tokens (INVALID_SESSION_ID), but it cannot fix INSUFFICIENT_ACCESS, REQUEST_LIMIT_EXCEEDED, or validation-rule failures. Catch the package's exception type, inspect the Salesforce error code array — the API returns errors as an array even on non-2xx, and partial-success batches return 200 with per-record failures — and only retry on auth/transient codes.

  • Token cache driver quirks. If the cache store is array or null, the package logs in on every request and you'll burn API calls fast. If the store is shared across apps with different credentials, you can get cross-contaminated tokens and confusing INVALID_SESSION_ID errors. Namespace the cache key or keep the store app-specific.

  • SOQL result pagination is not OFFSET-based. Large result sets come back with done: false and a nextRecordsUrl you must follow. If the package doesn't loop automatically, your "get all accounts" code silently returns the first 2,000 rows. Verify against an org with more than one batch of data before shipping a report.

  • Relationship depth is limited. You can traverse parent relationships in a query (SELECT Id, Account.Name FROM Contact) but child subqueries are one level deep, and each has its own row cap. Complex object graphs need multiple queries assembled in PHP.

  • Deletes are soft. ->delete() moves records to the Recycle Bin; isDeleted records can still surface in queries unless you filter. Don't assume deletion is a hard purge for compliance or dedupe logic.

  • Dates and datetimes. Salesforce returns datetimes in UTC ISO-8601 with a Z suffix; DATE fields have no time component and will bite you if you pass a Carbon with a timezone. Convert at the boundary and be explicit about which fields are Date vs. DateTime — describe() is the authoritative source.

  • describe() is the right way to build dynamic forms (required flags, picklist values, field types), but cache it and don't call it inside a loop over objects. It's also subject to field-level security: fields the integration user can't see simply won't appear, which is a common "why is the field missing" bug — check the Connected App's and running user's profile permissions first.

  • Extension points to favour: the config file for connection/locale/timeouts, the container binding for the client (for swapping in a fake), and Http::fake() if the package is built on the Laravel HTTP client. If you need logging of every request/response, a global middleware or a wrapping decorator around the client is cleaner than sprinkling Log::debug calls through your repositories.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings