Scripting Retool

Run JS Code (Perform custom behavior using Javascript)

Retool allows interacting with components and queries through JavaScript methods. This is useful for building interactions between components that aren't currently supported via the properties exposed in the Inspector tab.

To use this feature, let's open up the bottom pane, create a new query and select the Run JS Code in the "Select resource" dropdown.

Here we'll be able to programmatically set component's properties, trigger queries, and access useful Javascript libraries. Below are some examples on how to use Javascript to take your Retool app to the next level.

📘

Safety First!

Not all of Javascript will be available for you to use in your code snippets. In order to ensure safety and security for all users, certain interactions with the browser and the context outside of the Retool app will not be run. For example, you won't have access to browser events or libraries like jQuery.

Example: firing an API request for each row in a CSV

For this example, we'll try to fire an API request for each CSV, and then also show any errors that occurred along the way for each of the rows in the CSV.

1. Uploading a CSV

Create a FilePicker component - and a Table component. We'll use the table to render a preview of the CSV that we upload to Retool - so we'll set the Table's data property to filepicker1.parsedValue. The FilePicker component automatically parses CSVs and JSON files and you can access those values in the parsedValue property.

2. Crafting a query

Now that we have it hooked up, we'll create a query that we want to execute for each row in the table. By default the i property is going to be 0 - our JS Code is going to substitute the i property so that this query ends up executing against different data across the entire run of the query.

3. Add a button and make it execute query1 for each row in the CSV.

We'll create two text components - one to show the current status of the requests (e.g. how many have we processed) and one to show all the errors encountered while processing the CSV. We'll also add a button that will trigger the process.

And then, we'll have button1 trigger the following Javascript query.

var rows = filepicker1.parsedValue;
var errors = '';
var total = rows.length;

function runQuery (i) {
  // Update the statusText with the current progress
  statusText.setValue(i.toString() + '/' + total.toString())
  
  if (i >= rows.length) {
    console.log('Finished running all queries');
    return;
  }

  console.log('Running query for row', i);

  query1.trigger({
    additionalScope: { i: i }, // This is where we override the `i` variable from step 2!
    // You can use the argument to get the data with the onSuccess function
    onSuccess: function(data) {
      runQuery(i + 1);
    },
    onFailure: function(error) {
      // Update the errorsText with all the errors encountered
      errors += 'Found error at line ' + i.toString() + ':  ' + error +  '\n\n';
      errorText.setValue(errors);
      runQuery(i + 1);
    }
  });
}

runQuery(0);

4. Use it!

Since our api endpoint at https://approvals.tryretool.com/api/users/approve doesn't actually exist, all our requests fail and we'll see that we found an error at each of the lines.

Common Use Cases

Clearing state after running a query

To clear state after a query runs, you can use the following code snippet:

userInput.setValue('');
emailInput.setValue('');
pricingTierDropdown.setValue(null);

Triggering a query

To programatically trigger a query, you can type the following:

query1.trigger()

You can also pass in additional arguments to customize the behavior of the query. Here is an example of doing so:

query1.trigger({
  additionalScope: {
    name: 'hi',
  },
  // You can use the argument to get the data with the onSuccess function
  onSuccess: function(data) {
    console.log('Successully ran!');
  }
})

The additionalScope option allows you to pass to the query additional variables not defined on the global scope. Now, in query1 you may use {{name}} to refer to the new variable defined by the added scope.

The onSuccess callback is called once the function successfully completes.

Retrieving Source of Query Trigger

The variable triggeredById will return the name of the component that triggered a query. You can reference this inside of {{ }} in any kind of query to return the name of the component which triggered it to run. If the query was triggered by another query, triggeredById will return undefined.

text1.setValue("I was triggered by component: " + triggeredById)

Retrieving Index of Triggering Component

If a query is triggered by a component inside of a listView or table, the variable i will be defined in the query and return the component's index in that listView or table.

text1.setValue("I was triggered by component at index: " + i)

Triggering a query for each item in an array

Here is a complete example of doing so:

var rows = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }];

