· tutorials · 12 min read

Integrate Bubble and Salesforce - Tutorial

Bring data from Bubble.io to Salesforce using custom plugins.

Bring data from Bubble.io to Salesforce using custom plugins.

So if you’re looking to build a real-time sync between bubble and Salesforce, I’ve got you covered. I will show you how to bring data from a bubble database to Salesforce. By the end of this blog, you will know:

  • How to sync user data from Bubble with a Contact in Salesforce
  • How to map custom fields from bubble to custom fields in Salesforce
  • A basic understanding of how to build plugins in Bubble.

Demo

Bubble Configuration

To get started, let’s configure our Bubble app. We can start by:

  1. Creating an app
  2. Go to the data tab
  3. add fields. I am going to add some basic fields I would like to capture from a user. These are as follows:
    • First Name
    • Last Name
    • Marketing Opt Out
    • Mailing Address
    • Birthday
    • Bubble Created Date

Now that the data model is complete, we need to switch over to Salesforce to mirror the data model.

Salesforce Configuration

Salesforce ContactBubble User
FirstNameFirst Name
LastNameLast Name
EmailEmail
HasOptedOutOfEmailMarketing Opt Out
Bubble_Id__cBubble Id
MailingAddressMailing Address
BirthdayBirthday
Bubble_Created_Date__cBubble Created Date

We will be using the following mapping document to bring data over. Some of these fields are standard on the Contact. Other fields will need to be created. Specifically the fields that end in __c.

The most important field on this is the Bubble Id. To create this in Salesforce, we will want a text field, marked as an external id. We want to make this field unique, but not required. Take the maximum field length, and save.

This field will allow us to update existing records in Salesforce from Bubble once the relationship is established.

We also want to create a Bubble Created Date field as a datetime to bring over when the record was first created.

Every other field is a standard field in Salesforce. Any additional fields in your process can be created here.

Configuring the Integration User

Now that the data model is configured, we need a way of communicating between the two systems. We can use integration users inside of Salesforce to setup the authentication. This is a great way of authenticating third-party systems, and sectioning access to only this system.

We can create an integration user by going to setup -> users -> new user

We need to give it a:

  • name
  • username
  • email

Select the “integration user” license, then save. We will see an email that can be used to set the password for this user.

Next we will want to grant permissions so the integration user can see the data in Salesforce. Salesforce access is given explicity through permission sets.

We can go to permission sets in setup, then create a new permission set Bubble Integration. We need to set the license as Salesforce API Integration.

Then we can go to the contact permissions, add:

  • Create
  • Update
  • Edit
  • View All

permissions, as well as give field level security to the fields inside the mapping document.

Assign this permission set to the integration user, and we’re almost ready to go. The final thing we need is a security token to enable API access.

We can get this, by going to Login Access Policy under setup, clicking the Administrators Can Log in as Any User box, then:

  • Logging in as the user
  • Go to my settings -> reset my security token
  • and reset the security token

This will send an email to the address that was used when the integration user was initially created. Copy this down and save for later.

Bubble Plugin

Now that we have everything setup, it’s time to make the magic happen. We can user Bubble Plugins to write custom javascript for our Bubble App. This will allow us to build the Salesforce integration to our specification.

Inside of the bubble home page, we can make a new plugin. We can call this Salesforce Integration, and set a category. I like Data (things).

We will use the Actions, to store a step that will be later used in our workflow. We can create an action, and give it a name like Create Contact (SF). Then, we can set the action type as Server Side (this is important), and the category as Data (things).

We can use fields as a way of passing data from our bubble application, to the plugin. We can create the field user to pass in the user object. This needs to be a dynamic value with the type of User.

Additionally, we can use Returned Values as a way of passing our data from the plugin, to further steps down in our workflow. Let’s create the following fields:

  • properties: which will store data about the object
  • success: which will let us know if the contact was inserted correctly
  • error_message: which will contain information in case there is an error.

Now that the configuration for the plugin is complete, let’s add some simple code that will allow us to understand the shape of the data inside of bubble.

We can use the following code to understand what data is being passed to our plugin.

function(properties, context) {
  return {
    properties: JSON.stringify(properties),
  }
}

But to use this code, we need to add it to our app. We can do so under the settings tab, and set the Testing App to the name of our bubble application.

We also want to submit the id of the Application, and press Authorize.

Now, we can go back to our app, go to the plugin tab, and see the plugin installed.

Creating Database Trigger

We use database triggers to run when a record in bubble is updated.

First, let’s enable backend workflows.

Then we can use a database trigger to perform an action when a record in Bubble is created or updated. To create a database trigger we need to

  1. Go to the app
  2. then workflow tab
  3. Search for Backend workflows
  4. Press Click here to add a backend workflow
  5. Select the type New database trigger event...
  6. Give an event name: Sync User w/ SF
  7. Select a type User
  8. Select event color Blue

Now it’s time to add actions to your workflow. Click on the newly created workflow and:

  1. Make step 1 your plugin action Create Contact (SF)
  2. Make step 2 an email with the body being the properties of the user from step 1.

Now we can go to data, app data, and edit a record, and save.

You should receive an email with properties like so:

{
  "user": {
    "_pointer": {
      "_id": "1720037193884x290330829617143900",
      "_type": "user",
      "_source": {
        "birthday_date": "2000-01-01T03:01:00.000Z",
        "company_name_text": "1Sync",
        "first_name_text": "Justin",
        "last_name_text": "Wills",
        "mailing_address_geographic_address": {
          "address": "123 Main St, San Diego, CA 92111, USA",
          "lat": 32.6818884,
          "lng": -117.1094375
        },
        "marketing_opt_out_boolean": true,
        "email": "justin@1sync.co",
        "Created Date": "2024-07-03T20:06:33.884Z",
        "Modified Date": "2024-07-03T20:06:33.957Z",
        "_id": "1720037193884x290330829617143900",
        "logged_in": true,
        "_type": "user"
      }
    },
    "_call_metadata": {
      "server_base_url": "https://salesforce-demo-65810.bubbleapps.io/version-test/",
      "access_token": "bus|1720032805687x456783999487548860|1720037194854x448491190633149000",
      "appname": "salesforce-demo-65810",
      "app_version": "test",
      "plugin_api_version": "4",
      "app_properties": {
        "user": [
          "birthday_date",
          "company_name_text",
          "first_name_text",
          "last_name_text",
          "mailing_address_geographic_address",
          "marketing_opt_out_boolean",
          "email",
          "Slug",
          "Created Date",
          "Modified Date",
          "_id",
          "logged_in"
        ]
      }
    },
    "single_api": true,
    "list_api": false
  }
}

We can use this json text to understand how to access data from the user value. We can use the dot notation to access this data inside the plugin editor. For example, this expression

properties.user._pointer._source.first_text;

will output the following string:

"Justin"

Plugin Debugging

We can also check the debug logs to confirm that the plugin has ran. Inside the Logs section, we can go to the Server Logs tab, click Show advanced, and check the following settings:

  • Plugin Server side output
  • Plugin Server side error

After searching for logs, we can see the results of the plugin running.

Connecting Bubble And Salesforce

Now that we understand how to access and read the data inside Bubble, let’s move to integrating this with Salesforce.

We need to add the integration user’s credentials inside our plugin code. We can use Keys inside our plugin to share this data between multiple plugin actions.

To create keys, go to the Shared tab inside of the plugin editor, and find the Additional keys section. Here we will want to make the following keys to store the necessary data:

  • SF_USERNAME
  • SF_PASSWORD
  • SF_TOKEN
  • SF_URL

We want all of these keys to have a type of private.

Inside the action, we can now access these values. There is a window inside the app that allows for users to input these values.

Now let’s connect the plugin to Salesforce. We will be using the popular JavaScript library jsforce to streamline our api calls.

We can check the box to confirm that the plugin will use node modules, then add the dependency of jsforce:

{
  "dependencies": {
    "jsforce": "1.11.1"
  }
}

Now, we can instantiate jsforce with our login credentials. We can make the function async, then instantiate the function as follows:

async function(properties, context) {

  var jsforce = require('jsforce');
  var conn = new jsforce.Connection({
    loginUrl: context.keys.SF_URL
  });
}

This will import the jsforce library, then create a connection to the Salesforce login url. This will be:

  • https://login.salesforce.com
  • https://test.salesforce.com depending if you are connecting to a sandbox or production environment respectively.

Next, we want to authenticate with Salesforce, using our credentials. We can use the following code to perform this:

await conn.login(context.keys.SF_USERNAME, context.keys.SF_PASSWORD + context.keys.SF_TOKEN).then(async () => {
  // do stuff
});

We pass in the username for the username field, and the password and access token concatenated for the password.

We can then perform additional operations on this connection using the then() operation in a javascript promise.

Finally, let’s showcase how to call Salesforce data. We can use the find method to run a SOQL query against our Salesforce data, returning any data found.

Let’s run the following query:

