Skip to main content

Retool Apps tutorial

A hands-on introduction to Retool Apps.

Retool enables you to build web apps using a drag-and-drop interface and component library. You can connect your apps to almost any database and API to Retool, allowing your users to interact with data.

This tutorial explains how to build a user management app on Retool. You can edit user information, such as names and email addresses, and block or unblock users.

After completing this tutorial, you'll gain the necessary knowledge to:

  • Connect your data sources as resources.
  • Assemble user interfaces using drag-and-drop components.
  • Query resources to interact with data.
  • Connect queries and components to manipulate data.
  • Extend your app's functionality with JavaScript queries and transformers.

These terms and concepts may not be familiar yet, but you'll learn about them as you work through the tutorial.

Prerequisites

Familiarity with common technologies like APIs and JavaScript is expected to complete this tutorial. If you need to learn more about these technologies, try the following online learning resources:

Get started

To get started, log in or sign up to Retool.

If you're signing up to Retool for the first time, you also create an organization. Every Retool user account is bound to an organization and can only belong to one. When you sign up and create a new organization, you immediately become its administrator.

1. Generate an API and sample data for your app

The first step in building a Retool app is connecting your data source. Retool supports most databases and APIs, and you can use built-in integrations to connect to popular data sources with minimal configuration. You connect a data source by creating a resource in Retool. This guide uses a REST API as the resource.

To work through the tutorial, you'll use Retool's API generator (embedded below) to create a test REST API with some sample data. When building your own apps, you'll often use an existing data source.

Click the Generate API button below and save the Endpoint URL. This generates a data set that includes user IDs, names, email addresses, and blocked status.

2. Create a resource to connect your data source

Navigate to the Resources tab from the Retool homepage and create a new REST API resource. Make sure to paste the endpoint URL from the previous step in the Base URL field.

If this is your first time using Retool, you'll see a prompt for creating an app after saving the resource. Go ahead and create an app and name it User management. If you don't see a prompt, navigate to the Apps tab in Retool and create an app named User management.

3. Write a test query

Queries are covered in more detail later, but it's a good idea to run one now to verify the connection to your resource. It's time to create your first query.

Open the Code list, and create a new resource query. Click the suggested Users resource, or search for it. Save and run the query, and rename it to getUsers.

When you click Save & Run, you can see a success message and a JSON response with your data.

4. Display the app's name with a Text component

Components are prebuilt UI elements that your users interact with (like buttons, tables, and forms). Only a few components are used in this example, but you can find information on others in the Component Library.

For your app, you'll use Text, Table, and Split Button components on the Main frame of the Canvas. You can read more about frames and the Canvas in the App IDE documentation, but for now, just know that the Main frame is the core section of your app.

Click + on the left to select and drag a Text component to the Main frame. The right panel (the Inspector) updates automatically to show the component's properties. Components have numerous properties that you can set, and they're accessible from other components and queries. For this Text component, update its Value to ## {{ retoolContext.appName }}.

In Retool, anything between curly brackets {{ }} is JavaScript. This means you can write custom JavaScript in addition to using prebuilt component functionality.

For this text component, dot notation is used to access the retoolContext object and the app's name. The retoolContext object is unique to each app, and it stores information like the app name and UUID.

note

Retool supports Markdown, which you can read more about in Github's Basic writing and formatting syntax guide for more information—Markdown in Retool uses the same conventions.

5. Display user data with a Table component

Drag a Table component onto the Main frame. The table automatically populates with the data returned in getUsers. Verify this by making sure the Data source field for the table is set to getUsers, and the Primary key is set to ID.

There are a few more settings to enable to allow users to edit data in the table. First, select the Email column and set the Format to String. Then, make the Name and Email columns editable.

6. Add user management options with a Split Button component

The final component to add is a Split Button component. This button will have four functions:

  • Block individual users
  • Unblock individual users
  • Block all users
  • Unblock all users

Drag a Split Button component to the Main frame, and update the label for each action. Split Button components come with three actions by default, but you can add the fourth by clicking +.

