Connect Bubble to Salesforce using a Connected App with OAuth 2.0 authorization code flow — the most complex CRM integration in Bubble because Salesforce requires a registered redirect URI, a browser-based login flow, and per-org instance subdomains. A Backend Workflow catches the OAuth callback and stores tokens; SOQL queries run via GET with URL-encoded query strings. Requires Salesforce Enterprise or Developer Edition for API access.
| Fact | Value |
|---|---|
| Tool | Salesforce |
| Category | CRM & Sales |
| Method | Bubble API Connector |
| Difficulty | Advanced |
| Time required | 4–6 hours |
| Last updated | July 2026 |
Why Salesforce OAuth is fundamentally different from other CRM integrations in Bubble
Most CRM integrations in Bubble use either a static API key (Pipedrive) or a self-client OAuth flow that generates credentials without a browser redirect (Zoho). Salesforce is different: it requires the authorization code flow, where each user must log into their Salesforce account via a browser popup and explicitly grant your Connected App permission to access their data. This introduces three Bubble-specific challenges.
First, the redirect URI. Salesforce's Connected App settings require you to register the exact URL where Salesforce will send users after authentication. This URL must be your Bubble app's domain — specifically a page you create to handle the OAuth callback. Any mismatch (including trailing slashes) causes a `redirect_uri_mismatch` error that blocks the entire flow.
Second, instance-specific subdomains. Every Salesforce org has a unique subdomain: `yourcompany.my.salesforce.com`. The API Connector base URL must reference this exact subdomain — using `login.salesforce.com` or a generic domain for API calls returns errors. After OAuth authentication, Salesforce tells you the instance URL in the token response, so you must store it alongside the access token.
Third, SOQL requires explicit field lists. Salesforce does not support `SELECT *`. Every SOQL query must name each field: `SELECT Id, Name, StageName, Amount FROM Opportunity WHERE StageName != 'Closed Won'`. Salesforce Object Manager in the Setup panel shows all available field API names.
Despite this complexity, the result is a powerful integration: a Bubble app that acts as a custom Salesforce front-end, giving your team a tailored CRM experience without paying for Salesforce's own customization tools.
Integration method
API Connector with Salesforce Connected App OAuth 2.0 (authorization code flow), Backend Workflow for OAuth callback handling, and SOQL queries via GET URL parameters — with instance-specific subdomain handling.
Prerequisites
- A Salesforce account with API access — requires Enterprise Edition, Unlimited Edition, or a free Salesforce Developer org (developer.salesforce.com)
- Admin access to Salesforce Setup to create a Connected App
- A Bubble app on Starter plan or above — Backend Workflows are required for the OAuth callback handling and token refresh
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble)
- Your Bubble app's published domain (the URL where Salesforce will redirect users after authentication)
Step-by-step guide
Create a Salesforce Connected App with the correct Bubble redirect URI
In your Salesforce account, click the gear icon (Settings) in the top-right, then 'Setup.' In the left sidebar search box, type 'App Manager' and click it. Click 'New Connected App' in the top right corner. Fill in the required fields: - Connected App Name: `Bubble Integration` (or your app name) - API Name: auto-populates - Contact Email: your email Under 'API (Enable OAuth Settings)': - Check 'Enable OAuth Settings' - Callback URL: enter your Bubble app's OAuth callback page URL. This must be the exact URL you will create in Bubble: `https://yourapp.bubbleapps.io/oauth_callback` or your custom domain equivalent. If you have a custom domain, use that instead. You can add multiple callback URLs by adding each on a new line. - Selected OAuth Scopes: add at minimum these three: - `Access and manage your data (api)` — for API read/write access - `Perform requests on your behalf at any time (refresh_token, offline_access)` — for refresh tokens - `Provide access to your data via the Web (web)` — for web-based auth Click Save. Salesforce takes 2–10 minutes to provision the Connected App. Wait before trying to authenticate. After waiting, go back to App Manager, find your app, click the dropdown arrow, and click 'View.' Copy the Consumer Key (this is your client_id) and click 'Click to reveal' to see the Consumer Secret (this is your client_secret). Store both securely.
1// Connected App OAuth scopes to select:2// api → Access and manage your data3// refresh_token → Perform requests on your behalf at any time4// offline_access → included with refresh_token scope5// web → Provide access to your data via the Web67// Callback URLs (add your actual Bubble app URL):8// https://yourapp.bubbleapps.io/oauth_callback9// https://yourcustomdomain.com/oauth_callback (if using custom domain)1011// After saving, find your Connected App:12// Setup → App Manager → find 'Bubble Integration' → dropdown → View13// Consumer Key = client_id14// Consumer Secret = client_secret (click 'Click to reveal')Pro tip: The Connected App provisioning delay (2–10 minutes) is real. If you immediately try to test OAuth and get an error like 'invalid client_id,' wait another few minutes and try again. Do not assume the Connected App settings are wrong just because the first attempt fails.
Expected result: A Salesforce Connected App named 'Bubble Integration' is created with API OAuth scopes and your Bubble callback URL. You have the Consumer Key (client_id) and Consumer Secret (client_secret) stored safely.
Create the OAuth callback page and Backend Workflow in Bubble
Before configuring the API Connector, set up the Bubble infrastructure to handle the OAuth redirect from Salesforce. First, create an `oauth_callback` page in Bubble (Page name: `oauth_callback`). This is the page Salesforce redirects to with the `?code=` URL parameter after the user authorizes your Connected App. Create a Salesforce_Tokens data type in the Data tab with these fields: - `access_token` (text) - `refresh_token` (text) - `instance_url` (text) — CRITICAL: this is the user's org-specific Salesforce domain - `token_expiry` (date) - `user` (User) — link to the Bubble User who authenticated Apply privacy rules to Salesforce_Tokens: Everyone else → no fields visible. Enable 'Ignore privacy rules when using workflows.' Now go to Backend Workflows. Create a new workflow named `Exchange Salesforce Code`. Add an input parameter `code` (text). Inside this workflow: 1. Call 'API Connector → Salesforce → Exchange Code for Token' (you will create this call in the next step) 2. Create or update a Salesforce_Tokens record: access_token = result access_token, refresh_token = result refresh_token, instance_url = result instance_url, token_expiry = Current date/time + 7200 seconds, user = Current User On your `oauth_callback` Bubble page, add a Page is loaded workflow: - Condition: Only when Get URL parameter 'code' is not empty - Action: Schedule API Workflow → Exchange Salesforce Code, with code = Get URL parameter 'code' - After exchange: navigate user to your main app page
1// Salesforce_Tokens data type:2// access_token → text3// refresh_token → text4// instance_url → text (e.g., 'https://mycompany.my.salesforce.com')5// token_expiry → date6// user → User78// Privacy rule: Everyone else → no fields visible9// ✓ Ignore privacy rules when using workflows1011// oauth_callback page workflow:12// Trigger: Page is loaded13// Condition: Get URL parameter 'code' is not empty14// Step 1: Schedule API Workflow → Exchange Salesforce Code15// code = Get URL parameter 'code'16// Step 2: Navigate to /main-app-page1718// If code is empty (user cancelled):19// Condition: Get URL parameter 'error' is not empty20// Step 1: Navigate to /login with error messagePro tip: Salesforce also sends an `instance_url` field in the token exchange response — store it in your Salesforce_Tokens data type. This is the user's org-specific Salesforce domain (e.g., `https://mycompany.my.salesforce.com`) and must be used as the base URL for all subsequent API calls. Using `login.salesforce.com` as the base URL for data queries will fail.
Expected result: An `oauth_callback` Bubble page exists that reads the `?code=` URL parameter. A `Salesforce_Tokens` data type is created with privacy rules. The `Exchange Salesforce Code` Backend Workflow is created with a `code` input parameter and placeholder steps.
Configure the Salesforce API Connector calls
In Plugins → API Connector, click 'Add another API' and name it `Salesforce`. Leave the base URL blank for now — you will use the stored `instance_url` as a prefix in individual calls since each Salesforce org has a different subdomain. Call 1 — Exchange Code for Token: Click 'Add another call.' Name it `Exchange Code for Token`. Method: POST. URL: `https://login.salesforce.com/services/oauth2/token`. Body type: Form URL-encoded (NOT JSON — Salesforce's token endpoint requires form-encoded). Body parameters: - `grant_type` = `authorization_code` - `code` = `<code>` (dynamic) - `client_id` = your Consumer Key (mark Private) - `client_secret` = your Consumer Secret (mark Private) - `redirect_uri` = `https://yourapp.bubbleapps.io/oauth_callback` (must match Connected App exactly) Set 'Use as' to Action. Call 2 — Refresh Token: Name `Refresh Salesforce Token`. Method: POST. URL: `https://login.salesforce.com/services/oauth2/token`. Body: - `grant_type` = `refresh_token` - `refresh_token` = `<refresh_token>` (dynamic, Private) - `client_id` = your Consumer Key (Private) - `client_secret` = your Consumer Secret (Private) Set 'Use as' to Action. Call 3 — SOQL Query: Name `SOQL Query`. Method: GET. URL: `<instance_url>/services/data/v59.0/query` (instance_url dynamic). URL parameter: `q` = `<soql_query>` (dynamic). Add header `Authorization` = `Bearer <access_token>` (Private dynamic). Set 'Use as' to Data. Initialize with a test instance URL and token. Call 4 — Create Record: Name `Create SObject`. Method: POST. URL: `<instance_url>/services/data/v59.0/sobjects/<object_type>/` (both dynamic). Header: `Authorization: Bearer <access_token>`. JSON body = `<record_data>` (dynamic JSON object). Set 'Use as' to Action.
1// Call 1: Exchange Code for Token2{3 "method": "POST",4 "url": "https://login.salesforce.com/services/oauth2/token",5 "content_type": "application/x-www-form-urlencoded",6 "body": {7 "grant_type": "authorization_code",8 "code": "<authorization_code>",9 "client_id": "<consumer_key>", // Private10 "client_secret": "<consumer_secret>", // Private11 "redirect_uri": "https://yourapp.bubbleapps.io/oauth_callback"12 }13}14// Response: { "access_token": "...", "refresh_token": "...", "instance_url": "https://yourorg.my.salesforce.com", "token_type": "Bearer" }1516// Call 3: SOQL Query17{18 "method": "GET",19 "url": "<instance_url>/services/data/v59.0/query",20 "headers": {21 "Authorization": "Bearer <access_token>" // Private22 },23 "params": {24 "q": "<soql_query>"25 }26}27// Response: { "totalSize": 25, "done": true, "records": [ {...}, {...} ] }2829// Example SOQL queries (URL-encode spaces as +):30// Opportunities this quarter:31// SELECT Id, Name, StageName, Amount, CloseDate, Owner.Name FROM Opportunity WHERE CloseDate = THIS_QUARTER32// Open leads:33// SELECT Id, FirstName, LastName, Email, Company, Status FROM Lead WHERE IsConverted = false ORDER BY CreatedDate DESC LIMIT 50Pro tip: Salesforce's SOQL does not support `SELECT *`. You must explicitly list every field you want to retrieve. To find the API names of fields on any Salesforce object, go to Setup → Object Manager → select the object (e.g., Opportunity) → Fields & Relationships. The 'Field Name' column shows the API name (e.g., `StageName`, not 'Stage Name').
Expected result: All four API Connector calls are configured. The SOQL Query call initializes successfully with a test access token and instance URL, showing the `records` array in the response schema. Exchange Code and Refresh Token calls are set as Actions.
Build the Salesforce login flow from your Bubble app
Users need a way to connect their Salesforce account to your Bubble app. Create a 'Connect Salesforce' button on your main dashboard or onboarding page. When clicked, this button should redirect the user to Salesforce's authorization URL. In Bubble, add a workflow to the button: action 'Open an external website' with the URL: ``` https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id=YOUR_CONSUMER_KEY&redirect_uri=https://yourapp.bubbleapps.io/oauth_callback&scope=api+refresh_token+offline_access ``` Set this to open in the same window (not a new tab) so the redirect back to your Bubble app works correctly with the URL parameter. After the user authenticates and is redirected back to your `oauth_callback` page, the Backend Workflow you created in step 2 fires automatically, exchanges the code for tokens, and stores them in Salesforce_Tokens. Now add a token refresh Backend Workflow named `Refresh Salesforce Token`. This workflow reads the current user's Salesforce_Tokens record, calls the Refresh Token API Connector call with the stored refresh_token, and updates the Salesforce_Tokens record with the new access_token and token_expiry. In every workflow that makes a Salesforce API call: 1. Check if Salesforce_Tokens for Current User is empty OR if token_expiry < Current date/time 2. If so: Schedule API Workflow → Refresh Salesforce Token (at Current date/time) 3. Then proceed with the API call using the stored access_token and instance_url
1// Authorization URL to redirect users to for Salesforce login:2// https://login.salesforce.com/services/oauth2/authorize3// ?response_type=code4// &client_id=YOUR_CONSUMER_KEY5// &redirect_uri=https://yourapp.bubbleapps.io/oauth_callback6// &scope=api+refresh_token+offline_access78// Button workflow:9// Event: Button 'Connect Salesforce' is clicked10// Action: Open an external website11// URL: [build from above template]12// Open in current tab (not new tab)1314// Token freshness check pattern in all Salesforce workflows:15// Condition: Only when Current User's Salesforce_Tokens token_expiry < Current date/time16// OR Current User's Salesforce_Tokens is empty17// Action: Schedule API Workflow → Refresh Salesforce Token → at Current date/time18// Wait for workflow completion before proceeding with API call1920// Backend Workflow: Refresh Salesforce Token21// Step 1: API Connector → Salesforce → Refresh Salesforce Token22// refresh_token: Current User's Salesforce_Tokens's refresh_token23// client_id: [Consumer Key - Private]24// client_secret: [Consumer Secret - Private]25// Step 2: Make changes to Current User's Salesforce_Tokens26// access_token = Step 1 result access_token27// token_expiry = Current date/time + 7140 seconds (119 minutes, ~2hr with buffer)Pro tip: Salesforce access token expiry varies by org settings but defaults to 2 hours (7200 seconds). Set token_expiry to 7140 seconds (119 minutes) for a 60-second safety buffer. Some Salesforce org admins set shorter session timeouts — if users report unexpected 401 errors, check the Salesforce org's Session Settings in Setup.
Expected result: Clicking 'Connect Salesforce' redirects users to Salesforce's login page. After authenticating, they are redirected back to your Bubble app with their tokens stored. The 'Connect Salesforce' button can be replaced with 'Salesforce Connected' status when Salesforce_Tokens exists for the Current User.
Query Salesforce data and display in Bubble
With the authentication infrastructure in place, you can now query Salesforce data and display it in your Bubble app. For a Repeating Group showing Opportunities, set the data source to 'Get data from an external API → Salesforce → SOQL Query.' Build the SOQL query dynamically. Remember that spaces in SOQL must be URL-encoded — Bubble handles this automatically when you set the `q` parameter value in the API call. Build the instance_url dynamically in your workflow by reading `Current User's Salesforce_Tokens's instance_url`. Pass this as the `instance_url` parameter in the API Connector call. Example SOQL for open opportunities: ```sql SELECT Id, Name, StageName, Amount, CloseDate, Account.Name, Owner.Name FROM Opportunity WHERE IsClosed = false ORDER BY CloseDate ASC LIMIT 50 ``` Salesforce wraps all results in `{ "records": [...], "totalSize": N, "done": true }`. When binding to a Repeating Group, use the `records` array. For large result sets where `done = false`, use the `nextRecordsUrl` field to fetch the next batch. For creating records (POST to /sobjects/Lead/), build the JSON body as a flat object — Salesforce does NOT require the `data` array wrapper that Zoho uses. Required fields for Lead: `LastName` and `Company`. RapidDev's team has built enterprise Salesforce integrations in Bubble — if you need guidance on specific Salesforce objects (Campaigns, Cases, Custom Objects), visit rapidevelopers.com/contact for a free scoping call.
1// SOQL queries for common Salesforce objects:23// Opportunities (open pipeline):4// SELECT Id, Name, StageName, Amount, CloseDate, Account.Name, Owner.Name FROM Opportunity WHERE IsClosed = false ORDER BY Amount DESC LIMIT 5056// Leads (recent, unconverted):7// SELECT Id, FirstName, LastName, Email, Company, Phone, Status, LeadSource FROM Lead WHERE IsConverted = false ORDER BY CreatedDate DESC LIMIT 5089// Contacts (by account):10// SELECT Id, FirstName, LastName, Email, Phone, Title, Account.Name FROM Contact WHERE AccountId = '<account_id>' ORDER BY LastName ASC1112// Create Lead (flat JSON body - no data[] wrapper):13{14 "FirstName": "<first_name>",15 "LastName": "<last_name>", // required16 "Email": "<email>",17 "Company": "<company>", // required18 "Phone": "<phone>",19 "LeadSource": "Web",20 "Status": "Open - Not Contacted"21}2223// Update Opportunity Stage (PATCH to /sobjects/Opportunity/{id}):24{25 "StageName": "<new_stage>",26 "CloseDate": "<close_date_yyyy-mm-dd>"27}Pro tip: To PATCH (update) a Salesforce record, send a PATCH request to `/services/data/v59.0/sobjects/{ObjectType}/{record_id}`. Salesforce PATCH returns HTTP 204 (no body) on success — Bubble will not show an error but you will not get a confirmation JSON either. Add a separate GET call to verify the update if needed.
Expected result: A Repeating Group in your Bubble app displays live Salesforce Opportunities or Leads fetched via SOQL. Data refreshes correctly when the access token is refreshed. Creating a Lead from a Bubble form creates a real record visible in your Salesforce CRM.
Common use cases
Custom Salesforce CRM front-end for non-technical teams
Build a simplified Bubble interface that shows each sales rep only their assigned leads and opportunities in a clean kanban-style view — without the complexity of the full Salesforce UI. Reps can update opportunity stages, log activities, and add notes directly from the Bubble app.
Create a Bubble page with a Repeating Group for Opportunities filtered by OwnerId = Current User's Salesforce ID, grouped by StageName. Each card shows the deal name, amount, close date, and a dropdown to update the Stage. Stage changes trigger a PATCH call to the Salesforce REST API.
Copy this prompt to try it in Bubble
Lead scoring dashboard pulling Salesforce data
Pull Salesforce Leads and Opportunities into a Bubble analytics dashboard that shows conversion rates, pipeline value by stage, and average deal size — all computed in Bubble from raw Salesforce data. Non-technical sales managers get the visibility they need without needing Salesforce reporting skills.
On page load, run SOQL queries to fetch all Opportunities in the current quarter. Display total pipeline value in a text element, deals by stage in a chart, and a Repeating Group of the top 10 deals by amount. Use Bubble's :sum and :count operators on the API response list.
Copy this prompt to try it in Bubble
New lead intake form that syncs to Salesforce
Replace paper or spreadsheet lead intake with a branded Bubble form for trade show events, partner portals, or marketing campaigns. Submitted leads are created directly in Salesforce as Lead records with proper source attribution, owner assignment, and campaign membership — no manual data entry required.
When a user submits the lead intake form, POST to Salesforce /sobjects/Lead/ with FirstName, LastName, Email, Company, Phone, and LeadSource fields. After success, show a confirmation screen and send an internal Slack notification with the new lead's details.
Copy this prompt to try it in Bubble
Troubleshooting
OAuth redirect returns 'redirect_uri_mismatch' error
Cause: The redirect_uri used in the authorization URL does not exactly match any of the Callback URLs registered in the Salesforce Connected App settings. Salesforce performs an exact string match — trailing slashes, http vs https, or subdomain differences all cause this error.
Solution: In Salesforce Setup → App Manager → your Connected App → view → Edit, check the 'Callback URL' field exactly. Copy the value and paste it identically into both your authorization URL `redirect_uri` parameter and the `redirect_uri` body parameter in the Exchange Code API Connector call. Remove any trailing slashes or spaces. If you switch Bubble from `bubbleapps.io` to a custom domain, update the Connected App callback URL.
SOQL query returns 'MALFORMED_QUERY' error
Cause: Common causes: using `SELECT *` (Salesforce does not support wildcard selects), referencing a field API name incorrectly (field names are case-sensitive in SOQL), or using display labels instead of API names. For example, 'Stage' is the label but `StageName` is the API name for Opportunity stage.
Solution: Go to Salesforce Setup → Object Manager → select the object → Fields & Relationships. The 'Field Name' column shows exact API names. Replace display labels with API names in your SOQL. Test your SOQL in Salesforce's built-in SOQL editor (Developer Console → Query Editor) before pasting into Bubble. Never use `SELECT *` — list every field explicitly.
1// Wrong (causes MALFORMED_QUERY):2// SELECT * FROM Opportunity3// SELECT Name, Stage, Close Date FROM Opportunity (wrong field names)45// Correct:6// SELECT Id, Name, StageName, Amount, CloseDate FROM Opportunity WHERE IsClosed = false'There was an issue setting up your call' when initializing the SOQL Query call
Cause: The Initialize call for the SOQL Query needs a real valid Salesforce access token and instance URL. If either is expired, incorrect, or placeholder, Salesforce returns 401 and Bubble shows the setup error.
Solution: Generate a fresh access token by running the full OAuth flow and copying the access_token from your Salesforce_Tokens data record. Temporarily hardcode this token as a static value in the Authorization header of the SOQL Query call during initialization. Also hardcode your Salesforce instance_url (e.g., `https://mycompany.my.salesforce.com`) as the instance_url parameter. Use a simple test query: `SELECT Id, Name FROM Account LIMIT 1`. After successful initialization, revert all hardcoded values to dynamic parameters.
401 Unauthorized on API calls even though the token was recently refreshed
Cause: Salesforce's access token expiry is controlled by the org's Session Settings in Salesforce Setup — some admins set it to 30 minutes or even 15 minutes instead of the default 2 hours. Your token_expiry calculation may be using a hardcoded 2-hour TTL that does not match the actual org setting.
Solution: Check the Salesforce org's Session Settings (Setup → Session Settings → Session Timeout). Match your token_expiry calculation to this value minus a 60-second buffer. Alternatively, always refresh the token on every page load rather than relying on the stored expiry timestamp.
Best practices
- Store the Salesforce `instance_url` alongside the access token in your Salesforce_Tokens data type. Every API call must use the user's specific instance URL (e.g., `mycompany.my.salesforce.com`) — using `login.salesforce.com` for data queries returns errors.
- Apply Bubble privacy rules to Salesforce_Tokens so end users cannot read each other's access tokens. Set 'Everyone else → no fields visible' and restrict read access to the record's owner user only.
- Cache Salesforce query results in Bubble's database for read-heavy use cases (dashboards, reports). Each SOQL call consumes both Salesforce API credits and Bubble WU. Fetching the same opportunity list on every page load is wasteful — sync to Bubble's DB on a schedule instead.
- Test SOQL queries in Salesforce's Developer Console (Developer Tools → Query Editor) before integrating into Bubble. The Developer Console shows real query results and error messages that are more helpful than Bubble's API Connector errors.
- Use SOQL `WHERE` clauses and `LIMIT` to restrict result sets. Salesforce returns up to 2,000 records per query by default, but Bubble will attempt to process all of them — large unconstrained queries slow your app and consume excessive WU.
- Implement token refresh gracefully: check `token_expiry` before every API call and refresh proactively rather than waiting for a 401 error. A failed API call mid-workflow requires error handling to retry; proactive refresh keeps workflows running smoothly.
- Register multiple Callback URLs in your Salesforce Connected App — include both your `bubbleapps.io` URL and your custom domain. This lets you test in Bubble's preview environment without switching Connected App settings.
- Backend Workflows for OAuth token exchange and refresh require Bubble Starter plan or above — factor this into your project planning. The Free plan cannot securely handle the authorization_code flow.
Alternatives
Zoho CRM uses a simpler self-client OAuth flow (no browser redirect required) and costs significantly less per user. If your team does not already use Salesforce, Zoho is the faster integration and lower cost option. Choose Salesforce only if your organization is already standardized on it or needs its enterprise-grade customization capabilities.
HubSpot offers a free CRM tier with API access and has a Bubble plugin that simplifies OAuth setup. For teams without enterprise sales complexity, HubSpot is significantly easier to integrate and has more generous free API access. Salesforce is the better choice for enterprise deal sizes, complex territory management, or when Salesforce CPQ/billing is in scope.
Both are enterprise CRMs requiring OAuth setup, but Dynamics uses Azure AD (which may already be in place for Microsoft 365 organizations) and OData v4 query syntax instead of SOQL. Choose Dynamics if your organization is Microsoft-centric; choose Salesforce if it is the established CRM of record.
Frequently asked questions
Does Salesforce API access require a paid Salesforce license?
Yes. API access in Salesforce is available on Enterprise Edition, Unlimited Edition, and Performance Edition — but NOT on Professional Edition or Essentials Edition. If you are building this integration for an existing Salesforce customer, confirm their edition first. Alternatively, create a free Salesforce Developer org at developer.salesforce.com, which includes full API access for testing and development.
Why does my SOQL query work in Salesforce but return 0 results in Bubble?
The most likely cause is that the connected user's Salesforce profile does not have visibility to the records you are querying. Salesforce has complex sharing rules that restrict which records a user can see. If your Connected App authenticates as a specific Salesforce user (via the authorization code flow), that user's record visibility applies to all API queries. Use a Salesforce admin account or a user with an appropriate profile and sharing settings.
Can I connect one Bubble app to multiple Salesforce orgs?
Yes, by storing each user's `instance_url`, `access_token`, and `refresh_token` separately in your Salesforce_Tokens data type linked to each Bubble User. Each user authenticates with their own Salesforce org via the OAuth flow, and the returned `instance_url` identifies their org. Your API calls use each user's stored instance_url dynamically.
How do I query Salesforce custom objects in Bubble?
Custom objects in Salesforce have API names ending in `__c` (two underscores then 'c'). For example, a custom object named 'Project' has the API name `Project__c`. Query it in SOQL: `SELECT Id, Name, Status__c, Due_Date__c FROM Project__c WHERE OwnerId = 'userId'`. Custom field API names also end in `__c`. Find them in Setup → Object Manager → your custom object → Fields & Relationships.
What happens when a Salesforce user's session times out?
If the Salesforce session times out (access token expires), the next API call returns a 401 error. Your token refresh Backend Workflow should handle this: detect the 401 (Bubble logs it in Workflow Logs) and trigger a refresh using the stored refresh_token. If the refresh_token itself is expired (rare — they last until revoked), the user needs to re-authenticate via the Connect Salesforce button.
Can I use Salesforce on Bubble's Free plan?
No, not fully. The OAuth authorization code flow requires a Backend Workflow to catch the `?code=` callback and exchange it for tokens — Backend Workflows are only available on Bubble Starter plan and above. Without this, there is no secure way to handle the Salesforce OAuth flow in Bubble. The Free plan cannot support this integration.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation