9
May
2022
Tutorial

Build a Low Code Brex Client to Automate Operations

0
 minutes

Some things are fantastic together. 🍔🥤

In particular, REST APIs and Low code dovetail nicely.

Although APIs simplify development, they also demand a level of programming skill and technical ability. Low code frameworks like Appsmith can provide a visual layer on top of the API that empowers non-technical audiences and citizen developers to build client applications.

There is much value in simplifying the development process where we can. Low code works best for development projects where the problem domain is well understood and there are very few use cases. REST API clients are well suited to that extent - the API contract itself serves as a kind of blueprint for the client application. Take, for instance, the Brex APIs.

Brex offers several APIs that range from Onboarding and Payments to Teams and Transactions. Brex admins can plug into these APIs to build powerful workflows and custom automations. To demonstrate, we'll build an Appsmith dashboard that will connect to your Brex account.

Key Takeaways

  • Appsmith has a REST interface that allows us to create and modify query objects. A query object is a portable, reusable construct that defines parameters needed for a single API call. We'll use several query objects to engage the Brex APIs.
  • Building a multi-page interface with Appsmith is incredibly simple and well-documented. The platform provides a visual way to build pages using widgets and drag/drop functionality. However, unlike the global Appsmith store, page resources are page-scoped and cannot be referenced across different pages. Despite that, it is still possible to move or copy resources from one page to another.
  • We'll use JavaScript Promises to simplify asynchronous workflows. This will enable us to launch multiple API calls concurrently and then handle the responses elegantly. Normally, this is a challenge because API calls rely on the network and conclude at inexact points in time. To overcome this, we used to apply callback functions, but JavaScript Promises are an improvement over callbacks.

What are we building?

In this tutorial, we'll create an Appsmith tool that leverages the Brex APIs to perform bulk operations on your Brex account. By uploading a CSV file, you'll be able to create credit cards, users, and vendors. These changes will be reflected in your Brex dashboard immediately. Our tool will also support terminating cards and deleting vendors.

As it is not intended for production use, this application doesn't attempt to be either complete or systematic. It is more like a demo project. Rather than serve as a proper software solution, the final application is meant to demonstrate what is possible using Appsmith and the Brex APIs.

For context, this tutorial depicts the reader as a fictional Brex customer in order to provide a frame of reference and bring meaning to the sample CSV files. Pretend that you are an outer space company that issues Brex credit cards to your colleagues. The cardholders have personal cards as well as other cards designated for expenses such as Equipment Repairs, Launch Services, and Telecom. You will also define fictional Vendors: General Atomics, Orbital Transport Services, Space Food Systems. Let's suppose you are building this application to automate a few back office operations. You have chosen to build it using Appsmith.

What do I need?

First, you'll want to sign into Appsmith and create a new application. Next, sign in to your Brex account and generate a user token with Read/Write permissions for Cards, Referrals, Users, and Vendors. Appsmith will use this token to authorize API calls.

To confirm that your token is working, you can perform a quick sanity check with curl:

COPYcurl --request GET 'https://platform.brexapis.com/v2/users/me' \
--header 'Authorization: Bearer <your-token>'

The response will resemble the following:

COPY{
   "id": "cuuser...",
   "first_name": "Michael",
   "last_name": "Collins",
   "email": "mcollins@example.com",
   "status": "ACTIVE"
}

Postman

Alternatively, you can use Postman to test API calls. In fact, the Brex Developer workspace allows you to experiment with every endpoint documented in the Brex Developer Portal.

Appsmith Echo API

Consider using the Appsmith Echo API to spare production API calls.

COPY[POST] https://mock-api.appsmith.com/echo/post

While testing your Appsmith tool, sometimes you will need to invoke non-idempotent API endpoints such as Create card and Invite user. Instead of exercising the live endpoints, you can create an API datasource that relies on the Appsmith Echo API:

COPYfor(let i = 0; i < 5; i++) {
  echo_post.run(onSuccess, onError, params);
}

To mock HTTP status codes and errors, you might try httpstat.us.

Disposable Inbox

For testing user invites, I recommend a disposable email system like Mailinator or Temp Mail.

Git Repo

There's a companion repo that contains sample CSV files and supporting JavaScript code for this tutorial. You can also use Appsmith's Import-Export feature to migrate the demo application to your own account.

Brex APIs

The base URL for all Brex APIs is https://platform.brexapis.com.

  1. Use this URL to create an authenticated API datasource called brex-prod.
  2. Select Bearer Token for the Authentication Type.
  3. Supply your Brex user token.
  4. Click Save.

You should now have a datasource that we can use to create Queries.

New query objects will show in the left side panel, under QUERIES/JS. For example, here I've created a query for the Get current user endpoint:

get-current-user-api-pane

Now, you can create new query objects from the brex-prod datasource. The following search box will appear when you click the + icon next to the QUERIES/JS menu option:

brex-prod-datasource

Going forward, we'll create several queries to support our application.

Onboarding API

The Onboarding API allows you to refer your customers and personal contacts to Brex. You might use the Create referral endpoint as part of the Brex Referral Program.

Unlike the Get current user endpoint, Create referral is a POST endpoint that requires a JSON body.

create-referral-json-body

Later on, we'll discuss this.params. For now, understand that it is possible to run the create_referral query object and pass parameters into it, like so:

COPYconst params = { referral_code, email, first_name, last_name };
return create_referral.run(params); // always return the promise

Team API

The Team API lets you manage users, departments, locations, and cards. For this example, we'll manage users and cards, starting with the Invite user endpoint, which allows you to invite a new team member as an employee. Through the dashboard, Brex admins can assign various role types to the employee, but team members created through this endpoint will bear the Employee role type by default.

Your invite_user query object will POST to /v2/users. The request body should match the following:

COPY{
   "first_name": {{this.params.first_name}},
   "last_name": {{this.params.last_name}},
   "email": {{this.params.email}}
}

Idempotency

Under the Headers tab, be sure to generate a random string value for the Idempotency-Key:

idempotency-key

Idempotency is well beyond the scope of this tutorial. But, in short, the idempotency key ensures that the API server will only process a non-idempotent request once.

For a moment, consider network interruptions that may cause downstream services (or even the application itself) to perform a retry. Without an idempotency, the server would attempt to process the same request again. The idempotency key helps the API server keep track of whether the operation was already performed or not.

Notice that the code to generate the Idempotency-Key includes a reference to Lodash: _.random(1000). Lodash, among other libraries, is built into the Appsmith platform, which is super handy.

Create Card

The create_card query object also requires an Idempotency-Key, and will POST to /v2/cards. The request body is:

COPY{
   "owner": {
       "type": "USER",
       "user_id": {{this.params.user_id}}
   },
   "card_name": {{this.params.card_name}},
   "card_type": "VIRTUAL",
   "limit_type": "USER"
}

The Create card endpoint also accepts other parameters, card types, and limit types, so check out the developer docs. There's also a spend_controls parameter that allows you to define a spend limit for the card. This limit can be set to refresh each month, quarter, year, or never (for one-time use).

Terminate Card

We can also reference {{this.params...}} in the query object URL. For example, the terminate_card query object will POST to the Terminate card endpoint, which has a URI parameter, {{this.params.card_id}}:

COPY/v2/cards/{{this.params.card_id}}/terminate

The request body for terminate_card:

COPY{
 "description": "demo",
 "reason": "OTHER"
}

There are also other reason codes such as CARD_LOST, CARD_NOT_RECEIVED and FRAUD, but for this example, we'll stick with OTHER.

Note that the Idempotency-Key is not required, because card termination is idempotent by definition. In other words, terminating a card multiple times has the same effect as terminating a card once.

Payments API

The Payments API allows you to initiate and manage payments and vendors from your Brex Cash account. We'll use the Create vendor endpoint as an example. In a production application, you would also include vendor payment information to enable ACH, wire, or cheque payments from your Brex Cash account. But, for this example, we'll omit the vendor's payment type. You can also add vendor payment information from the dashboard - How do I manage my vendors on Brex Cash?

Create Vendor

The create_vendor query object will POST to /v1/vendors. This endpoint requires an Idempotency-Key and the request body is:

COPY{
  "company_name": {{this.params.company_name}}
}

Delete Vendor

The delete_vendor query object will send DELETE requests to /v1/vendors/{{this.params.vendor_id}}. There is no request body.

Initial Data Loading

The following query objects will enable us to populate tables on page load. To ensure that Appsmith pulls the initial dataset each time, you can configure each query object to run on page load:

QueryEndpointget_cardsList cardsget_usersList usersget_vendorsList vendors

This option is located under the Settings tab inside the query object:

query-settings

Appsmith Pages

The goal of our app is to provide a user-friendly way to create Brex credit cards, vendors, invites, and referrals. In this section, we'll envision the page layout and breifly touch on a few key points. You'll use a drag/drop interface to build the UI. But, before we get started:

  1. Rename Page1 to Cards.
  2. Add a page for Users and another page for Vendors.
  3. Move get_users, create_referral and invite_user from the Cards page to the Users page.
  4. Move get_vendors, create_vendor and delete_vendor to the Vendors page.

Now that our work is cut out, we'll begin creating each page.

Cards

Use the Appsmith widgets to create the following page to your ability. Note that the green Upload widget is a file picker, not a button.

cards-page-design

Make sure the Data Format property is set to Text for each file picker:

fp-text

Users

The Users page has a Tabs widget with two tabs, one for invites and one for referrals. You can drag each Table widget into the appropriate tab.

users-page-design

The Referrals tab:

users-page-referrals

Vendors

The Vendors page is almost identical to the Cards page. In fact, you can select every widget on the Cards page and copy/paste them onto the Vendors page. Of course, you'll also need to adjust the widget properties and table data.

vendors-page-design

Application Logic

Our application is starting to take shape. We now have an end-to-end skeleton system. Next, we'll incrementally add functionality to each page, incorporating the query objects we created earlier. We'll also learn about JS Objects, which allow us to define handler functions that we can bind to the page widgets.

Page Load

Earlier, we created a few query objects to run on page load. Now, we'll use them to populate our table widgets. As stated in Running APIs on Page Load, "If we connect an API response to a widget, Appsmith automatically runs that API on page load..."

In effect, this means that binding {{get_cards.data.items}} to the Table Data property for tbl_cards will populate the table. However, for this tutorial, we only need the card id and card_name fields from each card. So, we'll write some additional code to remove the unwanted fields:

COPY// bind this to tbl_cards - Table Data property
get_cards.data.items
 .filter(card => card.status == "ACTIVE") // only get ACTIVE cards
 .map(card => ({ id: card.id, card_name: card.card_name }))

Likewise, we initialize tbl_invites and tbl_vendors:

COPY// bind this to tbl_invites - Table Data property
get_users.data.items
 .filter(item => item.status == "INVITED") // only get INVITED users
 .map(item => ({ email: item.email, name: `${item.first_name} ${item.last_name}` }))

// bind this to tbl_vendors - Table Data property
get_vendors.data.items.map(vendor => ({ id: vendor.id, company_name: vendor.company_name }))

JS Objects

In earlier versions of Appsmith, there was only support for writing small code snippets to data bind page widgets. But, Appsmith now supports JS Objects, which enable multi-line JavaScript code and reusable functions.

To create a new JS Object, click the + icon next to the QUERIES/JS menu option, and find the New JS Object option.

new-js-object

Create an object for each page: cards_object, users_object, and vendors_object.

Cards

Replace the contents of cards_object with the following code:

COPYexport default {
   upload: () => {
       showAlert('Cards created!', 'success');
   }
}

We'll revisit this code shortly. At the moment, let's go back to the page editor and bind {{cards_object.upload()}} to the file picker's onFilesSelected event:

create-card-filepicker

If you are curious, deploy the application and upload a file to the Cards page. You should receive a Cards created! notification.

The cards_object.upload method will leverage the create_card query to issue virtual cards. To follow along here, you'll need your Brex cuuser ID, which you can obtain by running the get_current_user query. Place your ID into a CSV file, formatted like this:

COPYcard_name,user_id
Michael Collins,cuuser...

Later on, we'll upload this file to the Cards page.

Reading CSV Files

The following code will read lines from a CSV file, assuming that your file picker is called fp_cards:

COPY// get the first uploaded file
const csv_file = fp_cards.files[0];

// collect each line from the csv file
const csv_lines = csv_file.data.split("\n");

// omit csv header and blank lines
const rows = csv_lines.slice(1).filter(row => row.length > 0);

Simplified:

COPYconst rows = fp_cards.files[0].data.split("\n")
                    .slice(1).filter(row => row.length > 0);

Iterating API Calls

To smoothen the discussion around asynchronous code and JavaScript promises, we'll be using a helper function called loop to iterate API calls. For reference, the full loop implementation is here, but we'll strictly focus on understanding its usage:

COPY// visit each row
return cards_object.loop({
 collection: rows, // assign rows as the collection
 onReadLine: () => {
   // do this on every row
 },
 onEnd: () => {
   // do this after the loop ends
 }
})

The loop helper calls onReadLine once per row. On each loop iteration, we can pass card_name and user_id into create_card.run():

COPYonReadLine: (card_name, user_id) => {
 // be sure to always return the promise,
 // otherwise this will not behave as expected
 return create_card.run({ card_name, user_id });
}

When the loop ends, we report the number of cards created successfully, and then call get_cards.run so that the new items appear in tbl_cards:

COPYonEnd: () => {
 const count = appsmith.store.success_count;
 if (count > 0) {
   showAlert(`${count} cards created!`, "success");
 }
 get_cards.run();
}

Once you re-deploy the application and upload a valid CSV file, your card will show in the Brex dashboard, under Card > Manage cards:

michaels-card

Last, but not least, we have cards_object.terminate. Bind this method to btn_terminate_cards - onClick event:

COPYterminate: () => {
   // clear variables
   cards_object.reset();

   // get comma-separated values from the textbox
   const rows = txt_card_ids.text.split(",").map((id) => id.trim());

   // visit each row, terminate card
   return cards_object.loop({
     collection: rows,
     onReadLine: (card_id) => {
       return terminate_card.run({ card_id });
     },
     onEnd: () => {
       const count = appsmith.store.success_count;
       if (count > 0) {
         showAlert(`${count} cards terminated!`, "success");
       }
       get_cards.run();
     },
   });
 }

Users

The Users page involves many of the same techniques and concepts discussed earlier. However, there are two tables involved here. We'll need to refresh one of the tables, depending on whether the CSV file is intended for referrals or invites.

I should mention that an invite asks someone to join your Brex account as a team member, whereas a referral prompts someone to sign up for their own Brex account.

Invites vs Referrals

Creating an invite sends an email to the recipient:

brex-invite

On the other hand, creating a referral generates a personalized invite link to an onboarding flow. You can find your referral code at dashboard.brex.com/referrals. When the prospect visits their personalized link, they will see a pre-filled form containing data values sent through the Create referral API call:

brex-referral

When the CSV file is uploaded, we can inspect the header row to determine if the file contains referrals or not - referrals have a referral_code column while invites do not:

COPY// invites csv header: "first_name,last_name,email"
// referrals csv header: "referral_code,first_name,last_name,email"
let is_referral = rows[0].indexOf("referral_code,") == 0;

// ...

return is_referral
       ? users_object.createReferrals(rows)
       : users_object.createInvites(rows)

For brevity, the full implementation code is shown here.

Referral Data Store

Something to note about the Create referral endpoint is that it returns the referral id and the referral_signup_url, but it does not return the applicant details (first name, last name, and email). Instead, the API consumer is expected to store and handle their own Brex referrals securely, as they contain sensitive information about the applicant.

Why do we need a data store?

At first glance, this might seem trivial. Perhaps, we don't need a referral data store. Maybe we could just create the referrals and then use the List referrals endpoint to load the recent items. However, the List referrals endpoint does not return any applicant information either.

So, by design, we need the data store to establish a link between the applicant details (passed into create_referral) and the referral id (returned from create_referral).

Without a data store, it's also not possible to seed tbl_referrals on page load. The List referrals endpoint doesn't return any applicant information, so we need to query the data store instead and then use that response data to populate tbl_referrals.

Implementing the data store

I'm using a serverless database (Fauna) to host the data store for this tutorial, but the type of database you use is unimportant. It is only important that the data store is accessible via a REST API, so that we can easily connect to the data source and create query objects:

QueryEndpointadd_referralgraphql.us.fauna.com/graphqlget_referralsgraphql.us.fauna.com/graphql

Fauna exposes a GraphQL API. Please see the the wiki for more details about this as it relates to add_referral and get_referrals.

Storing the referral data

Now that the data store is in place, we can persist the referral id and referral_signup_url along with the applicant information:

COPY// get response data and csv row
onSuccess: ({ id, referral_signup_url }, row) => {
 // get applicant info from the row
 const [referral_code, first_name, last_name, email] = row;
 // store the referral data
 add_referral.run({
   id,
   name: `${first_name} ${last_name}`,
   email,
   referral_signup_url,
 });
},

Get referrals on page load

Also, remember to configure get_referrals to run on page load:

COPY// bind this to tbl_referrals - Table Data property
get_referrals.data &&
get_referrals.data.data &&
get_referrals.data.data.allReferrals.data

Vendors

Implementation of the Vendors page is left as an exercise for the reader. Here are some things to keep in mind:

  1. The fp_vendors file picker needs an event handler for onFilesSelected.
  2. The create_vendor query object has a company_name parameter.
  3. The delete_vendor query object has a vendor_id parameter.
  4. Bind {{appsmith.store.vendors_arr}} to tbl_vendors.
  5. Uploading vendors.csv should create three new vendors in your Brex dashboard.

The full vendors_object implementation is here. You can also create multiple vendors by using the bulk upload tool in your Brex dashboard.

Error Handling

Thus far, we've only explored the happy path. If we return to the Cards page and upload cards.invalid-user-id.csv the create_card query will fail. Thankfully, Appsmith will handle this exception and display an error message.

fail-create_card

But, imagine if you were to upload the following CSV file:

COPYcard_name,user_id
Space Mountain,invalid-user-id
Cargo Dragon,cuuser...
Planet Express,invalid-user-id

In this case, the API call would fail on Space Mountain and Planet Express, but the default error handling would not be able to detect which rows failed. To correct this, we'll need to implement the error handling ourselves.

During cards_object.loop(), whenever onReadLine fails, the onError function will trigger. We can implement onError to display a message:

COPYonError: ({ row }) => {
 const [card_name] = row;
 const message = `failed to create card "${card_name}"`;
 showAlert(message, "error");
 return message; // return message to the loop helper
}

The loop helper also keeps track of errors. There's a has_errors flag, which is a boolean that indicates when any errors have occurred. There is also an errors_arr collection where we store each error message for later display in tbl_errors.

The has_errors flag will determine if the red Icon Button is visible. Set its Visible property to {{appsmith.store.has_errors}}. Also bind its onClick handler to {{showModal("error_modal")}}. This button will launch a Modal that contains a table bound to {{appsmith.store.errors_arr}}:

error-modal

Conclusion

The beauty of Appsmith is that you can migrate this application to your own workspace and repurpose it for your own needs. Here, we've touched on a few Brex API endpoints. We've also learned how to create an Appsmith dashboard that allows Brex admins to create and terminate Brex cards and refer and invite Brex users.

I encourage Brex admins to build and share custom Appsmith workflows that tap into the Brex APIs. There is much value in simplifying the development process where we can. Low code tools like Appsmith can provide practical advantages to Brex admins who need them, but they can also change the perspectives and attitudes of those who do not.

The views expressed in this post are my own and have not been reviewed or approved by my employer (Brex, Inc.).

Build a Low Code Brex Client to Automate Operations

Some things are fantastic together. 🍔🥤

In particular, REST APIs and Low code dovetail nicely.

Although APIs simplify development, they also demand a level of programming skill and technical ability. Low code frameworks like Appsmith can provide a visual layer on top of the API that empowers non-technical audiences and citizen developers to build client applications.

There is much value in simplifying the development process where we can. Low code works best for development projects where the problem domain is well understood and there are very few use cases. REST API clients are well suited to that extent - the API contract itself serves as a kind of blueprint for the client application. Take, for instance, the Brex APIs.

Brex offers several APIs that range from Onboarding and Payments to Teams and Transactions. Brex admins can plug into these APIs to build powerful workflows and custom automations. To demonstrate, we'll build an Appsmith dashboard that will connect to your Brex account.

Key Takeaways

  • Appsmith has a REST interface that allows us to create and modify query objects. A query object is a portable, reusable construct that defines parameters needed for a single API call. We'll use several query objects to engage the Brex APIs.
  • Building a multi-page interface with Appsmith is incredibly simple and well-documented. The platform provides a visual way to build pages using widgets and drag/drop functionality. However, unlike the global Appsmith store, page resources are page-scoped and cannot be referenced across different pages. Despite that, it is still possible to move or copy resources from one page to another.
  • We'll use JavaScript Promises to simplify asynchronous workflows. This will enable us to launch multiple API calls concurrently and then handle the responses elegantly. Normally, this is a challenge because API calls rely on the network and conclude at inexact points in time. To overcome this, we used to apply callback functions, but JavaScript Promises are an improvement over callbacks.

What are we building?

In this tutorial, we'll create an Appsmith tool that leverages the Brex APIs to perform bulk operations on your Brex account. By uploading a CSV file, you'll be able to create credit cards, users, and vendors. These changes will be reflected in your Brex dashboard immediately. Our tool will also support terminating cards and deleting vendors.

As it is not intended for production use, this application doesn't attempt to be either complete or systematic. It is more like a demo project. Rather than serve as a proper software solution, the final application is meant to demonstrate what is possible using Appsmith and the Brex APIs.

For context, this tutorial depicts the reader as a fictional Brex customer in order to provide a frame of reference and bring meaning to the sample CSV files. Pretend that you are an outer space company that issues Brex credit cards to your colleagues. The cardholders have personal cards as well as other cards designated for expenses such as Equipment Repairs, Launch Services, and Telecom. You will also define fictional Vendors: General Atomics, Orbital Transport Services, Space Food Systems. Let's suppose you are building this application to automate a few back office operations. You have chosen to build it using Appsmith.

What do I need?

First, you'll want to sign into Appsmith and create a new application. Next, sign in to your Brex account and generate a user token with Read/Write permissions for Cards, Referrals, Users, and Vendors. Appsmith will use this token to authorize API calls.

To confirm that your token is working, you can perform a quick sanity check with curl:

COPYcurl --request GET 'https://platform.brexapis.com/v2/users/me' \
--header 'Authorization: Bearer <your-token>'

The response will resemble the following:

COPY{
   "id": "cuuser...",
   "first_name": "Michael",
   "last_name": "Collins",
   "email": "mcollins@example.com",
   "status": "ACTIVE"
}

Postman

Alternatively, you can use Postman to test API calls. In fact, the Brex Developer workspace allows you to experiment with every endpoint documented in the Brex Developer Portal.

Appsmith Echo API

Consider using the Appsmith Echo API to spare production API calls.

COPY[POST] https://mock-api.appsmith.com/echo/post

While testing your Appsmith tool, sometimes you will need to invoke non-idempotent API endpoints such as Create card and Invite user. Instead of exercising the live endpoints, you can create an API datasource that relies on the Appsmith Echo API:

COPYfor(let i = 0; i < 5; i++) {
  echo_post.run(onSuccess, onError, params);
}

To mock HTTP status codes and errors, you might try httpstat.us.

Disposable Inbox

For testing user invites, I recommend a disposable email system like Mailinator or Temp Mail.

Git Repo

There's a companion repo that contains sample CSV files and supporting JavaScript code for this tutorial. You can also use Appsmith's Import-Export feature to migrate the demo application to your own account.

Brex APIs

The base URL for all Brex APIs is https://platform.brexapis.com.

  1. Use this URL to create an authenticated API datasource called brex-prod.
  2. Select Bearer Token for the Authentication Type.
  3. Supply your Brex user token.
  4. Click Save.

You should now have a datasource that we can use to create Queries.

New query objects will show in the left side panel, under QUERIES/JS. For example, here I've created a query for the Get current user endpoint:

get-current-user-api-pane

Now, you can create new query objects from the brex-prod datasource. The following search box will appear when you click the + icon next to the QUERIES/JS menu option:

brex-prod-datasource

Going forward, we'll create several queries to support our application.

Onboarding API

The Onboarding API allows you to refer your customers and personal contacts to Brex. You might use the Create referral endpoint as part of the Brex Referral Program.

Unlike the Get current user endpoint, Create referral is a POST endpoint that requires a JSON body.

create-referral-json-body

Later on, we'll discuss this.params. For now, understand that it is possible to run the create_referral query object and pass parameters into it, like so:

COPYconst params = { referral_code, email, first_name, last_name };
return create_referral.run(params); // always return the promise

Team API

The Team API lets you manage users, departments, locations, and cards. For this example, we'll manage users and cards, starting with the Invite user endpoint, which allows you to invite a new team member as an employee. Through the dashboard, Brex admins can assign various role types to the employee, but team members created through this endpoint will bear the Employee role type by default.

Your invite_user query object will POST to /v2/users. The request body should match the following:

COPY{
   "first_name": {{this.params.first_name}},
   "last_name": {{this.params.last_name}},
   "email": {{this.params.email}}
}

Idempotency

Under the Headers tab, be sure to generate a random string value for the Idempotency-Key:

idempotency-key

Idempotency is well beyond the scope of this tutorial. But, in short, the idempotency key ensures that the API server will only process a non-idempotent request once.

For a moment, consider network interruptions that may cause downstream services (or even the application itself) to perform a retry. Without an idempotency, the server would attempt to process the same request again. The idempotency key helps the API server keep track of whether the operation was already performed or not.

Notice that the code to generate the Idempotency-Key includes a reference to Lodash: _.random(1000). Lodash, among other libraries, is built into the Appsmith platform, which is super handy.

Create Card

The create_card query object also requires an Idempotency-Key, and will POST to /v2/cards. The request body is:

COPY{
   "owner": {
       "type": "USER",
       "user_id": {{this.params.user_id}}
   },
   "card_name": {{this.params.card_name}},
   "card_type": "VIRTUAL",
   "limit_type": "USER"
}

The Create card endpoint also accepts other parameters, card types, and limit types, so check out the developer docs. There's also a spend_controls parameter that allows you to define a spend limit for the card. This limit can be set to refresh each month, quarter, year, or never (for one-time use).

Terminate Card

We can also reference {{this.params...}} in the query object URL. For example, the terminate_card query object will POST to the Terminate card endpoint, which has a URI parameter, {{this.params.card_id}}:

COPY/v2/cards/{{this.params.card_id}}/terminate

The request body for terminate_card:

COPY{
 "description": "demo",
 "reason": "OTHER"
}

There are also other reason codes such as CARD_LOST, CARD_NOT_RECEIVED and FRAUD, but for this example, we'll stick with OTHER.

Note that the Idempotency-Key is not required, because card termination is idempotent by definition. In other words, terminating a card multiple times has the same effect as terminating a card once.

Payments API

The Payments API allows you to initiate and manage payments and vendors from your Brex Cash account. We'll use the Create vendor endpoint as an example. In a production application, you would also include vendor payment information to enable ACH, wire, or cheque payments from your Brex Cash account. But, for this example, we'll omit the vendor's payment type. You can also add vendor payment information from the dashboard - How do I manage my vendors on Brex Cash?

Create Vendor

The create_vendor query object will POST to /v1/vendors. This endpoint requires an Idempotency-Key and the request body is:

COPY{
  "company_name": {{this.params.company_name}}
}

Delete Vendor

The delete_vendor query object will send DELETE requests to /v1/vendors/{{this.params.vendor_id}}. There is no request body.

Initial Data Loading

The following query objects will enable us to populate tables on page load. To ensure that Appsmith pulls the initial dataset each time, you can configure each query object to run on page load:

QueryEndpointget_cardsList cardsget_usersList usersget_vendorsList vendors

This option is located under the Settings tab inside the query object:

query-settings

Appsmith Pages

The goal of our app is to provide a user-friendly way to create Brex credit cards, vendors, invites, and referrals. In this section, we'll envision the page layout and breifly touch on a few key points. You'll use a drag/drop interface to build the UI. But, before we get started:

  1. Rename Page1 to Cards.
  2. Add a page for Users and another page for Vendors.
  3. Move get_users, create_referral and invite_user from the Cards page to the Users page.
  4. Move get_vendors, create_vendor and delete_vendor to the Vendors page.

Now that our work is cut out, we'll begin creating each page.

Cards

Use the Appsmith widgets to create the following page to your ability. Note that the green Upload widget is a file picker, not a button.

cards-page-design

Make sure the Data Format property is set to Text for each file picker:

fp-text

Users

The Users page has a Tabs widget with two tabs, one for invites and one for referrals. You can drag each Table widget into the appropriate tab.

users-page-design

The Referrals tab:

users-page-referrals

Vendors

The Vendors page is almost identical to the Cards page. In fact, you can select every widget on the Cards page and copy/paste them onto the Vendors page. Of course, you'll also need to adjust the widget properties and table data.

vendors-page-design

Application Logic

Our application is starting to take shape. We now have an end-to-end skeleton system. Next, we'll incrementally add functionality to each page, incorporating the query objects we created earlier. We'll also learn about JS Objects, which allow us to define handler functions that we can bind to the page widgets.

Page Load

Earlier, we created a few query objects to run on page load. Now, we'll use them to populate our table widgets. As stated in Running APIs on Page Load, "If we connect an API response to a widget, Appsmith automatically runs that API on page load..."

In effect, this means that binding {{get_cards.data.items}} to the Table Data property for tbl_cards will populate the table. However, for this tutorial, we only need the card id and card_name fields from each card. So, we'll write some additional code to remove the unwanted fields:

COPY// bind this to tbl_cards - Table Data property
get_cards.data.items
 .filter(card => card.status == "ACTIVE") // only get ACTIVE cards
 .map(card => ({ id: card.id, card_name: card.card_name }))

Likewise, we initialize tbl_invites and tbl_vendors:

COPY// bind this to tbl_invites - Table Data property
get_users.data.items
 .filter(item => item.status == "INVITED") // only get INVITED users
 .map(item => ({ email: item.email, name: `${item.first_name} ${item.last_name}` }))

// bind this to tbl_vendors - Table Data property
get_vendors.data.items.map(vendor => ({ id: vendor.id, company_name: vendor.company_name }))

JS Objects

In earlier versions of Appsmith, there was only support for writing small code snippets to data bind page widgets. But, Appsmith now supports JS Objects, which enable multi-line JavaScript code and reusable functions.

To create a new JS Object, click the + icon next to the QUERIES/JS menu option, and find the New JS Object option.

new-js-object

Create an object for each page: cards_object, users_object, and vendors_object.

Cards

Replace the contents of cards_object with the following code:

COPYexport default {
   upload: () => {
       showAlert('Cards created!', 'success');
   }
}

We'll revisit this code shortly. At the moment, let's go back to the page editor and bind {{cards_object.upload()}} to the file picker's onFilesSelected event:

create-card-filepicker

If you are curious, deploy the application and upload a file to the Cards page. You should receive a Cards created! notification.

The cards_object.upload method will leverage the create_card query to issue virtual cards. To follow along here, you'll need your Brex cuuser ID, which you can obtain by running the get_current_user query. Place your ID into a CSV file, formatted like this:

COPYcard_name,user_id
Michael Collins,cuuser...

Later on, we'll upload this file to the Cards page.

Reading CSV Files

The following code will read lines from a CSV file, assuming that your file picker is called fp_cards:

COPY// get the first uploaded file
const csv_file = fp_cards.files[0];

// collect each line from the csv file
const csv_lines = csv_file.data.split("\n");

// omit csv header and blank lines
const rows = csv_lines.slice(1).filter(row => row.length > 0);

Simplified:

COPYconst rows = fp_cards.files[0].data.split("\n")
                    .slice(1).filter(row => row.length > 0);

Iterating API Calls

To smoothen the discussion around asynchronous code and JavaScript promises, we'll be using a helper function called loop to iterate API calls. For reference, the full loop implementation is here, but we'll strictly focus on understanding its usage:

COPY// visit each row
return cards_object.loop({
 collection: rows, // assign rows as the collection
 onReadLine: () => {
   // do this on every row
 },
 onEnd: () => {
   // do this after the loop ends
 }
})

The loop helper calls onReadLine once per row. On each loop iteration, we can pass card_name and user_id into create_card.run():

COPYonReadLine: (card_name, user_id) => {
 // be sure to always return the promise,
 // otherwise this will not behave as expected
 return create_card.run({ card_name, user_id });
}

When the loop ends, we report the number of cards created successfully, and then call get_cards.run so that the new items appear in tbl_cards:

COPYonEnd: () => {
 const count = appsmith.store.success_count;
 if (count > 0) {
   showAlert(`${count} cards created!`, "success");
 }
 get_cards.run();
}

Once you re-deploy the application and upload a valid CSV file, your card will show in the Brex dashboard, under Card > Manage cards:

michaels-card

Last, but not least, we have cards_object.terminate. Bind this method to btn_terminate_cards - onClick event:

COPYterminate: () => {
   // clear variables
   cards_object.reset();

   // get comma-separated values from the textbox
   const rows = txt_card_ids.text.split(",").map((id) => id.trim());

   // visit each row, terminate card
   return cards_object.loop({
     collection: rows,
     onReadLine: (card_id) => {
       return terminate_card.run({ card_id });
     },
     onEnd: () => {
       const count = appsmith.store.success_count;
       if (count > 0) {
         showAlert(`${count} cards terminated!`, "success");
       }
       get_cards.run();
     },
   });
 }

Users

The Users page involves many of the same techniques and concepts discussed earlier. However, there are two tables involved here. We'll need to refresh one of the tables, depending on whether the CSV file is intended for referrals or invites.

I should mention that an invite asks someone to join your Brex account as a team member, whereas a referral prompts someone to sign up for their own Brex account.

Invites vs Referrals

Creating an invite sends an email to the recipient:

brex-invite

On the other hand, creating a referral generates a personalized invite link to an onboarding flow. You can find your referral code at dashboard.brex.com/referrals. When the prospect visits their personalized link, they will see a pre-filled form containing data values sent through the Create referral API call:

brex-referral

When the CSV file is uploaded, we can inspect the header row to determine if the file contains referrals or not - referrals have a referral_code column while invites do not:

COPY// invites csv header: "first_name,last_name,email"
// referrals csv header: "referral_code,first_name,last_name,email"
let is_referral = rows[0].indexOf("referral_code,") == 0;

// ...

return is_referral
       ? users_object.createReferrals(rows)
       : users_object.createInvites(rows)

For brevity, the full implementation code is shown here.

Referral Data Store

Something to note about the Create referral endpoint is that it returns the referral id and the referral_signup_url, but it does not return the applicant details (first name, last name, and email). Instead, the API consumer is expected to store and handle their own Brex referrals securely, as they contain sensitive information about the applicant.

Why do we need a data store?

At first glance, this might seem trivial. Perhaps, we don't need a referral data store. Maybe we could just create the referrals and then use the List referrals endpoint to load the recent items. However, the List referrals endpoint does not return any applicant information either.

So, by design, we need the data store to establish a link between the applicant details (passed into create_referral) and the referral id (returned from create_referral).

Without a data store, it's also not possible to seed tbl_referrals on page load. The List referrals endpoint doesn't return any applicant information, so we need to query the data store instead and then use that response data to populate tbl_referrals.

Implementing the data store

I'm using a serverless database (Fauna) to host the data store for this tutorial, but the type of database you use is unimportant. It is only important that the data store is accessible via a REST API, so that we can easily connect to the data source and create query objects:

QueryEndpointadd_referralgraphql.us.fauna.com/graphqlget_referralsgraphql.us.fauna.com/graphql

Fauna exposes a GraphQL API. Please see the the wiki for more details about this as it relates to add_referral and get_referrals.

Storing the referral data

Now that the data store is in place, we can persist the referral id and referral_signup_url along with the applicant information:

COPY// get response data and csv row
onSuccess: ({ id, referral_signup_url }, row) => {
 // get applicant info from the row
 const [referral_code, first_name, last_name, email] = row;
 // store the referral data
 add_referral.run({
   id,
   name: `${first_name} ${last_name}`,
   email,
   referral_signup_url,
 });
},

Get referrals on page load

Also, remember to configure get_referrals to run on page load:

COPY// bind this to tbl_referrals - Table Data property
get_referrals.data &&
get_referrals.data.data &&
get_referrals.data.data.allReferrals.data

Vendors

Implementation of the Vendors page is left as an exercise for the reader. Here are some things to keep in mind:

  1. The fp_vendors file picker needs an event handler for onFilesSelected.
  2. The create_vendor query object has a company_name parameter.
  3. The delete_vendor query object has a vendor_id parameter.
  4. Bind {{appsmith.store.vendors_arr}} to tbl_vendors.
  5. Uploading vendors.csv should create three new vendors in your Brex dashboard.

The full vendors_object implementation is here. You can also create multiple vendors by using the bulk upload tool in your Brex dashboard.

Error Handling

Thus far, we've only explored the happy path. If we return to the Cards page and upload cards.invalid-user-id.csv the create_card query will fail. Thankfully, Appsmith will handle this exception and display an error message.

fail-create_card

But, imagine if you were to upload the following CSV file:

COPYcard_name,user_id
Space Mountain,invalid-user-id
Cargo Dragon,cuuser...
Planet Express,invalid-user-id

In this case, the API call would fail on Space Mountain and Planet Express, but the default error handling would not be able to detect which rows failed. To correct this, we'll need to implement the error handling ourselves.

During cards_object.loop(), whenever onReadLine fails, the onError function will trigger. We can implement onError to display a message:

COPYonError: ({ row }) => {
 const [card_name] = row;
 const message = `failed to create card "${card_name}"`;
 showAlert(message, "error");
 return message; // return message to the loop helper
}

The loop helper also keeps track of errors. There's a has_errors flag, which is a boolean that indicates when any errors have occurred. There is also an errors_arr collection where we store each error message for later display in tbl_errors.

The has_errors flag will determine if the red Icon Button is visible. Set its Visible property to {{appsmith.store.has_errors}}. Also bind its onClick handler to {{showModal("error_modal")}}. This button will launch a Modal that contains a table bound to {{appsmith.store.errors_arr}}:

error-modal

Conclusion

The beauty of Appsmith is that you can migrate this application to your own workspace and repurpose it for your own needs. Here, we've touched on a few Brex API endpoints. We've also learned how to create an Appsmith dashboard that allows Brex admins to create and terminate Brex cards and refer and invite Brex users.

I encourage Brex admins to build and share custom Appsmith workflows that tap into the Brex APIs. There is much value in simplifying the development process where we can. Low code tools like Appsmith can provide practical advantages to Brex admins who need them, but they can also change the perspectives and attitudes of those who do not.

The views expressed in this post are my own and have not been reviewed or approved by my employer (Brex, Inc.).

Square
Try Appsmith
Introducing Version Control with Git
3
June
2022
Announcement

Introducing Version Control with Git

Introducing Version Control with Git
Visahavel Parthasarathy
0
 minutes ↗
#
announcement
#
community
#
developer
Announcement

Today we're excited to announce the launch of Version Control with Git, so that developers can collaborate and version control their applications. This has been one of the most requested features from our community.

While low-code frameworks like Appsmith save a developer's time via easier application management, faster plugging in of data sources, and effortless creation of UI, there are still aspects of a developer's usual workflow that aren't native to low-code platforms.

Version Control with Git, enables us to take a big step towards that.

Here's a short video on how you can use Version Control with Git:

Version Control with Git Highlights

Version Control with Git allows multiple developers to add their work in a git branch, raise a pull request for code reviews, integrate with CI/CD pipelines so that their changes go live when their pull requests are approved, and provide a commit history to go back to a previous version if something were to go wrong. You can now also test a new addition to your app on a separate branch without breaking your deployed version.

You can connect to any popular version control tool of your liking: Github, Gitlab, Bitbucket, AWS Code Commit and more.

Furthermore, if you're a large organization using isolated environments for your production and test apps, you can now ensure that your applications can only be promoted to production via your CI/CD processes.

Version Control For All

Appsmith's Community Edition users will be able to connect unlimited public repos and up to 3 private repos.

In contrast, low-code frameworks in the market today either don't offer this functionality as part of their open source or free edition (and instead offer a simple auto-save), or push it deep into their most expensive pricing plans (like Retool).

Head over to our Version Control with Git documentation on how to set up version control with your favorite version control tools and get the most out of it.

May Round-up: App Theming, New Copy Paste Experience, and Product Updates
2
June
2022
Monthly Round-up

May Round-up: App Theming, New Copy Paste Experience, and Product Updates

May Round-up: App Theming, New Copy Paste Experience, and Product Updates
Vihar Kurama
0
 minutes ↗
#
announcement
#
developer
#
community
Monthly Round-up

If you’ve followed Appsmith for a while, you know that we can do a lot in 30 days! I am here to give you all the deets; follow along!

Application Theming [Beta]

One of our most awaited features — App Theming — is now out! Style your pages and widgets using global controls. We've also made it easy to change the visual layout with a single click.

Note that while all apps support theming, changing the theme or customizing the theme may not update the app entirely for older apps. We’ve done this to prevent overriding any changes you may have already made while styling your widgets. (Note that, this is a beta feature at the moment, and we are going to be improving it in the coming weeks).


Theming automatically updates multiple properties, including fonts, colors, borders, shadows, etc. All these properties can be configured from the property pane globally. Read the complete documentation here.

Discard Changes/ Delete Branch on Version Control

Version Control with Git is well-loved by Appsmith users, especially when working with teams. We’ve enhanced this feature by adding another option other than ‘commit’ to make it more flexible. Now you can discard changes on the current branch and reload the earlier, committed application. Additionally, you can also delete the stale or unused branches.

Allowing Camera and Microphone on iFrame

Are you communicating to other websites from Appsmith? Our latest improvements will help you get the most out of it. All you have to do is use allow="camera; microphone" on the attributes to be able to access the camera/microphone on the iframe.

Making it easy for you on Windows using WSL

Have trouble setting up the Appsmith server on Windows? Don’t worry. We’ve worked on some documentation to help you contribute to Appsmith on Windows operating systems that use WSL; check it out here. Prerequisite MongoDB is included, along with detailed notes on building/running the code and how to set up your code editor.

Improvements

New Copy Paste Experience

Duplicating widgets on Appsmith is now easier with the new-copy-paste experience. Here’s a quick summary of how it works:

When the layout widget is selected:

  • If the mouse pointer is not on the layout widget, the widget is pasted inside the layout widget at the bottom of the canvas.
  • If the mouse pointer is on the layout widget, the pasted widget will be aligned top-left of the mouse pointer inside the layout widget.

If the non-layout widget is selected, no matter where the mouse pointer is, It is pasted below the non-layout widget, and the widgets are aligned horizontally.

If the mouse pointer is on a widget and no widgets are selected, widgets will be posted below and are horizontally aligned.

More Pages? View them all at once!

We’ve added a new option to resize the entity explorer page to view more pages at once. Just find the expander and extend it based on your comfort :)

Total Records and Page Count on Table Header

More love to the table widget; we’ve just added total record count and page count on the table header.

CleanShot 2022-05-25 at 15.34.35@2x.png
Better Way of Running JS Objects

We’ve revamped the way we run JS Objects from the JS Editor. Just put the cursor inside the function and hit the RUN button. The editor automatically identifies which function you’re working on; you can always use the shortcuts to make this easier!

Additionally, the page load functions can be configured from the Settings tab, just like how we do it from queries!

CleanShot 2022-05-25 at 15.40.46@2x.png
Added top-level isValid property to JSONForm

Currently, the JSONForm widget has an isValid property inside the fieldState for each field. However, there is no top-level property to check all fields at once. With this update, we added an isValid property which can be used to check if the current form is valid (passes all validation) or not.

CleanShot 2022-05-25 at 15.47.07@2x.png
Enhanced Map Widget

We’ve added a new property to the Map widget that lets us enable search location from Map without entering the coordinates. You can search directly on the map and display searched fields on other widgets without clicking on the Map widget.

CleanShot 2022-05-25 at 15.48.29@2x.png

Collaborations, Technical Content, Videos & Tutorials

Last month was hectic! We’ve published a new video on how you can generate PDFs from Appsmith using APITemplate; not just this, we’ve successfully hosted a couple of live sessions.

  • In this Video, Confidence, our Developer Advocate, show us how you can build a workflow to generate PDFs from Appsmith by consuming APIs from APITemplte.io.
  • Shams Mosowi from Rowy.io collaborated with us to demonstrate how we can build an expense management tool that employees can use to submit expenses for approval using Rowy as the backend.
  • Getting started with Version Control with Git on Appsmith? We’ve created a guide on how you can connect to a Git provider to version control Appsmith apps. Watch it here.

If you're curious about what we were up to, look no further and follow this link.

If you’re interested in using a database not listed on our website as an integration, please let us know about it by raising a PR on Github, and we will do our best to include it at the earliest.

Join our growing community on Discord, and follow us on Youtube and Twitter to stay up to date.

April Round-up: Multiple Widget Reflow, Readable URLs, more Product Updates
3
May
2022
Monthly Round-up

April Round-up: Multiple Widget Reflow, Readable URLs, more Product Updates

April Round-up: Multiple Widget Reflow, Readable URLs, more Product Updates
Vihar Kurama
0
 minutes ↗
#
announcement
#
community
Monthly Round-up

If you’ve followed Appsmith for a while, you know that we can do a lot in 30 days!

I am here to give you all the deets; follow along!

Introducing Multiple Widget Reflow

We’ve received a lot of positive feedback on our new drag and drop experience. Users dig being able to resize widgets and move them around. This provides them with the ultimate freedom to build applications however they want without barriers that might get in the way :P

We’ve made this even more fun and powerful by adding a multiple widget reflow feature. With this, users should be able to move widgets and resize them even when they are grouped.

This also makes the drag and drop experience more performant in avoiding UI lags while moving widgets.

Readable Application URLs

All of you have been waiting for a much-requested feature, and today, it’s finally here! The application URL now has your app name as a slug, making it easier for you to share and find your Appsmith applications. This is available immediately for all the old applications and, of course, the newly created ones. Here’s a sample app URL.


https://app.appsmith.com/app/april-round-up/home-6266f76700cba01cd0301e20

To upgrade your old app URLs, please navigate the updates section from the bottom bar and hit update. Here’s the recording of how you can do it:

Product Updates

New Response View Switchers

We've added a new response view switcher feature that lets users easily switch between response formats on the fly; you'll be able to view them in different formats (JSON, Table, RAW) based purely on ease of choice. This feature will be available on all action types (APIs/Queries on a datasource).

Added Button Variants in Table Column Type

It’s super easy to set a column type to a button on Appsmith - just navigate to column properties and update the type to Button. To make your table button more extraordinary, we’ve added the variant property taking inspiration from our button widget; with this, you can update table button types to different variants available.

CleanShot 2022-04-26 at 01.05.56@2x.png
Smart Substitution Feature on Firestore Plugin

We’ve added a smart substitution feature to Firestore plugin methods (to dynamically perform type conversions on field values in a request body). The smart substitution will work the same way for REST API requests.

Currently, this feature only gets applied to the body section of Create, Set, Add or Update commands, as this is the only input field that expects a JSON input type. This can be toggled by navigating to the settings tab on datasource.

Added MultipartFormDataType variants for REST Datasource

We’ve added ​​MultipartFormDataType variants; with this, users can add an array as a value for multipart requests on REST APIs. It will handle every item in the list as a part of the multipart request. Here’s what an example body will look like:


--IpJ9ACFxVbhXlAQuIFccoJ0K0ttFS5PVyfX3
Content-Disposition: form-data; name="myArray"
Content-Type: text/plain;charset=UTF-8
Content-Length: 3

One
--IpJ9ACFxVbhXlAQuIFccoJ0K0ttFS5PVyfX3
Content-Disposition: form-data; name="myArray"
Content-Type: application/json
Content-Length: 1

2
--IpJ9ACFxVbhXlAQuIFccoJ0K0ttFS5PVyfX3
Content-Disposition: form-data; name="myArray"
Content-Type: application/json
Content-Length: 4


Enhanced Text Widget

When the truncating text option is enabled in a text widget, the scroll switch is no longer valid; as we truncate to fix this issue, we made the following changes.

We’ve added an overflow property option with the following configurations:

  • Scroll Contents - Enables scroll in the text widget
  • Truncate Text - Enables truncation in the text widget
  • No Overflow - No scroll or truncation
CleanShot 2022-05-02 at 16.39.07@2x.png
Added Support to MinIO in S3

You can now connect MinIO instances on Appsmith using the S3 plugin. This can be done when:

  • over the unsecured connection (HTTP)
  • when secured via a CA-signed certificate (HTTPS)

You can find this connecting in a dropdown from the S3 datasource.

Select Widget is now 50x Faster on Large Datasets

We’ve been listening to a few issues with the select widgets when large datasets are used. Previously, in the case of these large data sets, the Select widget took up to 5 seconds to render. The initial render is down to ~100ms once the options are available from the API call.

Our engineers tested this thoroughly by loading 1000 options from a mock API on a newly upgraded test widget :)

New Collaborations, Technical Content, Videos & Tutorials

Last month was crazy for us; we’ve published a blog post on how you can connect Stipe on Appsmith to manage your transactions; not just this, we’ve successfully hosted four live sessions.

  • We’ve created a small guide on how you can use JSON forms on Appsmith. This widget lets you build any kind of form with JSON dynamically. Learn how to use it here.
  • Stephen Tillman, one of our active community members, joined us to show how Appsmith helped him to build a great dashboard that lets him manage credit cards on Brex using BrexAPIs. Watch the recording here.
  • Pranav and I hosted the third How Do I Do X session, where we picked questions from the community to integrate with ZohoCRM and Stripe APIs. We’ve also talked about how you can use resetWidget and clearWidget functions on Appsmith to handle form actions. Watch the recording here.

If you're curious about what we were up to, look no further and follow this link.

If you’re interested in using a database not listed on our website as an integration, please let us know about it by raising a PR on Github, and we will do our best to include it at the earliest.

Join our growing community on Discord, and follow us on Youtube and Twitter to stay up to date.

What’s a Rich Text element?

asdsadasdsa

asdsadasdsa

The rich text element allows you to create and format headings, paragraphs, blockquotes, images, and video all in one place instead of having to add and format them individually. Just double-click and easily create content.

sfdfsdfds

dsfdsfdsf

adfkaldf

The rich text element allows you to create and format

sadadasdasdas dsada sadas asd ad

Static and dynamic content editing

  1. vdfgdgd
  2. gjgjg

A rich text element can be used with static or dynamic content. For static content, just drop it into any page and begin editing. For dynamic content, add a rich text field to any collection and then connect a rich text element to that field in the settings panel. Voila!

How to customize formatting for each rich text

Headings, paragraphs, blockquotes, figures, images, and figure captions can all be styled after a class is added to the rich text element using the "When inside of" nested selector system.

swzdswxzdsw