To integrate Salesforce CRM with Lovable, register a Connected App in Salesforce, use JWT bearer token flow for server-to-server authentication in your Edge Functions, and store your private key and consumer key in Cloud → Secrets. Edge Functions then proxy Salesforce REST API calls for contacts, leads, opportunities, and SOQL queries — letting you build modern UI dashboards and data entry forms on top of your Salesforce CRM data.
Build modern CRM dashboards and data tools on top of Salesforce using Lovable Edge Functions
Salesforce is the CRM system of record for tens of thousands of companies, managing contacts, leads, opportunities, accounts, cases, and custom objects built around specific business processes. But Salesforce's native interface, while powerful, has limitations for teams that want custom dashboards, simplified data entry forms, or specialized views tailored to specific workflows. Lovable lets you build those custom tools on top of Salesforce data without touching Salesforce's backend configuration.
The integration pattern is: Lovable builds the frontend, Edge Functions authenticate to Salesforce and proxy REST API calls, and Salesforce remains the system of record. Your team gets a modern React interface with custom views, better UX for their specific workflows, and direct Salesforce data integration — without switching CRMs or duplicating data.
Salesforce authentication for server-to-server integrations uses the JWT bearer token flow. Unlike username/password or OAuth2 authorization code flows, JWT bearer flow requires no user interaction — the Edge Function generates a signed JWT using an RSA private key, Salesforce validates the signature against the public key registered in a Connected App, and returns an access token. This is the enterprise-grade pattern for headless integrations and is what Salesforce recommends for API-to-API connections.
The setup has more steps than HubSpot (which uses a simpler token) because Salesforce's security model requires an RSA key pair, a Connected App registration, and user permission configuration. But once configured, the integration is stable, doesn't expire unless you rotate keys, and can be scoped precisely to the Salesforce objects and APIs your Edge Functions need.
Integration method
Salesforce has no native Lovable connector. CRM operations are proxied through Lovable Edge Functions that call the Salesforce REST API v58+. The recommended server-to-server authentication is the JWT bearer token flow: a Connected App is configured in Salesforce, and the Edge Function signs a JWT with an RSA private key stored in Cloud → Secrets to obtain an access token.
Prerequisites
- A Lovable project with at least one deployed app (Edge Functions require Lovable Cloud)
- A Salesforce Developer Edition, sandbox, or production org with System Administrator profile access
- Ability to create Connected Apps in Salesforce Setup (requires Customize Application permission)
- An RSA key pair for JWT signing — you will generate this during setup (no software installation required if using an online tool, but a local OpenSSL installation is more secure)
- Basic familiarity with Salesforce concepts: objects, records, SOQL queries, and API access permissions
Step-by-step guide
Generate an RSA key pair for JWT authentication
Generate an RSA key pair for JWT authentication
Salesforce's JWT bearer flow requires you to sign JWTs with an RSA private key and register the corresponding public key in your Connected App. You need to generate this key pair before setting up the Connected App in Salesforce. The most secure way to generate RSA keys is using OpenSSL in a terminal, but since this guide targets browser-only workflows, you can use an online RSA key generator at a trusted site like cryptotools.net or generate-random.org/rsa-key-generator. Use 2048-bit key size minimum. The output will be two blocks: a private key (starting with -----BEGIN RSA PRIVATE KEY----- or -----BEGIN PRIVATE KEY-----) and a public key (starting with -----BEGIN PUBLIC KEY----- or -----BEGIN CERTIFICATE-----). To create a self-signed certificate (which is what Salesforce requires for the Connected App): the certificate is essentially the public key wrapped in X.509 format. If you used a standard RSA key generator, you may need to also create a certificate. A simpler approach: use jwt.io's key generator, or use an online X.509 certificate generator with your public key. Copy both the full private key PEM text (everything including the -----BEGIN...----- and -----END...----- headers) and the certificate/public key text. You will paste the private key into Lovable's Secrets panel and upload the certificate to Salesforce's Connected App. Store the private key content carefully — it is the equivalent of a password. Never share it or commit it to any repository.
Pro tip: For a production integration, generate keys locally using OpenSSL if possible rather than using online tools. Online generators create keys in memory on someone else's server — for development and testing it is acceptable, but for production CRM access it is safer to generate keys offline.
Expected result: You have a PEM-format RSA private key and a corresponding X.509 certificate (public key). Keep both in a safe location — you will need the private key for Cloud Secrets and the certificate for Salesforce setup.
Create a Salesforce Connected App with JWT authentication
Create a Salesforce Connected App with JWT authentication
A Connected App in Salesforce represents your Lovable integration and defines its authentication method and API permissions. Setting it up correctly is the most involved part of the Salesforce integration. Log in to Salesforce Setup: click the gear icon in the top navigation → Setup. In the Quick Find search box, type 'App Manager' and select 'App Manager' under Apps. Click 'New Connected App' in the top-right corner. Fill in the Basic Information section: - Connected App Name: Lovable Integration (or a descriptive name) - API Name: auto-fills from the name - Contact Email: your email address In the API (Enable OAuth Settings) section: - Check 'Enable OAuth Settings' - Callback URL: https://localhost:3000 (required even for JWT-only apps) - Check 'Use digital signatures' — this enables JWT authentication - Upload your X.509 certificate file (the public key/certificate from Step 1) - Selected OAuth Scopes: add 'Manage user data via APIs (api)' and 'Perform requests at any time (refresh_token, offline_access)' Click Save. Salesforce may take a few minutes to activate the Connected App. After it is active, find the Connected App in App Manager and click 'View'. Note the Consumer Key (also called Client ID) — this is your SALESFORCE_CONSUMER_KEY secret value. Finally, you need to pre-authorize the API user: in Setup → Manage Connected Apps → your app → Manage → Edit Policies, set IP Relaxation to 'Relax IP restrictions' and in Permitted Users, set to 'Admin approved users are pre-authorized'. Then go to Setup → Users → your API user → Permission Sets and assign the Connected App permission set to that user.
Pro tip: The Connected App may take up to 10 minutes to become active in Salesforce after saving. If JWT authentication returns 'invalid_client_id' immediately after setup, wait a few minutes and try again.
Expected result: A Salesforce Connected App is created with JWT digital signatures enabled. The Consumer Key is copied. The API user is pre-authorized to use the Connected App.
Add Salesforce credentials to Lovable Cloud Secrets
Add Salesforce credentials to Lovable Cloud Secrets
Store your Salesforce credentials in Cloud Secrets. The RSA private key is particularly sensitive — it signs JWT tokens that grant API access to your Salesforce CRM data. In Lovable, click '+' next to the Preview label → Cloud panel → Secrets tab. Add the following secrets: SALESFORCE_CONSUMER_KEY: the Consumer Key from your Connected App (a long alphanumeric string) SALESFORCE_PRIVATE_KEY: the full RSA private key PEM content, including the -----BEGIN RSA PRIVATE KEY----- header and -----END RSA PRIVATE KEY----- footer. When pasting multi-line content into the secrets panel, paste the entire block. SALESFORCE_USERNAME: the Salesforce username (email) of the API user that will make the API calls. This user must be pre-authorized for the Connected App as configured in Step 2. SALESFORCE_INSTANCE_URL: your Salesforce instance URL (e.g., https://mycompany.my.salesforce.com for production, or https://mycompany--sandbox.sandbox.my.salesforce.com for sandbox). With these four secrets, your Edge Function has everything needed to generate and sign a JWT, exchange it for a Salesforce access token, and make API calls.
Pro tip: The SALESFORCE_PRIVATE_KEY secret should contain the exact PEM text from your key file including newlines. In the Secrets panel, paste the multi-line key as-is — Lovable's Secrets panel handles multi-line values correctly.
Expected result: SALESFORCE_CONSUMER_KEY, SALESFORCE_PRIVATE_KEY, SALESFORCE_USERNAME, and SALESFORCE_INSTANCE_URL appear in Cloud → Secrets with masked values.
Create the Salesforce JWT authentication and API proxy Edge Function
Create the Salesforce JWT authentication and API proxy Edge Function
Create the Edge Function that handles JWT token generation and Salesforce REST API calls. The JWT bearer flow has three steps: generate a JWT assertion, POST it to Salesforce's token endpoint, and use the returned access token for subsequent API calls. In Deno, JWT signing uses the WebCrypto API (crypto.subtle.sign) with the RSA-PSS or PKCS#1 v1.5 algorithm. Deno's crypto module supports RSA signing natively without third-party libraries. The signed JWT payload must include: iss (your consumer key), sub (the Salesforce username), aud (login.salesforce.com or test.salesforce.com for sandbox), and exp (expiry timestamp, max 5 minutes from now). After authentication, Salesforce REST API calls use the Bearer token pattern. SOQL queries use the /services/data/vXX.X/query endpoint with the SOQL string URL-encoded as a q parameter. Create, update, and delete operations use the /services/data/vXX.X/sobjects/{ObjectType} endpoints. The Edge Function below shows the complete JWT generation, token exchange, and a sample SOQL query. Deno's Web Crypto API handles RSA signing natively, so no npm packages are needed for the JWT signing step.
Create an Edge Function called salesforce-api at supabase/functions/salesforce-api/index.ts. It should generate a JWT using SALESFORCE_CONSUMER_KEY, SALESFORCE_PRIVATE_KEY, SALESFORCE_USERNAME from secrets. POST the JWT to Salesforce's token endpoint. Then accept { soql } in the POST body and execute the SOQL query using the Salesforce REST API against SALESFORCE_INSTANCE_URL. Return the query results. Include CORS headers and error handling.
Paste this in Lovable chat
1import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'23const corsHeaders = {4 'Access-Control-Allow-Origin': '*',5 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',6}78function base64UrlEncode(buffer: ArrayBuffer): string {9 return btoa(String.fromCharCode(...new Uint8Array(buffer)))10 .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')11}1213async function getSalesforceToken(14 consumerKey: string,15 privateKeyPem: string,16 username: string,17 isSandbox = false18): Promise<{ accessToken: string; instanceUrl: string }> {19 const audience = isSandbox20 ? 'https://test.salesforce.com'21 : 'https://login.salesforce.com'2223 const now = Math.floor(Date.now() / 1000)24 const header = { alg: 'RS256', typ: 'JWT' }25 const payload = { iss: consumerKey, sub: username, aud: audience, exp: now + 300 }2627 const encodedHeader = base64UrlEncode(new TextEncoder().encode(JSON.stringify(header)))28 const encodedPayload = base64UrlEncode(new TextEncoder().encode(JSON.stringify(payload)))29 const signingInput = `${encodedHeader}.${encodedPayload}`3031 // Import RSA private key32 const pemContent = privateKeyPem33 .replace(/-----BEGIN (RSA )?PRIVATE KEY-----/, '')34 .replace(/-----END (RSA )?PRIVATE KEY-----/, '')35 .replace(/\s/g, '')36 const keyBuffer = Uint8Array.from(atob(pemContent), (c) => c.charCodeAt(0))3738 const cryptoKey = await crypto.subtle.importKey(39 'pkcs8',40 keyBuffer,41 { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },42 false,43 ['sign']44 )4546 const signature = await crypto.subtle.sign(47 'RSASSA-PKCS1-v1_5',48 cryptoKey,49 new TextEncoder().encode(signingInput)50 )5152 const jwtAssertion = `${signingInput}.${base64UrlEncode(signature)}`5354 const tokenResponse = await fetch(`${audience}/services/oauth2/token`, {55 method: 'POST',56 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },57 body: new URLSearchParams({58 grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',59 assertion: jwtAssertion,60 }),61 })6263 const tokenData = await tokenResponse.json()64 if (!tokenResponse.ok) {65 throw new Error(`Salesforce auth failed: ${tokenData.error}: ${tokenData.error_description}`)66 }6768 return { accessToken: tokenData.access_token, instanceUrl: tokenData.instance_url }69}7071serve(async (req) => {72 if (req.method === 'OPTIONS') {73 return new Response('ok', { headers: corsHeaders })74 }7576 try {77 const { soql } = await req.json()7879 const consumerKey = Deno.env.get('SALESFORCE_CONSUMER_KEY')!80 const privateKey = Deno.env.get('SALESFORCE_PRIVATE_KEY')!81 const username = Deno.env.get('SALESFORCE_USERNAME')!82 const isSandbox = Deno.env.get('SALESFORCE_INSTANCE_URL')?.includes('sandbox') ?? false8384 if (!consumerKey || !privateKey || !username) {85 throw new Error('Missing Salesforce secrets')86 }8788 const { accessToken, instanceUrl } = await getSalesforceToken(89 consumerKey, privateKey, username, isSandbox90 )9192 const queryUrl = `${instanceUrl}/services/data/v58.0/query?q=${encodeURIComponent(soql)}`93 const queryResponse = await fetch(queryUrl, {94 headers: { Authorization: `Bearer ${accessToken}` },95 })9697 const queryData = await queryResponse.json()98 if (!queryResponse.ok) {99 throw new Error(`Salesforce query failed: ${JSON.stringify(queryData)}`)100 }101102 return new Response(103 JSON.stringify({ records: queryData.records, totalSize: queryData.totalSize }),104 { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }105 )106 } catch (error) {107 return new Response(108 JSON.stringify({ error: error.message }),109 { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }110 )111 }112})Pro tip: Cache the Salesforce access token in Supabase between Edge Function calls — Salesforce access tokens last 2 hours by default. Re-authenticating on every call is safe but wasteful for high-traffic apps.
Expected result: The salesforce-api Edge Function is deployed. Test it from Cloud → Edge Functions with a simple SOQL query like 'SELECT Id, Name FROM Account LIMIT 5' to verify authentication and querying work correctly.
Common use cases
Build a custom opportunity pipeline view for field sales teams
Create a simplified sales dashboard in Lovable that shows each sales rep only their own open opportunities, close dates, and next actions — pulling live data from Salesforce via Edge Functions. Field teams get a fast, mobile-friendly view of their pipeline without navigating complex Salesforce views.
Create an Edge Function called salesforce-opportunities that authenticates to Salesforce using JWT bearer flow with SALESFORCE_CONSUMER_KEY and SALESFORCE_PRIVATE_KEY from secrets. Run a SOQL query to fetch open opportunities for the current user: SELECT Id, Name, Amount, CloseDate, StageName, Account.Name FROM Opportunity WHERE OwnerId = :currentUserId AND IsClosed = false ORDER BY CloseDate ASC. Return formatted opportunity data. Then create a pipeline page showing opportunities as cards grouped by stage.
Copy this prompt to try it in Lovable
Sync new Lovable app signups to Salesforce as leads
When a user registers for your Lovable app, automatically create a Salesforce Lead record via an Edge Function. Map the user's name, email, company, and signup source to Salesforce Lead fields. Your sales team receives new leads in Salesforce in real time as users sign up, enabling immediate follow-up.
Create an Edge Function called salesforce-create-lead that accepts { firstName, lastName, email, company, leadSource } and creates a Lead record in Salesforce using JWT bearer authentication with credentials from secrets. Map leadSource to 'Web' for app signups. Return the Salesforce Lead ID. Call this Edge Function after successful user registration in my Supabase Auth signup flow.
Copy this prompt to try it in Lovable
Display Salesforce account details in a customer-facing portal
Build a customer portal in Lovable where logged-in customers can see their Salesforce account information, open cases, and recent orders. Edge Functions fetch the relevant Salesforce records for the authenticated customer (matched by email or external ID), and the React components display a clean self-service portal experience.
Create Edge Functions to fetch a Salesforce Account and its related Cases by the customer's email address. Use JWT bearer authentication with credentials from secrets. First query Account by Email: SELECT Id, Name, Phone, BillingCity, Type FROM Account WHERE PersonEmail = :email. Then query related Cases: SELECT Id, Subject, Status, Priority, CreatedDate FROM Case WHERE AccountId = :accountId ORDER BY CreatedDate DESC. Return both. Create a customer portal page displaying this information.
Copy this prompt to try it in Lovable
Troubleshooting
JWT authentication returns 'invalid_grant: user hasn't approved this consumer' error
Cause: The Salesforce user specified in SALESFORCE_USERNAME has not been pre-authorized to use the Connected App, or the Connected App's Permitted Users policy is not set correctly.
Solution: In Salesforce Setup → App Manager → your Connected App → Manage → Edit Policies: set 'Permitted Users' to 'Admin approved users are pre-authorized'. Then go to Setup → Users → the API username → Permission Set Assignments and ensure the user has a permission set that includes the Connected App. Also verify the user's profile has API Enabled permission checked.
JWT token generation throws 'NotSupportedError: importKey algorithm not supported' in Deno
Cause: The private key PEM format is PKCS#1 (starts with -----BEGIN RSA PRIVATE KEY-----) but Deno's SubtleCrypto expects PKCS#8 format (-----BEGIN PRIVATE KEY-----) for importKey with 'pkcs8' format.
Solution: Convert your key from PKCS#1 to PKCS#8 format. If you have access to OpenSSL: openssl pkcs8 -topk8 -nocrypt -in private_key.pem -out private_key_pkcs8.pem. If using only browser tools, use an online PEM converter. Alternatively, change the importKey format parameter in the code: for PKCS#1 keys, use 'raw' format with manual DER parsing, or use a library. The safest fix is to regenerate your keys in PKCS#8 format from the start.
1// For PKCS#8 keys (BEGIN PRIVATE KEY), the import works as shown2// For PKCS#1 keys (BEGIN RSA PRIVATE KEY), strip the header differently:3const pemContent = privateKeyPem4 .replace('-----BEGIN RSA PRIVATE KEY-----', '')5 .replace('-----END RSA PRIVATE KEY-----', '')6 .replace(/\s/g, '')Salesforce SOQL query returns 400 with 'MALFORMED_QUERY' error
Cause: The SOQL query syntax is incorrect, the field name does not exist on the object, or a relationship field is referenced incorrectly.
Solution: Test your SOQL query in Salesforce's Developer Console (Setup → Developer Console → Query Editor) before using it in the Edge Function. This catches syntax errors instantly. Common SOQL gotchas: field names are case-sensitive in SOQL, relationship fields use dot notation (Account.Name), and some fields require specific permissions to query. Start with a simple SELECT Id, Name FROM Account LIMIT 10 to verify the connection works, then build up to more complex queries.
Edge Function authenticates successfully but REST API calls return 403 Forbidden
Cause: The Salesforce user's profile or permission sets do not grant API access to the objects being queried. API Enabled must be checked on the profile, and object-level access (Read, Edit, Create, Delete) must be granted for each Salesforce object the Edge Function accesses.
Solution: In Salesforce Setup → Users → the API username → Profile → Object Settings, verify the user has Read (at minimum) on the objects you are querying. Also check that 'API Enabled' is checked in the profile's System Permissions section. For fine-grained control, assign a Permission Set with the specific object permissions needed rather than modifying the base profile.
Best practices
- Use a dedicated Salesforce API user (a separate Salesforce user created just for API access) rather than a real human user account — this isolates API access, makes permission management cleaner, and prevents human login issues from affecting your integration.
- Cache Salesforce access tokens between Edge Function calls — they last 2 hours by default, and re-authenticating on every API call adds unnecessary latency and consumes API request quota.
- Limit your SOQL queries to exactly the fields you need — never use SELECT *, as it fetches every field including large text fields, increasing response size and query latency.
- Always specify LIMIT clauses in SOQL queries and implement pagination for large result sets — Salesforce returns up to 2,000 records per query page by default, and unbounded queries on large orgs can time out.
- Store the Salesforce instance URL (SALESFORCE_INSTANCE_URL) as a secret and use it in all API calls — do not hardcode your Salesforce domain. This makes it easy to switch between sandbox and production environments.
- Use Salesforce sandbox environments for development and testing — never test with production API credentials, as Salesforce production has real customer data and any bugs in your Edge Functions could modify or delete live records.
- Rotate your RSA private key annually or when team members with access to the key leave — update SALESFORCE_PRIVATE_KEY in Cloud → Secrets and regenerate the Connected App's certificate in Salesforce Setup.
Alternatives
Choose HubSpot if you are building a new CRM integration without an existing Salesforce investment — HubSpot has a free tier, simpler API authentication (Private App token vs JWT), and faster setup.
Choose Salesforce Commerce Cloud integration if your use case is headless e-commerce storefronts rather than CRM contact, lead, and opportunity management.
Choose Monday.com if you need a lightweight project/CRM hybrid with a simpler API and no per-record pricing, suitable for teams that find Salesforce too complex.
Frequently asked questions
What is the difference between Salesforce CRM and Salesforce Commerce Cloud?
Salesforce CRM (Sales Cloud, Service Cloud) manages customer relationships — contacts, leads, opportunities, cases, and accounts. It is the foundation of Salesforce's product family and what most people mean when they say 'Salesforce'. Salesforce Commerce Cloud (formerly Demandware) is a separate e-commerce platform for running online storefronts, managing product catalogs, and processing orders. They are different products with different APIs, different pricing, and different use cases. This guide covers Salesforce CRM.
Why does Salesforce use JWT bearer authentication instead of a simple API token like HubSpot?
Salesforce's JWT bearer flow reflects its enterprise security model. RSA key pairs provide non-repudiation (you can prove which system made which API call), support key rotation without affecting other parts of the system, and allow fine-grained user-level authorization (the JWT specifies which Salesforce user the API acts as). For enterprise organizations with strict security requirements, this is more auditable than a static token. For smaller teams, HubSpot's simpler Private App token model may be preferable.
Can I update Salesforce records from Lovable, or only read them?
Yes — the Salesforce REST API supports full CRUD operations. Reading uses SOQL queries via GET /services/data/vXX.X/query. Creating records uses POST to /services/data/vXX.X/sobjects/{ObjectType}/. Updating uses PATCH to /services/data/vXX.X/sobjects/{ObjectType}/{recordId}. Deleting uses DELETE. The same Edge Function that handles reads can be extended to handle writes — extend the request body to include an 'action' field and the relevant Salesforce object data.
How do I avoid hitting Salesforce API limits from my Lovable app?
Salesforce limits API calls based on your edition (Developer Edition has 15,000 calls/day; Enterprise has higher limits). To stay within limits: cache frequently read data in your Supabase database rather than querying Salesforce on every page load, use Salesforce Change Data Capture events (webhooks) to push updates to your app rather than polling, and batch multiple SOQL queries where possible. Monitor your API usage in Salesforce Setup → API Usage and Limits.
Can RapidDev help with complex Salesforce integrations like bi-directional sync?
Yes — for complex Salesforce integration patterns like bi-directional data sync between Lovable/Supabase and Salesforce, Change Data Capture event processing, multi-object upsert flows, or Salesforce Platform Events, RapidDev's team can design and implement the full architecture. The pattern in this guide covers the foundational read/write proxy — production-grade Salesforce integrations often require additional error handling, retry logic, and data mapping layers.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation