Skip to main content
RapidDev - Software Development Agency
retool-tutorial

How to Create Notifications in Retool

Create toast notifications in Retool using utils.showNotification({ title: 'Done', description: 'Record saved', notificationType: 'success', duration: 3 }). Call this function in JS Queries, or add it as a 'Show notification' action in event handlers on any component or query success/failure callbacks.

What you'll learn

  • How to call utils.showNotification() with type, title, description, and duration
  • How to trigger notifications from query success and failure event handlers
  • How to show different notification types: success, warning, error, info
  • How to customize notification content dynamically with {{ }} data references
  • The difference between in-app notifications (utils.showNotification) and external alerts
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read10-15 minRetool Cloud and Self-hostedLast updated March 2026RapidDev Engineering Team
TL;DR

Create toast notifications in Retool using utils.showNotification({ title: 'Done', description: 'Record saved', notificationType: 'success', duration: 3 }). Call this function in JS Queries, or add it as a 'Show notification' action in event handlers on any component or query success/failure callbacks.

Quick facts about this guide
FactValue
ToolRetool
DifficultyBeginner
Time required10-15 min
CompatibilityRetool Cloud and Self-hosted
Last updatedMarch 2026

In-App Toast Notifications with utils.showNotification()

Retool's utils.showNotification() displays non-blocking toast notifications in the bottom-right corner of your app. These are the primary way to give users feedback after an action — confirming a save was successful, warning about a data issue, or reporting an error.

Notifications can be triggered from three places: directly in a JS Query, via a 'Show notification' action in an event handler, or from a query's built-in success/failure event handlers. This tutorial covers all three approaches and shows how to customize notification content dynamically.

Prerequisites

  • A Retool app with at least one query or button component
  • Basic familiarity with Retool's event handler system in the Inspector
  • Understanding of Retool JS Queries (for dynamic notifications)

Step-by-step guide

1

Call utils.showNotification() in a JS Query

The most flexible way to show a notification is in a JS Query. This lets you use dynamic data in the notification content and show notifications conditionally based on query results. utils.showNotification() accepts an object with title (required), description (optional), notificationType (success | error | warning | info), and duration (seconds, default 3).

typescript
1// Basic notification types:
2
3// Success (green):
4utils.showNotification({
5 title: 'Record saved',
6 description: 'User profile updated successfully.',
7 notificationType: 'success',
8 duration: 3
9});
10
11// Error (red):
12utils.showNotification({
13 title: 'Save failed',
14 description: 'Please check your input and try again.',
15 notificationType: 'error',
16 duration: 5
17});
18
19// Warning (yellow):
20utils.showNotification({
21 title: 'Low inventory',
22 description: 'Stock level below minimum threshold.',
23 notificationType: 'warning',
24 duration: 4
25});
26
27// Info (blue):
28utils.showNotification({
29 title: 'Processing',
30 description: 'Your export is being prepared.',
31 notificationType: 'info',
32 duration: 2
33});

Expected result: Toast notifications appear in the bottom-right corner of the app with the appropriate color and icon.

2

Add a 'Show Notification' Action to an Event Handler

For simple notifications that don't require dynamic content, use the 'Show notification' action directly in a component's event handler without writing a JS Query. This is available for button clicks, form submissions, and other component events. In the Inspector's Interaction section, click '+ Add event handler', select the event (e.g. 'Click'), and choose 'Show notification' as the action. Configure the title, description, type, and duration in the action fields.

Expected result: Clicking the button shows the configured notification without needing a JS Query.

3

Trigger Notifications on Query Success and Failure

Every Retool query has built-in event handlers for success and failure. Open your query, go to its 'Event handlers' section (below the query code), and add handlers for 'Success' and 'Failure' events. Choose 'Show notification' as the action for each. The success handler runs when the query completes without error; the failure handler runs when the query throws an error. This is the recommended pattern for data mutation queries (INSERT, UPDATE, DELETE).

typescript
1// Query event handlers in the query editor:
2// Success → Show notification:
3// Title: 'Saved successfully'
4// Description: {{ updateUser.data.rowCount + ' record(s) updated' }}
5// Type: success
6// Duration: 3
7
8// Failure → Show notification:
9// Title: 'Error saving'
10// Description: {{ updateUser.error.message || 'An unexpected error occurred' }}
11// Type: error
12// Duration: 5
13
14// OR equivalently in a JS Query wrapper:
15try {
16 await updateUser.trigger();
17 utils.showNotification({
18 title: 'Saved',
19 description: updateUser.data.rowCount + ' record(s) updated.',
20 notificationType: 'success',
21 duration: 3
22 });
23} catch (err) {
24 utils.showNotification({
25 title: 'Save failed',
26 description: err.message || 'Unknown error',
27 notificationType: 'error',
28 duration: 5
29 });
30}

Expected result: Success and error notifications appear automatically whenever the query runs, without additional button wiring.

4

Show Dynamic Notification Content with Query Data

Include query results or component values in notification messages by building the description string in a JS Query. This is useful for confirming what was created/updated or how many records were affected. Access query results after a trigger with queryName.data.

typescript
1// After inserting a record, show what was created:
2await createOrderQuery.trigger();
3
4const newOrder = createOrderQuery.data?.[0];
5utils.showNotification({
6 title: 'Order created',
7 description: newOrder
8 ? `Order #${newOrder.order_number} for $${Number(newOrder.total).toFixed(2)} is confirmed.`
9 : 'Order created successfully.',
10 notificationType: 'success',
11 duration: 4
12});
13
14// Show affected row count from UPDATE:
15await updateStatusQuery.trigger();
16const rowsAffected = updateStatusQuery.data?.rowCount || 0;
17utils.showNotification({
18 title: rowsAffected > 0 ? 'Status updated' : 'Nothing to update',
19 description: rowsAffected + ' record(s) updated.',
20 notificationType: rowsAffected > 0 ? 'success' : 'warning',
21 duration: 3
22});

Expected result: Notifications include specific details from the query result like order numbers, record counts, or affected items.

5

Show a Progress Notification for Long Operations

For operations that take several seconds (like large exports or bulk inserts), show an 'info' notification with a long duration before starting the operation. Then show a success/error notification when it completes. This keeps users informed while waiting. For very long operations, use the first notification as a 'started' signal and the final notification as a 'done' signal.

typescript
1// JS Query: bulkExportWithNotifications
2
3// Show 'in progress' notification first:
4utils.showNotification({
5 title: 'Export started',
6 description: 'Fetching all records... this may take a moment.',
7 notificationType: 'info',
8 duration: 30 // Long enough to cover the operation
9});
10
11try {
12 // Run the long operation:
13 const result = await getAllRecordsForExport.trigger();
14 const rowCount = result.data?.length || 0;
15
16 // Show completion:
17 utils.showNotification({
18 title: 'Export ready',
19 description: rowCount + ' rows exported successfully.',
20 notificationType: 'success',
21 duration: 4
22 });
23
24 // Trigger the download...
25} catch (err) {
26 utils.showNotification({
27 title: 'Export failed',
28 description: err.message,
29 notificationType: 'error',
30 duration: 5
31 });
32}

Expected result: Users see a 'started' notification immediately and a 'done' or 'error' notification when the operation completes.

6

Conditionally Show Different Notifications Based on Data

Use if/else logic in a JS Query to show different notification types based on the outcome of an operation. This is useful for operations that can partially succeed (e.g. some rows inserted, some skipped) or when you want different messages based on the data state.

typescript
1// After a data import, show contextual notification:
2await importCSVData.trigger();
3
4const result = importCSVData.data;
5const inserted = result?.inserted || 0;
6const skipped = result?.skipped || 0;
7const errors = result?.errors || [];
8
9if (errors.length > 0) {
10 utils.showNotification({
11 title: 'Import completed with errors',
12 description: `${inserted} rows imported, ${errors.length} rows failed. Check logs.`,
13 notificationType: 'warning',
14 duration: 6
15 });
16} else if (skipped > 0) {
17 utils.showNotification({
18 title: 'Import complete',
19 description: `${inserted} new rows added. ${skipped} duplicates skipped.`,
20 notificationType: 'info',
21 duration: 4
22 });
23} else {
24 utils.showNotification({
25 title: 'Import successful',
26 description: `All ${inserted} rows imported successfully.`,
27 notificationType: 'success',
28 duration: 3
29 });
30}

Expected result: The notification type and message accurately reflect the actual outcome of the operation.

Complete working example

JS Query: saveWithNotification
1// Complete save flow with contextual notifications
2// Triggered by a 'Save' button
3
4// Validate before saving
5const name = nameInput.value?.trim();
6const email = emailInput.value?.trim();
7
8if (!name || !email) {
9 utils.showNotification({
10 title: 'Validation error',
11 description: 'Name and email are required.',
12 notificationType: 'error',
13 duration: 4
14 });
15 return;
16}
17
18// Validate email format
19const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
20if (!emailRegex.test(email)) {
21 utils.showNotification({
22 title: 'Invalid email',
23 description: 'Please enter a valid email address.',
24 notificationType: 'warning',
25 duration: 4
26 });
27 return;
28}
29
30try {
31 // Perform the save
32 await saveUserQuery.trigger();
33
34 const rowsAffected = saveUserQuery.data?.rowCount || 0;
35
36 if (rowsAffected > 0) {
37 utils.showNotification({
38 title: 'Profile saved',
39 description: `${name}'s profile has been updated.`,
40 notificationType: 'success',
41 duration: 3
42 });
43 // Refresh the main data table
44 await query1.trigger();
45 } else {
46 utils.showNotification({
47 title: 'No changes',
48 description: 'Nothing was updated.',
49 notificationType: 'info',
50 duration: 3
51 });
52 }
53} catch (err) {
54 utils.showNotification({
55 title: 'Save failed',
56 description: err.message || 'An unexpected error occurred. Please try again.',
57 notificationType: 'error',
58 duration: 6
59 });
60}

Common mistakes when creating Notifications in Retool

Why it's a problem: Showing a success notification before awaiting the query, which displays success even if the query fails

How to avoid: Always show the notification after the await: 'await saveQuery.trigger(); utils.showNotification({...})'. Placing the notification before the await or without await means it shows regardless of success or failure.

Why it's a problem: Using the same short duration for all notification types

How to avoid: Error messages need more reading time. Use 3 seconds for success, 4 for warning, 5-6 for error. A 2-second error message often dismisses before the user reads it.

Why it's a problem: Not showing any notification after a delete or save operation, leaving users uncertain

How to avoid: Every data mutation should produce a notification. Users expect visual confirmation that their action was processed — without it, they often click the button multiple times.

Why it's a problem: Catching errors but not showing a notification in the catch block

How to avoid: Always include a utils.showNotification() in your catch block: catch(err) { utils.showNotification({ title: 'Error', description: err.message, notificationType: 'error' }); }. Without this, errors fail silently.

Best practices

  • Always show a notification after data mutations (INSERT, UPDATE, DELETE) — users need confirmation that their action was processed.
  • Use 'error' type for failures, 'success' for completions, 'warning' for partial success or data concerns, and 'info' for status updates that don't require user action.
  • Set longer durations for errors (5-8 seconds) and warnings (4-5 seconds) since users need more time to read them than success messages (2-3 seconds).
  • Include specific details in error notifications (what failed and why) rather than generic 'An error occurred' messages.
  • Use try/catch in JS Queries to catch query errors and show a user-friendly error notification instead of letting Retool show its default error banner.
  • For bulk operations, show a count of affected records: '42 records updated successfully.' rather than just 'Done.'
  • Notifications are temporary — for critical information users must not miss, use a Modal dialog component instead.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a Retool app with a form that saves user data. Write a JS Query named 'saveUser' that: (1) validates that name and email inputs are not empty, showing a warning notification if validation fails, (2) triggers a saveUserQuery, (3) shows a success notification with the user's name if the save succeeds (check rowCount > 0), (4) shows a 'no changes' info notification if rowCount is 0, and (5) shows an error notification in the catch block if the query throws. Use utils.showNotification() with appropriate types and durations.

Retool Prompt

Add contextual notifications to my Retool save form. After the saveUserQuery runs: show 'Profile saved' (success, 3s) if rowCount > 0, 'Nothing updated' (info, 3s) if rowCount is 0, and 'Save failed' with err.message (error, 5s) on error. Also add query-level event handlers to my deleteUserQuery showing 'User deleted' on success and 'Delete failed' on failure. Use utils.showNotification() syntax.

Frequently asked questions

Where do Retool toast notifications appear on screen?

Toast notifications from utils.showNotification() appear in the bottom-right corner of the Retool app window. They stack if multiple are shown simultaneously. The position cannot be changed without custom CSS targeting Retool's notification container class.

Can I show a Retool notification that stays on screen indefinitely?

Set the duration to a very large number (e.g. 999999) to keep a notification visible until the user dismisses it. However, Retool notifications do not have a manual dismiss button in all versions. For persistent important messages that users must acknowledge, use a Modal dialog component instead.

What is the difference between utils.showNotification() and query error banners in Retool?

Query error banners appear automatically at the top of the page when a query fails — they are Retool's default error display. utils.showNotification() is a programmatic API you call explicitly to show custom messages with your own wording, timing, and type. For better UX, catch query errors and show custom notifications instead of relying on Retool's default error banners.

Can I use utils.showNotification() in a transformer?

No. Transformers are read-only and synchronous — they cannot call utils.showNotification() or any other side-effect function. Call utils.showNotification() only in JS Queries or event handler actions.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Learning is great. Shipping is faster with help.

Our engineers have built 600+ apps on Retool and the tools around it. If your project needs to be live sooner than your learning curve allows — book a free consultation.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.