function runQuery (i) {
  if (i >= rows.length) {
    console.log('Finished running all queries');
    return;
  }
  var data = rows[i];
  console.log('Running query for row', data);

  query1.trigger({
    additionalScope: {
      data: data
    },
    // You can use the argument to get the data with the onSuccess function
    onSuccess: function(data) {
      runQuery(i + 1);
    }
  });
}

runQuery(0);

Returning data

Besides just manipulating components and queries, Javascript queries can also return data. For example, let's say you want to generate a random number inside a Javascript query, and then use it elsewhere. You can do that by first writing some simple JS in the query (let's call the "query" generateRandomNumber):

return Math.random()

Now, when this query is triggered, you can access the number generated via the .data property on the query (so {{ generateRandomNumber.data }}).

Promises and async queries

Javascript queries can be asynchronous. If you return a Promise, Retool will wait for the promise to resolve before considering the query as "done."

Simple promise

Passing in a query to be triggered as the first argument in a Promise.resolve() will trigger the other query, wait for it to finish, and then return the .data result from the triggered query.

return Promise.resolve(query1.trigger())

Promising an array of query returns

You can also pass in an array of items which are dependent on query triggers, and use Promise.all() to return all of their results in an array of the same length. In this case, we would trigger query1 for each row in table1, and pass in the id value from each row. Using {{id}} inside query1 will evaluate as the provided row id when it is run.

const promises = table1.data.map(row => {
    return query1.trigger({
        additionalScope: {
            "id": row.id
        }
    });
});

return Promise.all(promises);

Resolve, reject

In this case, if you want to return a value from the query, you can pass it in as a parameter to the Promise's resolve function. You can also use the reject function from the Promise to fail the query.

In the following example, the query takes 2 seconds to run, and returns the value 12345 through the resolve function.

return new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(12345)
  }, 2000)
})

Reference: Component and Query Methods.

All queries:

query.trigger(options)

The options is an object with any of the following properties:

{
  additionalScope: {},
  onSuccess: (data) => { },
  onFailure: (error) => { },
}
  • The additionalScope option allows you to define or override the variables used in the query being triggered with any key/values present in the additionalScope object. For example, with additionalScope: { test: "New Value" }, {{test}} inside the triggered query would return "New Value" when it is run.

🚧

additionalScope keys will be undefined until run

In the triggered query, any keys passed in the additionalScope object will be available only when it is actually triggered by the javascript query. Until that exact moment, those values will evaluate as undefined

  • The onSuccess option is a callback that will be called when the query successfully finishes running, with the data of the query as an argument
  • The onFailure option is a callback that will be called when the query encounters an error while running with the error as an argument.

The .trigger function also returns a promise that resolves to the query's .data property.

query.reset()

This function clears the .data and .error property on the query.

Text Component

text.setValue(string | number | null)

Sets the text value of the Text widget to whatever is passed in as an argument

Text Input

textinput.setValue(string | number | null)

Sets the text value of the Text widget to whatever is passed in as an argument

textinput.focus()

Focuses the text input, causing the user's cursor to select inside the input

Table

table.selectRow(index)

Changes the selected row in the table to be the index (integer | array) passed as an argument. Can be a single index or an array of indexes ( [0,4,8] ). Sending null will clear the selection.

table.selectPage(index)

Changes the selected page in the table to be the index (integer) passed as an argument.

table.setData(data)

Overrides the data of the Table to whatever is passed as an argument. Any input type is accepted, but only an array of objects or object of arrays will display correctly.
[ { key1: val, key2: val }, { key1: val, key2: val } ] or { key1: [val, val], key2: [val, val] }

table.setSort(columnName, descending)

Sorts the table by a columnName (string) in a specified direction, true for descending, false for ascending.
table.setSort("name", true)

Passing in null will reset the sorting of the table:
table.setSort(null)

JSON Schema Form

jsonschemaform.clear()

Clears all the inputs in the JSON Schema Form

Form

form.clear()

Clears all the inputs in the Form

Tabbed Container

tabbedcontainer.selectTab(index)