When you're done editing the actions, the list should look like this.

Split button options for blocking and unblocking users

7. Create a query that updates names and email addresses

Queries are how apps interact with data, and they're often triggered by interactions or changes to components. Your app already has a query that pulls in data from your API, but you need to create a few more queries so you can fully interact with your data.

In a previous section, you made the Name and Email columns editable. This lets you double-click on a cell and edit it, but it doesn't send the user's changes back to your API. It does, however, temporarily save changes to the changesetArray property of the table, which stores a list of rows and their edited values.

You can use the changesetArray to dynamically populate a PATCH query with a user's changes and send these changes to update your API.

Create a new resource query, name it patchUser, and set the action type to PATCH.

Fill out the rest of the endpoint URL and JSON body with the following values. Make sure to click Save after filling out the query.

FieldValue
Endpoint slug{{table1.changesetArray['0'].id}}
name{{table1.changesetArray['0'].name}}
email{{table1.changesetArray['0'].email}}
`patchUser` endpoint URL and body

You might notice that these three values are currently null (and therefore in red). This is expected because there haven't been any changes to the table yet, so there aren't any entries in changesetArray.

8. Save changes and update user data

There are various ways for queries and components to interact with each other. Event handlers are a common method, which we discuss in this section. You can read more about them in the Event handlers documentation.

Event handlers allow you to perform actions based on user interactions. You can set event handlers on interactions with components or queries. Here are a few examples of how to use event handlers.

  • An event handler set on a component triggers a query when users click a button.
  • An event handler set on a query updates a Text component after a query runs successfully.

In the User managment app, trigger the patchUser query any time a user edits the table. Select your table to display its settings in the Inspect tab. Select Save actions to add an event handler that runs the patchUser query.

Now test this out by clicking a user's name and editing it. Click the Save button and patchUser runs automatically. This writes the change back to your API, but it doesn't update the table.

To automatically update the table, open the patchUser query and scroll to the Event Handlers section. Add a success event handler and select the getUsers query. Once complete, save the query.

Now when you save a change in the table, patchUser runs and then triggers getUsers to refresh the table.

9. Create queries that block and unblock users

In a previous section, you added a Split Button component that'll allow you to block and unblock users. Blocking and unblocking users requires adding a few queries that the button can trigger when clicked.

To block individual users, create another PATCH query. Name the query blockUser, and use {{ table1.selectedRow.id }} at the end of the endpoint URL to pass in the user ID. Then, edit the JSON body to set blocked to true, and save the query.

Repeat this process for the unblock action, but make sure to:

  • Name the query unblockUser
  • Set blocked to false

10. Configure options to block and unblock users

Click on your Split Button component and select the Block user action. Create an event handler to run the block query. As a small design improvement, set Disabled to {{table1.selectedRow.blocked}}. This disables the button when the JavaScript evaluates to true—in this case, if the user is already blocked.

Just like you did for the updateUser query, you need to update the table after making a change to the user data. Add a success event handler to the blockUser query that triggers getUsers.

Repeat this process for the unblock option, but set Disabled to {{ !table1.selectedRow.blocked }} so that the button is disabled for users that already have access.

Test these actions out by selecting a row and clicking the button.

11. Dynamically display user names

As you've learned, anything between curly brackets {{ }} is processed as JavaScript in Retool. This is great for smaller pieces of code, but sometimes apps require more complex logic. This is where transformers and writing custom JavaScript come in.

Transformers allow you to write reusable pieces of code. They're often used to manipulate data, and you can access the transformer throughout your app using {{ transformerName.value }}. Transformers execute their code automatically, so you don't need to trigger them with event handlers or components.

Alternatively, you can write more complex logic using JavaScript queries. This allows you to trigger queries and configure components, which gives you more control over how your app works. If you create an app with small pieces of code spread throughout it, you might want to write JavaScript queries to centralize that logic instead.

When blocking and unblocking a user, it'd be useful to see their name displayed on the button before clicking it. JavaScript transformers update automatically when the object they reference changes, which means you can write a transformer that reads the user's name from the currently selected row.

Click the + button in the code list and select Transformer. Name it userFirstName and paste return {{ table1.selectedRow.name.split(' ')[0] }} into the editor. This code reads the user's name from the selected row, and then splits out the first name. Make sure to save the transformer.

Then, select your Split Button component. Click on the block action and update the label to Block {{userFirstName.value}}. Do the same for the unblock action.

The button now automatically shows the user's first name when you select a row in the table.

12. Add functionality to block and unblock all users

Blocking and unblocking all users uses custom JavaScript to interact with the REST API resource. The JavaScript query identifies which users to block or unblock and then triggers the appropriate query.

Create a JavaScript query

Start by clicking the + button on the code list and select JavaScript Query. Name it blockAll and then paste this code into the editor.

var rows = table1.data;

const unblockedUsers = rows
.map((row, index) => [row, index])
.filter(([row]) => !row.blocked);

let blockIndex = 0;

const blockNext = () => {
// Notify and quit when finished
if (blockIndex >= unblockedUsers.length) {
utils.showNotification({
title: "Success",
description: "Successfully blocked " + unblockedUsers.length + " users.",
notificationType: "success",
duration: 3,
});

// Update table after queries finish
getUsers.trigger();

return;
}

// Get next unblocked user and unblock them
const [unblockedUser, index] = unblockedUsers[blockIndex];
blockAllUsers.trigger({
additionalScope: { i: index },
onSuccess: function () {
blockIndex++;
blockNext();
},
});
};

blockNext();

This code finds unblocked users, blocks them with the blockAllUsers API query (which you'll create in a moment), and then displays a notification with the number of users unblocked. There are a few more things to note:

  • The i variable is the index of the current row. This will be used in the blockAllUsers query to block the user.
  • additionalScope allows you to pass parameters to your REST query. In this example, it passes the i variable to the blockAllUsers.
  • utils.showNotification is a built-in JavaScript method provided by Retool. You can read more about it and other methods in our JavaScript API Reference.

Create a query to block all users

Next, create another PATCH query and name it blockAllUsers. Set the endpoint slug to {{ table1.data[i].id }}, and set blocked to true in the JSON body. Make sure to save the query.

This query uses the i value passed from the JavaScript query to block the appropriate user ID.

By default, queries show notifications after they run. If you're running several queries in succession, you might want to turn these notifications off. To do this, make sure the blockAllUsers query is selected and open the Response tab. Scroll down to Query Success and uncheck the Show notification on success box.

patchUser endpoint URL and body

With all the code written, select the Split Button component and open the Block all users action. Add an event handler that runs the blockAll JavaScript query when the button is clicked.

Add unblocking functionality

You can add functionality to unblock all users by duplicating what you just created and swapping the blocked logic to unblock users.

First, create a JavaScript query to unblock all users using the following code.

var rows = table1.data;

const blockedUsers = rows
.map((row, index) => [row, index])
.filter(([row]) => row.blocked);

let unblockIndex = 0;

const unblockNext = () => {
// Notify and quit when finished
if (unblockIndex >= blockedUsers.length) {
utils.showNotification({
title: "Success",
description: "Successfully unblocked " + blockedUsers.length + " users.",
notificationType: "success",
duration: 3,
});

// Update table after queries finish
getUsers.trigger();

return;
}

// Get next blocked user and unblock them
const [blockedUser, index] = blockedUsers[unblockIndex];
unblockAllUsers.trigger({
additionalScope: { i: index },
onSuccess: function () {
unblockIndex++;
unblockNext();
},
});
};

unblockNext();

Then, create an unblockAllUsers query that sets blocked to false. Make sure to turn off the success notification for this query.

Wrap up

You've successfully built an app that allows someone to view a list of users and their attributes, edit user names and emails, and mark some or all of them as blocked or unblocked.

To continue exploring Retool and building out your sample app, consider adding other components, arranging a mobile layout, or connecting another resource like Retool Email.