Skip to main content
RapidDev - Software Development Agency
bubble-integrationsBubble API Connector

Salesforce

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.

What you'll learn

  • How to create a Salesforce Connected App with the correct redirect URI pointing to your Bubble app
  • How to implement the OAuth 2.0 authorization code flow in Bubble using a login redirect and callback Backend Workflow
  • How to handle Salesforce's per-org instance subdomains in API Connector base URLs
  • How to construct SOQL queries for Leads, Contacts, Opportunities, and Accounts in Bubble
  • How to implement token refresh when Salesforce access tokens expire
  • How to handle Salesforce's response format including the `records` array wrapper
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced18 min read4–6 hoursCRM & SalesLast updated July 2026RapidDev Engineering Team
TL;DR

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.

Quick facts about this guide
FactValue
ToolSalesforce
CategoryCRM & Sales
MethodBubble API Connector
DifficultyAdvanced
Time required4–6 hours
Last updatedJuly 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

Bubble API Connector

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

1

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.

salesforce_connected_app_setup.txt
1// Connected App OAuth scopes to select:
2// api → Access and manage your data
3// refresh_token → Perform requests on your behalf at any time
4// offline_access → included with refresh_token scope
5// web → Provide access to your data via the Web
6
7// Callback URLs (add your actual Bubble app URL):
8// https://yourapp.bubbleapps.io/oauth_callback
9// https://yourcustomdomain.com/oauth_callback (if using custom domain)
10
11// After saving, find your Connected App:
12// Setup → App Manager → find 'Bubble Integration' → dropdown → View
13// Consumer Key = client_id
14// 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.

2

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

salesforce_oauth_callback.txt
1// Salesforce_Tokens data type:
2// access_token → text
3// refresh_token → text
4// instance_url → text (e.g., 'https://mycompany.my.salesforce.com')
5// token_expiry → date
6// user → User
7
8// Privacy rule: Everyone else → no fields visible
9// ✓ Ignore privacy rules when using workflows
10
11// oauth_callback page workflow:
12// Trigger: Page is loaded
13// Condition: Get URL parameter 'code' is not empty
14// Step 1: Schedule API Workflow → Exchange Salesforce Code
15// code = Get URL parameter 'code'
16// Step 2: Navigate to /main-app-page
17
18// If code is empty (user cancelled):
19// Condition: Get URL parameter 'error' is not empty
20// Step 1: Navigate to /login with error message

Pro 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.

3

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.

salesforce_api_connector_config.json
1// Call 1: Exchange Code for Token
2{
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>", // Private
10 "client_secret": "<consumer_secret>", // Private
11 "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" }
15
16// Call 3: SOQL Query
17{
18 "method": "GET",
19 "url": "<instance_url>/services/data/v59.0/query",
20 "headers": {
21 "Authorization": "Bearer <access_token>" // Private
22 },
23 "params": {
24 "q": "<soql_query>"
25 }
26}
27// Response: { "totalSize": 25, "done": true, "records": [ {...}, {...} ] }
28
29// 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_QUARTER
32// Open leads:
33// SELECT Id, FirstName, LastName, Email, Company, Status FROM Lead WHERE IsConverted = false ORDER BY CreatedDate DESC LIMIT 50

Pro 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.

4

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

salesforce_login_flow.txt
1// Authorization URL to redirect users to for Salesforce login:
2// https://login.salesforce.com/services/oauth2/authorize
3// ?response_type=code
4// &client_id=YOUR_CONSUMER_KEY
5// &redirect_uri=https://yourapp.bubbleapps.io/oauth_callback
6// &scope=api+refresh_token+offline_access
7
8// Button workflow:
9// Event: Button 'Connect Salesforce' is clicked
10// Action: Open an external website
11// URL: [build from above template]
12// Open in current tab (not new tab)
13
14// Token freshness check pattern in all Salesforce workflows:
15// Condition: Only when Current User's Salesforce_Tokens token_expiry < Current date/time
16// OR Current User's Salesforce_Tokens is empty
17// Action: Schedule API Workflow → Refresh Salesforce Token → at Current date/time
18// Wait for workflow completion before proceeding with API call
19
20// Backend Workflow: Refresh Salesforce Token
21// Step 1: API Connector → Salesforce → Refresh Salesforce Token
22// refresh_token: Current User's Salesforce_Tokens's refresh_token
23// client_id: [Consumer Key - Private]
24// client_secret: [Consumer Secret - Private]
25// Step 2: Make changes to Current User's Salesforce_Tokens
26// access_token = Step 1 result access_token
27// 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.

5

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.

salesforce_data_operations.json
1// SOQL queries for common Salesforce objects:
2
3// Opportunities (open pipeline):
4// SELECT Id, Name, StageName, Amount, CloseDate, Account.Name, Owner.Name FROM Opportunity WHERE IsClosed = false ORDER BY Amount DESC LIMIT 50
5
6// Leads (recent, unconverted):
7// SELECT Id, FirstName, LastName, Email, Company, Phone, Status, LeadSource FROM Lead WHERE IsConverted = false ORDER BY CreatedDate DESC LIMIT 50
8
9// Contacts (by account):
10// SELECT Id, FirstName, LastName, Email, Phone, Title, Account.Name FROM Contact WHERE AccountId = '<account_id>' ORDER BY LastName ASC
11
12// Create Lead (flat JSON body - no data[] wrapper):
13{
14 "FirstName": "<first_name>",
15 "LastName": "<last_name>", // required
16 "Email": "<email>",
17 "Company": "<company>", // required
18 "Phone": "<phone>",
19 "LeadSource": "Web",
20 "Status": "Open - Not Contacted"
21}
22
23// 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.

Bubble Prompt

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.

Bubble Prompt

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.

Bubble Prompt

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.

typescript
1// Wrong (causes MALFORMED_QUERY):
2// SELECT * FROM Opportunity
3// SELECT Name, Stage, Close Date FROM Opportunity (wrong field names)
4
5// 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

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire Bubble integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.