Changes the selected tab in the tabbed container to be the index (integer) passed as an argument.

Modal

modal.open()

Opens a modal

modal.close()

Closes the modal

Temporary State

state.setValue(value)

Sets the value of the temporary state variable to whatever value is passed in as an argument

state.setIn(path, value)

Sets the value of state.value at a specified location. The path variable accepts an array of keys/indexes to select. The value will be set without destroying and recreating the other values of the state.
state.setIn( [ 0, "trout" ], {length_inches: 16 } )

Controls

textinput.setValue(string | number | null)

Sets the value of the TextInput to the string or number passed as an argument, or clears the component if null is sent.

checkbox.setValue(bool)

Sets the value of the Checkbox to whatever boolean is passed in as an argument. Sending null here will change the value to null, but not uncheck the box graphically.

texteditor.setValue(string)

Sets the value of the Rich Text Editor to whatever is passed in as an argument

jsoneditor.setValue(value)

Sets the value of the JSON Editor to whatever is passed in as an argument.

datetimepicker.setValue(string | date)

Sets the value of the Date Time Picker to whatever is passed in as an argument. It will attempt to parse any string value as a date.

daterangepicker.setStartValue(string | date)

daterangepicker.setEndValue(string | date)

Sets the start or end value of the Date Range Picker to whatever is passed in as an argument. It will attempt to parse any string value as a date.

daterangepicker.setRange( [ startValue, endvalue ] )

Sets the start and end values of the Date Range picker. Accepts and array of two dates or strings as the argument for the start and end values.

select.setValue(string | number | null)

Sets the value of the Select component (Dropdown) to whatever is passed in as an argument

multiselect.setValue(string[] | number[])

Sets the value of the Multi-Select component to whatever is passed in as an argument. The argument should be an array since the multiselect's .value property is an array.

iframe.reload()

No arguments needed. Reloads the current URL of the targeted iFrame component.

radiogroup.setValue(string | number | null)

Sets the value of the Radio Group to whatever is passed in as an argument

buttongroup.setValue(string)

Sets the value of the Button Group to whatever is passed in as an argument

rate.setValue(number)

Sets the value of the Rating component to whatever is passed in as an argument

Filepicker

filepicker.reset()

Resets the filepicker to it's default state (no file selected).

Utilities

utils.openUrl(url, inNewPage)

Opens a URL(string) in a new page by default. It can also be configured to open the URL in the same page by passing false in the second argument.

utils.downloadFile(data, fileName, fileType)

Downloads the data value using fileName (string) and fileType (string) if possible

👍

Example

RESTQuery1 fetches from http://somewebsite.com/metrics.pdf

Call utils.downloadFile(RESTQuery1.data) to download metrics.pdf
Call utils.downloadFile(RESTQuery1.data, 'custom_file', 'docx') to download the same file as custom_file.docx

utils.showNotification({ title: string, description: string, notificationType: string })

Shows a notification message on the top right corner of the screen. Use this to display messages like error messages after a query fails. Title and Description are strings, accepts string for notificationTypes of : 'info' | 'success' | 'warning' | 'error'.

localStorage.setValue(key, value)

Saves a key-value pair to localStorage. The key must be a string and the value can be any type. You can access the data from localStorage elsewhere by using {{ localStorage.values.yourKey }}. You can clear the localStorage with the JavaScript function localStorage.clear().

Tip: to remove a single key-value pair from localStorage, you can set the value to undefined, like this: localStorage.setValue("removeThisKey",undefined).

Libraries

Anything between {{ }} is Javascript. We give you the following utility libraries:

Name

Docs

Versions

lodash

https://lodash.com/docs/

4.17.4

moment

https://momentjs.com/docs/

2.18.1

uuid

https://github.com/kelektiv/node-uuid

Both v1 and v4; use uuid.v1() or uuid.v4()

numbro

https://github.com/BenjaminVanRyseghem/numbro

2.1.0

Updated about a month ago


Scripting Retool


Run JS Code (Perform custom behavior using Javascript)

Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.