Scripting Retool

Learn how to run JavaScript in your Retool apps.

You can write JavaScript to interact with components and queries. This is useful for adding custom logic to your apps. To get started, create a new query in the bottom pane and select the Run JS Code (JavaScript) resource.

In the editor, you can write code to set component properties, trigger queries, and access useful JavaScript libraries. The rest of this page covers some examples, but you might also want to check out the Component Library. Each component includes documentation on supported JavaScript methods.

📘

Security notes

To ensure safety and security for all users, certain interactions with the browser and context outside of Retool apps can't be run. For example, you can't access browser events or use libraries like jQuery.

Run an API request for each row in a table


This examples makes an API request for each row in a table, and then shows any errors returned
in the process.

1. Add a table

Add a Table component to your app and copy this JSON into the Data attribute.

[
  {
    "id": 1,
    "name": "Hanson Deck",
    "email": "[email protected]",
    "sales": 37
  },
  {
    "id": 2,
    "name": "Sue Shei",
    "email": "[email protected]",
    "sales": 550
  },
  {
    "id": 3,
    "name": "Jason Response",
    "email": "[email protected]",
    "sales": 55
  },
  {
    "id": 4,
    "name": "Cher Actor",
    "email": "[email protected]",
    "sales": 424
  },
  {
    "id": 5,
    "name": "Erica Widget",
    "email": "[email protected]",
    "sales": 243
  }
]

2. Create a query

Create a query using the RestQuery (restapi) resource. Set the Action type to Post and use this URL: https://approvals.tryretool.com/api/users/approve?email={{table1.data[i].email}}. The URL parameters are populated automatically.

By default, the i property is 0 but JavaScript in a subsequent step will increment this value so the query runs on each row.

3. Add a Button and Text components

Add a Button and two Text components to your app. Status shows the query's progress and Errors displays any errors while the query runs.

4. Write the JavaScript query

Create a JavaScript query named query2 and add the following JavaScript.

var rows = table1.data;
var errors = "";
var total = rows.length;

function runQuery(i) {
  // Update the Status text
  Status.setValue("Progress: " + (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
    // You can use the argument to get the data with the onSuccess function
    onSuccess: function (data) {
      runQuery(i + 1);
    },
    onFailure: function (error) {
      // Update the Errors text
      errors += "Found error at line " + i.toString() + ":  " + error + "\n\n";
      Errors.setValue(errors);
      runQuery(i + 1);
    },
  });
}

runQuery(0);

5. Add an event handler to your button

After saving query2, add an event handler to your button that triggers the JavaScript query.

Now click the Submit button to test the app. As the query runs on each row, the status updates and errors are displayed. Since the API endpoint at https://approvals.tryretool.com/api/users/approve doesn't exist, all the requests fail.

Clear state after running a query


You can use the following snippet to clear state after a query runs.

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

Trigger a query


This snippet programmatically triggers a query.

query1.trigger();

You can also pass additional arguments to customize the behavior of a query.

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 more variables to the query that aren't defined on the global scope. In this example, you could now use {{name}} in query1.

The onSuccess or onFailure callback is called after the function completes. Here's an example of additionalScope being used in an app:

This JS query passes `additionalScope` values into `query1`. `query1` won't return any data until it gets triggered from this JS query.This JS query passes `additionalScope` values into `query1`. `query1` won't return any data until it gets triggered from this JS query.

`query1` won't properly run on its own since `first_scope` isn't defined until it's directly passed in from the JS query's `.trigger()`. Data is returned after the JS query that triggers `query1` finishes.`query1` won't properly run on its own since `first_scope` isn't defined until it's directly passed in from the JS query's `.trigger()`. Data is returned after the JS query that triggers `query1` finishes.

Retrieve triggering components


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

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

Retrieve triggering component indexes


If a query is triggered by a table action or a button in a list view, the variable i is defined in the query and returns the component's index in that table or list view.

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

Trigger a query for each item in an array


This script triggers a query for each item in an array.

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);

Return data


Besides manipulating components and queries, JavaScript queries can also return data. For example, this script generates a random number.

return Math.random();

When this query is triggered, you can access the number generated using the .data property: {{ generateRandomNumber.data }}.

Promises and async queries


JavaScript queries can be asynchronous. If you return a Promise, Retool waits for the promise to resolve before considering the query complete.

Simple promise

Passing in a query to be triggered as the first argument in a Promise.resolve() triggers the other query, waits for it to finish, and then returns 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 that 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, you would trigger query1 for each row in table1, and pass in the id value from each row. Using {{id}} inside query1 evaluates as the provided row id when it's run.

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

