Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Integrate FlutterFlow with Salesforce CRM

Connect FlutterFlow to Salesforce by building a Cloud Function that handles OAuth 2.0 authentication and proxies API calls to Salesforce REST API v58.0. The Cloud Function manages token refresh every 2 hours. Use endpoints GET /sobjects/Contact for contacts, POST /sobjects/Lead to create leads, and PATCH /sobjects/Opportunity/{id} to update deals. Mirror data to Firestore for offline access.

What you'll learn

  • How to build a Cloud Function that authenticates with Salesforce OAuth 2.0 and proxies API calls securely
  • How to query Salesforce contacts, create leads, and update opportunity stages from FlutterFlow
  • How to sync Salesforce data to Firestore for offline access and faster reads in the mobile app
  • How to receive Salesforce Outbound Messages via Cloud Function to keep Firestore data current
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner12 min read45-60 minFlutterFlow Free+ (Firebase Cloud Functions required)March 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to Salesforce by building a Cloud Function that handles OAuth 2.0 authentication and proxies API calls to Salesforce REST API v58.0. The Cloud Function manages token refresh every 2 hours. Use endpoints GET /sobjects/Contact for contacts, POST /sobjects/Lead to create leads, and PATCH /sobjects/Opportunity/{id} to update deals. Mirror data to Firestore for offline access.

Build a mobile Salesforce CRM companion app in FlutterFlow

Sales reps need quick mobile access to their Salesforce data — viewing contacts before a meeting, logging a call note immediately after, or updating a deal stage on the drive back to the office. FlutterFlow can power this use case, but Salesforce's OAuth tokens expire every 2 hours and the client_secret must never appear in mobile app code. The solution is a Cloud Function that handles all Salesforce authentication server-side and exposes clean REST endpoints for FlutterFlow to call. This tutorial walks through building the Cloud Function proxy, querying and writing Salesforce objects, syncing data to Firestore for offline access, and receiving Salesforce Outbound Messages when records change.

Prerequisites

  • A Salesforce org (Developer Edition is free at developer.salesforce.com) with at least a few Contact and Opportunity records
  • A Connected App created in Salesforce: Setup → App Manager → New Connected App with OAuth enabled and callback URL set to https://login.salesforce.com/services/oauth2/success
  • Firebase project with Cloud Functions enabled (requires Blaze pay-as-you-go billing)
  • Node.js installed locally for writing and deploying Cloud Functions

Step-by-step guide

1

Create the Salesforce Connected App and get OAuth credentials

In your Salesforce org, go to Setup (gear icon top-right) → App Manager → New Connected App. Fill in: Connected App Name (e.g., FlutterFlowMobile), Contact Email (your email), Enable OAuth Settings (check the box), Callback URL (https://login.salesforce.com/services/oauth2/success). Under Selected OAuth Scopes, add: Access and manage your data (api), Perform requests at any time (refresh_token, offline_access). Save. Salesforce takes 2-10 minutes to activate the app. After activation, click Manage → View Consumer Key and Consumer Secret — copy both. These are your client_id and client_secret for OAuth. In your Firebase project, go to Cloud Functions → open your functions/index.js file (or create it) and store these as environment config: run firebase functions:config:set salesforce.client_id='YOUR_ID' salesforce.client_secret='YOUR_SECRET' salesforce.username='your@email.com' salesforce.password='yourpassword+securitytoken' from your terminal. The security token is found in Salesforce: Settings (top-right avatar) → My Personal Information → Reset My Security Token.

Expected result: Connected App is active in Salesforce and OAuth credentials are stored securely in Firebase Functions config — not in FlutterFlow or app code.

2

Build the Cloud Function proxy that handles Salesforce OAuth

Create a Cloud Function named salesforce that authenticates with Salesforce's username-password OAuth flow and proxies requests. The function accepts a POST request from FlutterFlow with a JSON body specifying the Salesforce operation: {action: 'getContacts'}, {action: 'createLead', data: {...}}, {action: 'updateOpportunity', id: '...', data: {...}}. The function first calls Salesforce's token endpoint to get a fresh access_token and instance_url, then uses those to make the actual API call, then returns the result to FlutterFlow. Cache the access token in Firestore (with the expiry time) and reuse it until 5 minutes before expiry to avoid a new OAuth call on every request.

functions/index.js
1// functions/index.js — Salesforce Cloud Function proxy
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4const axios = require('axios');
5
6admin.initializeApp();
7
8const SF_CONFIG = {
9 loginUrl: 'https://login.salesforce.com',
10 apiVersion: 'v58.0',
11};
12
13// Get or refresh Salesforce access token
14async function getSalesforceToken() {
15 const db = admin.firestore();
16 const tokenDoc = await db.collection('_sf_tokens').doc('current').get();
17 const now = Date.now();
18
19 // Reuse cached token if valid for 5+ more minutes
20 if (tokenDoc.exists) {
21 const { accessToken, instanceUrl, expiresAt } = tokenDoc.data();
22 if (expiresAt > now + 300000) {
23 return { accessToken, instanceUrl };
24 }
25 }
26
27 // Request new token via username-password flow
28 const cfg = functions.config().salesforce;
29 const params = new URLSearchParams({
30 grant_type: 'password',
31 client_id: cfg.client_id,
32 client_secret: cfg.client_secret,
33 username: cfg.username,
34 password: cfg.password,
35 });
36 const resp = await axios.post(
37 `${SF_CONFIG.loginUrl}/services/oauth2/token`,
38 params.toString(),
39 { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
40 );
41
42 const { access_token, instance_url } = resp.data;
43 // Cache token — Salesforce tokens valid for ~2 hours
44 await db.collection('_sf_tokens').doc('current').set({
45 accessToken: access_token,
46 instanceUrl: instance_url,
47 expiresAt: now + 7200000, // 2 hours
48 });
49 return { accessToken: access_token, instanceUrl: instance_url };
50}
51
52exports.salesforce = functions.https.onRequest(async (req, res) => {
53 res.set('Access-Control-Allow-Origin', '*');
54 if (req.method === 'OPTIONS') { res.status(204).send(''); return; }
55
56 try {
57 const { action, id, data, query } = req.body;
58 const { accessToken, instanceUrl } = await getSalesforceToken();
59 const base = `${instanceUrl}/services/data/${SF_CONFIG.apiVersion}`;
60 const headers = { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' };
61
62 let result;
63 if (action === 'getContacts') {
64 const soql = `SELECT Id,Name,Email,Phone,Account.Name FROM Contact ORDER BY LastModifiedDate DESC LIMIT 50`;
65 const r = await axios.get(`${base}/query?q=${encodeURIComponent(soql)}`, { headers });
66 result = r.data.records;
67 } else if (action === 'createLead') {
68 const r = await axios.post(`${base}/sobjects/Lead`, data, { headers });
69 result = r.data;
70 } else if (action === 'updateOpportunity') {
71 await axios.patch(`${base}/sobjects/Opportunity/${id}`, data, { headers });
72 result = { success: true };
73 } else if (action === 'getOpportunities') {
74 const soql = `SELECT Id,Name,StageName,Amount,CloseDate,AccountId FROM Opportunity ORDER BY LastModifiedDate DESC LIMIT 50`;
75 const r = await axios.get(`${base}/query?q=${encodeURIComponent(soql)}`, { headers });
76 result = r.data.records;
77 }
78 res.json({ success: true, data: result });
79 } catch (err) {
80 console.error('Salesforce CF error:', err.response?.data || err.message);
81 res.status(500).json({ success: false, error: err.message });
82 }
83});

Expected result: Cloud Function is deployed and returns Salesforce contact data when called with {action: 'getContacts'} from Postman or curl.

3

Configure the FlutterFlow API call to your Cloud Function

In FlutterFlow, go to API Manager → Add API Group. Name it SalesforceProxy. Set the Base URL to your deployed Cloud Function URL — it looks like https://us-central1-{your-project}.cloudfunctions.net. Set the Method to POST. Under Headers, add Content-Type: application/json. Add an individual API Call named getContacts. Under Request Body, add a JSON body: {"action": "getContacts"}. Click Test — the response should show your Salesforce contacts as a JSON array. Similarly add createLead with body {"action": "createLead", "data": {"FirstName": "[firstName]", "LastName": "[lastName]", "Company": "[company]", "Email": "[email]"}} using FlutterFlow's variable syntax [variableName] for the dynamic fields. Add updateOpportunity with body {"action": "updateOpportunity", "id": "[opportunityId]", "data": {"StageName": "[stageName]"}}. Extract response JSON paths: for getContacts, FlutterFlow will parse the data array — mark the fields you need (Id, Name, Email, Phone) in the response inspector.

Expected result: SalesforceProxy API Group shows three API Calls, each testing successfully and returning the expected Salesforce data in the response inspector.

4

Build the contacts ListView and lead creation form

Create a Contacts page in FlutterFlow. Add a Backend Query to the page using the getContacts API call. Add a ListView widget — in its properties, click Generate Dynamic Children and bind to the query result's data array. Each generated child is a ContactCard component: add a Column widget containing a Text widget bound to item.Name, a Row with an email icon + Text bound to item.Email, a phone icon + Text bound to item.Phone, and a Text bound to item.Account.Name in grey. Add an onTap action to the Column that navigates to a ContactDetail page passing item.Id and item.Name as page parameters. For lead creation: add a floating action button on the Contacts page. On Tap: Show Bottom Sheet containing a form with TextFields for firstName, lastName, company, and email, plus a Submit Button. Submit Button Action Flow: call createLead API with the TextField values, show a SnackBar 'Lead created in Salesforce', dismiss the bottom sheet, and refresh the Backend Query on the parent page.

Expected result: Contacts page shows a list of Salesforce contacts with names, emails, and company names. The FAB opens a lead creation form that creates a real Salesforce Lead record.

5

Sync Salesforce data to Firestore for offline access

Mobile apps need offline access — if the user opens the app on the subway with no signal, they should still see their contact list from the last sync. Add a syncSalesforceData Cloud Function that fetches contacts and opportunities from Salesforce and writes them to Firestore collections sf_contacts and sf_opportunities. Schedule it with Cloud Scheduler to run every 15 minutes using a Pub/Sub trigger. In FlutterFlow, change the Backend Query on the Contacts page to use Firestore (read from sf_contacts collection) instead of the direct API call — this gives instant display from Firestore cache. Add a pull-to-refresh gesture on the ListView: On Refresh → call the SalesforceProxy getContacts API → write each contact to Firestore sf_contacts/{id} document. The Cloud Scheduler ensures data stays current even when the user does not manually refresh.

Expected result: Contacts page loads instantly from Firestore. Pull-to-refresh updates Firestore from live Salesforce data. Data is accessible offline from the last sync.

Complete working example

Salesforce Integration Architecture
1Cloud Function: salesforce (HTTPS onRequest)
2
3Actions handled:
4 getContacts SOQL: SELECT Id,Name,Email,Phone,Account.Name
5 FROM Contact ORDER BY LastModifiedDate DESC LIMIT 50
6 getOpportunities SOQL: SELECT Id,Name,StageName,Amount,CloseDate
7 FROM Opportunity ORDER BY LastModifiedDate DESC LIMIT 50
8 createLead POST /sobjects/Lead
9 { FirstName, LastName, Company, Email, Phone }
10 updateOpportunity PATCH /sobjects/Opportunity/{id}
11 { StageName: 'Closed Won' | 'Closed Lost' | etc. }
12
13OAuth: username-password flow cached in Firestore _sf_tokens/current
14Token refresh: auto-refresh when token expires within 5 minutes
15
16Firestore Sync Collections
17
18sf_contacts/{salesforceId}
19 name, email, phone, accountName, lastSyncedAt
20
21sf_opportunities/{salesforceId}
22 name, stageName, amount, closeDate, accountId, lastSyncedAt
23
24sf_metadata/sync
25 contactsLastSync: Timestamp
26 opportunitiesLastSync: Timestamp
27
28FlutterFlow Pages
29
30Contacts
31 Backend Query: sf_contacts (Firestore, real-time)
32 ListView ContactCard (name, email, phone, company)
33 FAB Create Lead Bottom Sheet
34 Fields: firstName, lastName, company, email
35 Submit createLead API SnackBar dismiss
36
37ContactDetail (param: contactId, contactName)
38 Backend Query: sf_contacts/{contactId} (Firestore)
39 Display: full contact info + recent activities
40 Button: Create Follow-up creates Salesforce Task
41
42Opportunities
43 Backend Query: sf_opportunities (Firestore)
44 ListView OpportunityCard (name, stage, amount, closeDate)
45 DropDown (StageName) On Change updateOpportunity API
46
47API Manager: SalesforceProxy
48
49Base URL: https://us-central1-{project}.cloudfunctions.net
50Method: POST
51Header: Content-Type: application/json
52
53Calls:
54 getContacts { "action": "getContacts" }
55 createLead { "action": "createLead", "data": {...} }
56 updateOpportunity { "action": "updateOpportunity", "id": "[id]", "data": {...} }

Common mistakes

Why it's a problem: Making Salesforce API calls directly from FlutterFlow's API Manager without a Cloud Function, putting the client_secret in the API Group headers

How to avoid: All Salesforce API calls must go through a Cloud Function. The Cloud Function stores client_id, client_secret, username, and password in Firebase Functions config (server-side secrets), handles token refresh transparently, and exposes a clean action-based REST API for FlutterFlow to call.

Why it's a problem: Querying Salesforce directly on every page load instead of reading from the Firestore sync cache

How to avoid: Sync Salesforce data to Firestore on a schedule (every 15 minutes via Cloud Scheduler). Read from Firestore in FlutterFlow for instant display. Only call the Salesforce API directly when the user explicitly triggers a write operation (creating a lead, updating a stage) or manually refreshes.

Why it's a problem: Using the deprecated Salesforce API key (Simple_API_Key) instead of the Connected App OAuth credentials

How to avoid: Create a Connected App in Setup → App Manager → New Connected App with OAuth enabled. Use the Consumer Key as client_id and Consumer Secret as client_secret in the OAuth token request. The username-password OAuth flow works without user interaction, suitable for server-to-server integration.

Best practices

  • Always proxy Salesforce API calls through a Cloud Function — never put OAuth credentials or access tokens in FlutterFlow's API Manager headers
  • Cache Salesforce access tokens in Firestore with their expiry time and reuse them across requests to minimize OAuth round-trips
  • Sync read-heavy data (contacts, opportunities) to Firestore on a schedule so the app loads instantly and works offline
  • Use SOQL LIMIT clauses in all queries — SELECT * without LIMIT can return thousands of records and hit Salesforce's batch size limit of 2,000 records per query
  • Set up Salesforce Outbound Messages for records that change frequently (opportunity stage changes, contact updates) so your Firestore cache stays current without polling
  • Log every Salesforce write operation (createLead, updateOpportunity) to a Firestore audit_log collection with userId, timestamp, action, and recordId for accountability
  • Test with Salesforce Developer Edition (free sandbox) before connecting to a production org — mistakes in production Salesforce can affect live CRM data

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I am building a FlutterFlow app that needs to integrate with Salesforce CRM. Write a Firebase Cloud Function in Node.js that: (1) authenticates with Salesforce using the OAuth 2.0 username-password flow to get an access token, (2) caches the access token with its expiry time and refreshes it automatically, (3) handles these actions via POST request: getContacts (SOQL query for Id, Name, Email, Phone, Account.Name), createLead (POST to sobjects/Lead), updateOpportunity (PATCH to sobjects/Opportunity). Include error handling and CORS headers.

FlutterFlow Prompt

Create a page called Contacts with a Backend Query that reads from the sf_contacts Firestore collection. Add a ListView that displays each contact's name, email, and company in a card. Add a floating action button that opens a bottom sheet form with fields for first name, last name, company name, and email, with a Submit button that calls the SalesforceProxy createLead API call.

Frequently asked questions

Do I need a paid Salesforce license to use the REST API?

Salesforce Developer Edition is free and includes full REST API access — it is perfect for development and testing. For production apps used by your sales team, each user needs at least a Salesforce Professional Edition license ($75/user/month). The API call limits vary by edition: Essentials (no API access), Professional (1,000 API calls/day per license), Enterprise (15,000 API calls/day per org + 1,000 per license). Developer Edition has 15,000 API calls/day.

Can FlutterFlow users log in with their own Salesforce credentials?

Yes, using the Salesforce OAuth 2.0 web server flow instead of the username-password flow. The user taps 'Log in with Salesforce' → your Cloud Function redirects them to Salesforce's login page → they authenticate → Salesforce redirects back with an auth code → Cloud Function exchanges the code for access + refresh tokens specific to that user. Each user's access token is scoped to their own Salesforce data and permissions. This is more complex than the single service-account approach covered in this tutorial.

How do I handle Salesforce sandbox vs production environments?

Salesforce sandbox uses login.salesforce.com for production and test.salesforce.com for sandbox. In your Cloud Function, store the login URL as an environment variable: firebase functions:config:set salesforce.login_url='https://test.salesforce.com' for sandbox builds and 'https://login.salesforce.com' for production. Use separate Firebase projects (and thus separate Cloud Functions) for your development and production environments.

What Salesforce objects can I access via the REST API?

All standard Salesforce objects: Account, Contact, Lead, Opportunity, Case, Task, Event, User, Campaign, Contract, and hundreds more. Also any custom objects your org has created — they appear as CustomObjectName__c in the API. Use GET /services/data/v58.0/sobjects/ to get a list of all objects your Connected App has permission to access. Salesforce's REST API covers the full CRM data model.

How do I handle Salesforce SOQL queries with complex filters?

Build the SOQL query string in your Cloud Function using JavaScript template literals. SOQL syntax: SELECT Fields FROM ObjectName WHERE Condition ORDER BY Field LIMIT N. For example: SELECT Id,Name FROM Contact WHERE Email LIKE '%@apple.com' AND LastModifiedDate > 2024-01-01T00:00:00Z ORDER BY Name ASC LIMIT 100. URL-encode the query with encodeURIComponent() before appending to the /query? endpoint. For parameterized queries, build the string with variables: const soql = SELECT ... WHERE OwnerId = '${userId}' — this is safe inside the Cloud Function since the userId comes from authenticated Firebase calls.

Can RapidDev build a complete Salesforce mobile CRM companion app in FlutterFlow?

Yes. A production Salesforce mobile companion with user login via Salesforce OAuth, full CRUD on contacts/opportunities/tasks/cases, offline sync with conflict resolution, push notifications on record assignments, and custom Salesforce object support goes well beyond this basic integration. RapidDev has built Salesforce-connected FlutterFlow apps for field sales teams and can architect a solution that stays within your org's API limits.

RapidDev

Talk to an Expert

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

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

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.