var contact_records = await conn
  .sobject('Contact')
  .find(
    // conditions in JSON object
    { Email: properties.user._pointer._source.email },
    // fields in JSON object
    { Id: 1 }
  )
  .execute(function (err, records) {
    if (err) {
      error_message = err;
      return console.error(err);
    }
    console.log('fetched : ' + records.length);
    return records;
  });

then let’s:

  1. create a variable output at the top of the file,
  2. assign the contact_records to the output.
  3. Return the JSON.stringify() output in our properties value.

Then, inside our app, we can:

  1. go to the plugin settings
  2. fill our keys using the data from Salesforce
  3. Edit and save a record.

This will result in an email, showing a list of contacts retrieved from Salesforce. Keep in mind we need to change a value of a field to trigger the workflow. In this case, I am adding a space to the name.

Mapping Bubble and Salesforce fields

Now that the connection between the two systems is established, let’s map the fields between the two systems.

First, let’s create variables that we can assign to use as outputs for reading the data outside the plugin.

Then, we can map text fields like

  • first name
  • last name
  • etc.

using the dot notation .

Let’s create an object fields, and map the text fields accordingly:

const fields = {
  FirstName: properties.user._pointer._source.first_text,
  LastName: properties.user._pointer._source.last_text,
  Email: properties.user._pointer._source.email,
  Bubble_Id__c: properties.user._pointer._source._id,
};

Next, let’s map the boolean fields like Marketing Opt Out.

While we can use the same mapping as before, bubble has a weird behavior. Falesy values are not included in the data inside the plugin. If a user opt’s out of marketing, mapping fields directly will work as expected. If a user then decides they want to then receive marketing emails, this value will not carry over into the plugin. Thus, on the Salesforce side it will be as if the user never opted into marketing again.

To remedy this, we can assign the variable as an expression to see if it is undefined

let opt_out = !(properties.user._pointer._source.marketing_opt_out_boolean === undefined);

then assign to the fields function:

const fields = {
  //...additional fields
  HasOptedOutOfEmail: opt_out,
};

For date and date time fields, we can map the values as expected. Because the Created Date field inside of bubble has a space, we need to use bracket notation to access the property.

const fields = {
  //...additional fields
  Bubble_Created_Date__c: properties.user._pointer._source['Created Date'],
  Birthdate: properties.user._pointer._source.birthday_date,
};

and finally for address fields, we can map like so:

const fields = {
  //...additional fields
  MailingStreet: properties.user._pointer._source.mailing_address_geographic_address.address,
  MailingLatitude: properties.user._pointer._source.mailing_address_geographic_address.lat,
  MailingLongitude: properties.user._pointer._source.mailing_address_geographic_address.lng,
};

then, we can upsert the contact record into Salesforce. Using the upsert function, we can match the update against the bubble id, so future updates will use the created record and not create duplicates.

We can use the following:

await conn
  .sobject('Contact')
  .upsert(fields, 'Bubble_Id__c')
  .then((ret, err) => {
    if (!ret.success || err) {
      error_message = JSON.stringify(ret);
    } else {
      error_message = JSON.stringify(ret);
      success = ret.success;
    }
  })
  .catch((err, result) => {
    error_message = JSON.stringify(err);
  });

Where we specify the Salesforce object, then use the upsert function with the fields, and the external id specified.

We can then write an error message if the callout is not successful, otherwise mark the callout as successfuly

We can also catch any authentication errors, and write the error message as well.

Now when we update and save a record inside the bubble editor, the data brought over from Bubble to Salesforce.

Error Logging

While it would be nice to assume that the integration will function perfectly every time, sometimes it’s better to plan for a rainy day.

Many of the objects we created initially can be used to inform users if there is an error.

We can leverage the variables and email function created earlier to create the error logging system.

Using the email action, we can create a new workflow step.

We can have a subject, email target, and use merge fields from the previous workflow step to add data from our salesforce plugin.

Finally, we can trigger this work flow when success = false, so the error email is only sent when there is an error.

Deploying the plugin To Production

Once we are happy with the plugin functionality, we can publish the plugin to make it available for our production app.

Let’s go to the Settings tab in our plugin, and Submit a new version. We can give it a nice description, and save.

We will need to then install this plugin inside our app. Let’s go to the plugins tab inside of our app, and add a plugin. We can immediately see the plugin we created. Let’s install the plugin, and then update our workflows to include the action from the new plugin.

Conclusion

And that’s everything you need to build an integration with Bubble and Salesforce.

Need Our Help To Get Your Data Into Salesforce?

Join dozens of other companies by learning how you can get all your company's data in one place.

Back to Blog