return Promise.all(promises);

Resolve and reject

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 two seconds to run, and returns the value 12345 through the resolve function.

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

📘

Ask the community about scripting

If you're running into any issues with writing JavaScript or scripting in Retool, check out some of the answered questions on our community forums, or ask one of your own.

Query methods


You can read more about query methods below. See our Component Library for component methods.

query.trigger(options)

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

{
  additionalScope: {},
  onSuccess: (data) => { },
  onFailure: (error) => { },
}

The additionalScope option allows you to define or override variables used in the query being triggered with any key/values present in the additionalScope object. For example, additionalScope: { test: "New Value" }, {{test}} inside the triggered query returns New Value.

🚧

additionalScope keys are undefined until run

In the triggered query, keys passed in the additionalScope object are only available after the the JavaScript query runs. Until then, those values are undefined.

onSuccess is a callback that's executed when the query successfully finishes running, with the data of the query as an argument. onFailure is a callback that's executed 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 properties on the query.

query.invalidateCache()

This function declares cached query results invalid. The next time a query is triggered, it returns fresh results. See Caching in Retool to learn more about controlling query cache.

state.setValue(value)

Sets the value of the temporary state variable to the value 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 is set without destroying and recreating other values. For example, state.setIn( [ 0, "trout" ], {length_inches: 16 } )

Libraries


Anything between {{ }} is JavaScript. Retool provides the following utility libraries.

NameDocsVersions
lodashhttps://lodash.com/docs/4.17.4
momenthttps://momentjs.com/docs/2.18.1
uuidhttps://github.com/kelektiv/node-uuidBoth v1 and v4; use uuid.v1() or uuid.v4()
numbrohttps://github.com/BenjaminVanRyseghem/numbro2.1.0
papaparsehttps://www.papaparse.com/docs4.6.3

Utilities

utils.openUrl(url, { newTab: boolean = true, forceReload: boolean = false })

Opens a URL (string) in a new tab by default. Pass in { newTab: false } to open the URL in the current tab. Pass in { forceReload: true } to prevent client side routing and force a page reload. The URL string must start with http:// or https://.

utils.openApp(appUuid, { queryParams: {key:'value'}, newTab: boolean = true})

Opens a specific app (string) in a new tab by default. Pass in { newTab: false } to open the app in the current tab. Pass in { queryParams: {key:'value'} } to add query parameters.

utils.downloadFile(data, fileName, fileType)

Downloads the data value using fileName (string) and fileType (string) if possible. To download base64 encoded files, use the following syntax.

utils.downloadFile({base64Binary: BASE64_STRING} , fileName, fileType)

Example: Download query data as CSV

With a Postgres query named query1.data, you can download the data as a CSV using utils.downloadFile(Papa.unparse(formatDataAsArray(query1.data)), 'test_csv', 'csv').

Example: REST Query as a PDF

With a REST query named RESTQuery1 that fetches a PDF from http://somewebsite.com/metrics.pdf, you can call utils.downloadFile(RESTQuery1.data) to download metrics.pdf. You can also change the file's name using utils.downloadFile(RESTQuery1.data, 'custom_file', 'pdf'), which downloads the same data as custom_file.pdf.

utils.showNotification({ title?: String, description?: String, notificationType?: String, duration?: Number })

Shows a notification message on the top right corner of the screen for duration seconds (default 4.5s). Use this to display messages like error messages after a query fails. Supported notificationTypes are "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 by using {{ localStorage.values.yourKey }}. You can clear the localStorage with the JavaScript function localStorage.clear().

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

utils.downloadPage(fileName, { selectorsToExclude, componentsToExclude, scale, fullscreen })

Downloads the current Retool page as a PDF.

PropertyTypeNotes
fileNameStringRequired, name to use for the exported file.
componentsToExcludeString[]Optional, lets you specify an array of components by name (e.g., ['select1', 'textinput1']) to exclude from the screenshot.
selectorsToExcludeString[]Optional, lets you specify an array of components by CSS selector (e.g., ['._retool-text4']) to exclude from the screenshot.
scaleNumberOptional, lets you pass a numerical scale parameter to configure the resolution (defaults to window.devicePixelRatio).
fullscreenBooleanOptional, setting to true exports the entire app in presentation mode.

🚧

Custom components, HTML components, and elements embedded in IFrames are excluded from PDF exports.

utils.copyToClipboard( text: string )

Sets the clipboard's content to the provided string value.


Did this page help you?