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

How to Schedule Tasks in Retool

Schedule recurring tasks in Retool using Retool Workflows with a Schedule trigger. Create a Workflow, click the Trigger block, select 'Schedule', and configure a cron expression (e.g. '0 8 * * *' for 8 AM daily). Add query blocks, code blocks, and notification blocks to define what the workflow does. Workflows run server-side without a user present.

What you'll learn

  • How to create a Retool Workflow with a schedule trigger using cron expressions
  • How to build recurring tasks like daily reports and weekly cleanups in Workflows
  • How to trigger a Workflow on demand from a Retool app using a webhook trigger
  • How to use the Code block for custom JavaScript logic in Workflow automation
  • How to monitor and debug Workflow run history and errors
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read20-25 minRetool Cloud and Self-hostedLast updated March 2026RapidDev Engineering Team
TL;DR

Schedule recurring tasks in Retool using Retool Workflows with a Schedule trigger. Create a Workflow, click the Trigger block, select 'Schedule', and configure a cron expression (e.g. '0 8 * * *' for 8 AM daily). Add query blocks, code blocks, and notification blocks to define what the workflow does. Workflows run server-side without a user present.

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

Automate Recurring Tasks with Retool Workflows

Retool Workflows is Retool's built-in automation engine. It lets you run scheduled jobs, process background tasks, and respond to webhooks without needing a separate cron service or Lambda function. Workflows run entirely server-side — they can query databases, call APIs, send emails, and execute JavaScript even when no users are in the Retool app.

Common scheduled tasks include: daily data sync between systems, weekly summary reports emailed to stakeholders, nightly cleanup of expired records, hourly monitoring queries that trigger alerts when thresholds are exceeded, and data transformation pipelines that run on a schedule.

This tutorial covers creating scheduled workflows, writing cron expressions, building multi-step automation logic, and triggering workflows from Retool apps on demand.

Prerequisites

  • Retool account on a paid plan with Workflows access
  • Understanding of what the automated task needs to do (query, transform, notify, etc.)
  • Optional: familiarity with cron syntax for custom schedules
  • For app-triggered workflows: an existing Retool app

Step-by-step guide

1

Create a New Retool Workflow

Navigate to the Workflows section from the Retool main sidebar. Click '+ New Workflow'. Give it a descriptive name like 'daily-report-workflow' or 'inventory-sync'. The Workflow editor opens with a blank canvas showing an initial Trigger block. Workflows are separate from Retool apps — they have their own editor with a block-based flow designer. Each block is a discrete operation: query, code, condition, email, HTTP request, etc.

Expected result: A new Workflow editor opens with an empty canvas.

2

Configure the Schedule Trigger

Click the Trigger block at the top of the canvas. The trigger configuration panel opens on the right. Select 'Schedule' as the trigger type. Choose between preset intervals (every N minutes, hourly, daily, weekly) or enter a custom cron expression for more control. The cron expression follows standard 5-field syntax: minute hour day-of-month month day-of-week. Timezone selection is important — Retool executes cron in UTC by default. Adjust your time appropriately.

typescript
1// Common cron expressions:
2// Every hour at top of hour: 0 * * * *
3// Daily at 8 AM UTC: 0 8 * * *
4// Daily at 9 AM Eastern: 0 14 * * * (9 AM EST = 14:00 UTC)
5// Weekdays at 8 AM UTC: 0 8 * * 1-5
6// Every Monday at 9 AM: 0 9 * * 1
7// First day of month: 0 8 1 * *
8// Every 15 minutes: */15 * * * *
9// Twice daily (8 AM + 8 PM): 0 8,20 * * *
10
11// Cron syntax: minute hour day month weekday
12// 0 = Sunday, 1 = Monday, ... 6 = Saturday

Expected result: The trigger is configured to fire at the specified schedule. The workflow will activate when you publish it.

3

Add a Database Query Block

Click the '+' button below the Trigger block to add a new block. Select 'Resource query' (or 'Query') to add a database query. Choose your resource from the dropdown and write your SQL. In Workflows, query results are accessed in subsequent blocks as blocks.queryBlockName.data. Block names are set by clicking the block name label at the top of each block.

typescript
1-- Example: daily cleanup query (block name: deleteExpiredSessions)
2DELETE FROM user_sessions
3WHERE expires_at < NOW()
4RETURNING id;
5
6-- Example: daily report query (block name: getDailyStats)
7SELECT
8 DATE_TRUNC('day', created_at)::date AS date,
9 COUNT(*) AS order_count,
10 SUM(total_amount) AS revenue,
11 COUNT(*) FILTER (WHERE status = 'refunded') AS refunds
12FROM orders
13WHERE created_at >= CURRENT_DATE - INTERVAL '1 day'
14 AND created_at < CURRENT_DATE
15GROUP BY 1;
16
17-- Example: sync check (block name: getUnsyncedRecords)
18SELECT id, data, created_at
19FROM queue_items
20WHERE synced = false
21 AND created_at < NOW() - INTERVAL '5 minutes'
22ORDER BY created_at ASC
23LIMIT 100;

Expected result: The query block is configured and returns data when tested manually with the 'Run' button in the Workflow editor.

4

Add a Code Block for Custom Logic

Add a Code block (JavaScript) after your query for data transformation, conditional logic, or formatting. Code blocks in Workflows work similarly to JS Queries in apps but have access to blocks.blockName.data to reference previous block outputs. Code blocks support async/await and can access Retool configuration variables via retoolContext.configVars.NAME.

typescript
1// Code block: buildReport
2// Processes getDailyStats.data into a formatted report
3
4const stats = blocks.getDailyStats.data;
5
6if (!stats || stats.length === 0) {
7 return { hasData: false, message: 'No orders yesterday.' };
8}
9
10const day = stats[0];
11const revenueFormatted = Number(day.revenue || 0).toLocaleString('en-US', {
12 style: 'currency', currency: 'USD'
13});
14
15const reportLines = [
16 `Date: ${day.date}`,
17 `Orders: ${day.order_count}`,
18 `Revenue: ${revenueFormatted}`,
19 `Refunds: ${day.refunds}`
20];
21
22return {
23 hasData: true,
24 summary: reportLines.join('\n'),
25 revenue: Number(day.revenue || 0),
26 orderCount: Number(day.order_count || 0)
27};

Expected result: The code block processes query data and returns a structured result used by downstream blocks.

5

Add a Condition Block for Branching Logic

Condition blocks let you branch the workflow based on data conditions. This is essential for scheduled workflows — only send a report if there's data, only run cleanup if there are expired records, only sync if the queue is non-empty. Add a Condition block and configure its expression using block output references.

typescript
1// Condition block: hasDataToProcess
2// Expression (True/False):
3{{ blocks.buildReport.data.hasData === true }}
4
5// Connect True branch → notification blocks
6// Connect False branch → End (or a log block)
7
8// Another example: alert only if revenue dropped significantly
9{{ blocks.getDailyStats.data[0]?.revenue < 1000 }}
10// True → send low-revenue alert
11// False → send normal report

Expected result: The workflow branches correctly based on data conditions, skipping unnecessary blocks when there's nothing to process.

6

Set Up a Webhook Trigger for On-Demand Execution

In addition to scheduled triggers, Workflows support a Webhook trigger that lets you execute the workflow on demand via HTTP POST. This is useful for triggering workflows from external systems or from your Retool app. Change the trigger type to 'Webhook' to get a unique URL. Send a POST request to that URL to start the workflow. You can pass payload data in the POST body, accessible in blocks as trigger.data.

typescript
1// Webhook trigger URL: https://api.retool.com/v1/workflows/abc123/startTrigger/...
2// POST body (from external system or Retool app):
3{
4 "userId": 456,
5 "reportType": "monthly",
6 "sendTo": "manager@company.com"
7}
8
9// Access in Workflow blocks:
10// {{ trigger.data.userId }}
11// {{ trigger.data.reportType }}
12
13// From a Retool app, trigger via Resource Query:
14// Resource: Retool Workflows
15// Select workflow and configure inputs
16
17// Or via an HTTP Request from a JS Query:
18await fetch('https://api.retool.com/v1/workflows/abc123/startTrigger/...', {
19 method: 'POST',
20 headers: { 'Content-Type': 'application/json' },
21 body: JSON.stringify({ userId: currentUserId.value })
22});

Expected result: The workflow can be triggered on demand via the webhook URL from any external system or Retool app.

7

Activate and Monitor the Workflow

Once your workflow is built and tested, activate it by clicking the 'Activate' or 'Publish' button in the Workflow editor. Inactive workflows do not run on schedule even if configured. Monitor workflow execution in the Workflow's 'Run History' tab. Each execution shows its status (success/failed), duration, and the output of each block. When a workflow fails, click the failed run to see which block failed and what the error was.

Expected result: The workflow is active, shows runs in the Run History, and executes on the configured schedule.

8

Add Error Handling in Workflows

Workflows can fail silently if a block throws an error and there's no error handling. Add error handling by: (1) using try/catch in Code blocks, (2) configuring block-level 'On failure' behavior (continue vs stop), or (3) adding a parallel error path that sends an alert when the workflow fails. Configure critical workflows to notify you on failure using the Workflow settings panel.

typescript
1// Code block with error handling:
2try {
3 const records = blocks.getUnsyncedRecords.data;
4
5 const results = await Promise.allSettled(
6 records.map(async (record) => {
7 // Process each record
8 const response = await fetch('https://api.external.com/sync', {
9 method: 'POST',
10 headers: { 'Content-Type': 'application/json',
11 'Authorization': `Bearer ${retoolContext.configVars.API_KEY}` },
12 body: JSON.stringify(record.data)
13 });
14 return { id: record.id, success: response.ok };
15 })
16 );
17
18 const successful = results.filter(r => r.status === 'fulfilled' && r.value.success);
19 const failed = results.filter(r => r.status === 'rejected' || !r.value?.success);
20
21 return {
22 synced: successful.length,
23 failed: failed.length,
24 errors: failed.map(f => f.reason?.message || 'Unknown error')
25 };
26} catch (err) {
27 // Return error data instead of throwing (prevents workflow from halting)
28 return { synced: 0, failed: -1, criticalError: err.message };
29}

Expected result: The workflow handles errors gracefully, continuing to process other items even when individual operations fail.

Complete working example

Workflow: dailyReportWorkflow
1// Workflow: dailyReportWorkflow
2// Trigger: Schedule — 0 8 * * 1-5 (8 AM UTC, weekdays)
3// Purpose: Send daily order summary to ops team
4
5// Block 1: getDailyOrders (Resource Query - PostgreSQL)
6SELECT
7 COUNT(*) AS total_orders,
8 SUM(total_amount) AS revenue,
9 COUNT(*) FILTER (WHERE status = 'pending') AS pending,
10 COUNT(*) FILTER (WHERE status = 'completed') AS completed,
11 COUNT(*) FILTER (WHERE status = 'refunded') AS refunded
12FROM orders
13WHERE created_at >= NOW() - INTERVAL '24 hours';
14
15// Block 2: buildReportMessage (Code - JavaScript)
16const data = blocks.getDailyOrders.data[0] || {};
17const yesterday = new Date();
18yesterday.setDate(yesterday.getDate() - 1);
19const dateStr = yesterday.toLocaleDateString('en-US', {
20 weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
21});
22
23const revenue = Number(data.revenue || 0).toLocaleString('en-US', {
24 style: 'currency', currency: 'USD'
25});
26
27return {
28 subject: `Daily Report: ${data.total_orders || 0} orders on ${dateStr}`,
29 html: `
30 <h2>Daily Order Report ${dateStr}</h2>
31 <table border="1" cellpadding="8">
32 <tr><td><b>Total Orders</b></td><td>${data.total_orders || 0}</td></tr>
33 <tr><td><b>Revenue</b></td><td>${revenue}</td></tr>
34 <tr><td><b>Pending</b></td><td>${data.pending || 0}</td></tr>
35 <tr><td><b>Completed</b></td><td>${data.completed || 0}</td></tr>
36 <tr><td><b>Refunded</b></td><td>${data.refunded || 0}</td></tr>
37 </table>
38 `,
39 hasOrders: (data.total_orders || 0) > 0
40};
41
42// Block 3: shouldSendReport (Condition)
43// {{ blocks.buildReportMessage.data.hasOrders }}
44
45// Block 4: sendReport (Email)
46// To: ops-team@yourcompany.com
47// Subject: {{ blocks.buildReportMessage.data.subject }}
48// Body: {{ blocks.buildReportMessage.data.html }} (HTML mode)

Common mistakes

Why it's a problem: Building a workflow but not activating it, then wondering why it never runs

How to avoid: Workflows must be explicitly activated (published) after building. An inactive workflow shows 'Inactive' status and will not run on schedule. Click 'Activate' in the Workflow editor.

Why it's a problem: Using UTC time in cron without accounting for the recipient's timezone

How to avoid: If users expect reports at 8 AM Eastern, set your cron to 8 AM EST/EDT which is 13:00 or 12:00 UTC depending on daylight saving time. Use a timezone converter and note that UTC doesn't observe daylight saving time.

Why it's a problem: Accessing component values like textInput1.value in a Workflow Code block

How to avoid: Workflow Code blocks are server-side and have no access to Retool app components. They can only access block output (blocks.blockName.data), trigger data (trigger.data), and configuration variables (retoolContext.configVars).

Why it's a problem: Not adding error handling, causing the entire workflow to fail silently when one item fails

How to avoid: Wrap risky operations in try/catch inside Code blocks and return error information rather than throwing. Use Promise.allSettled() instead of Promise.all() when processing multiple items in parallel to handle individual failures gracefully.

Best practices

  • Always test workflows manually with the 'Run' button before activating the schedule — this saves time compared to waiting for the next scheduled run.
  • Add a Condition block early in the workflow to exit early if there is nothing to process — avoid sending empty reports or pointless notifications.
  • Store all API keys, webhook URLs, and secrets in Retool configuration variables accessed via retoolContext.configVars — never hardcode credentials in workflow blocks.
  • Use descriptive block names (getDailyOrders, buildReport, sendEmail) instead of default names — block output references are cleaner and easier to maintain.
  • For long-running workflows, break complex logic into multiple Code blocks rather than one giant block — this makes debugging easier since you can see each block's output.
  • Configure 'On failure' email notifications in Workflow settings so your team is alerted when critical scheduled workflows fail.
  • Use RETURNING or record counts in SQL mutation queries to confirm rows were actually affected — log this in the workflow for audit trails.

Still stuck?

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

ChatGPT Prompt

I want to create a Retool Workflow that runs every weekday at 8 AM UTC. It should: (1) query yesterday's orders from PostgreSQL (count, revenue, pending/completed/refunded breakdown), (2) check if there were any orders at all using a Condition block, (3) format the data into an HTML email report, (4) send the report to ops-team@company.com with a subject line including the date and order count. Write the SQL query for block 1, the JavaScript code block for step 3, and show the cron expression for the schedule trigger.

Retool Prompt

Create a Retool Workflow named 'dailyOrderReport' with: (1) Schedule trigger for 8 AM UTC weekdays (cron: 0 8 * * 1-5), (2) PostgreSQL query block named 'getDailyOrders' selecting yesterday's order stats (total count, revenue, by status), (3) Condition block that exits if total_orders is 0, (4) Code block named 'buildMessage' that formats the data as an HTML table, (5) Email block sending to ops@company.com with subject 'Daily Report: N orders'. Include proper block naming and show how to reference block outputs as blocks.blockName.data.

Frequently asked questions

Do Retool Workflows run even when nobody is logged into Retool?

Yes. Retool Workflows run server-side and are completely independent of whether any users are logged in. Scheduled workflows fire based on the cron schedule regardless of user activity. This is what makes them suitable for nightly jobs, data syncs, and automated reports.

What is the minimum schedule interval for a Retool Workflow?

Retool Workflows support schedules as frequent as every 1 minute (cron: */1 * * * *). However, very frequent workflows (under 5 minutes) can consume significant compute resources and may be subject to rate limits depending on your plan. For true real-time processing, consider a webhook trigger rather than polling.

Can a Retool Workflow call another Workflow?

Not directly within the Workflow block builder. However, you can trigger another workflow via an HTTP Request block pointing to the second workflow's webhook URL. Store the target webhook URL in a configuration variable and POST to it from the HTTP Request block.

How do I debug a Retool Workflow that is failing silently?

Check the Run History tab on the Workflow page — every execution (scheduled or manual) is logged with status and duration. Click a failed run to see which block failed and the error message. Also click 'Run' in the Workflow editor to trigger a test run and watch each block execute step by step with visible outputs.

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.