Skip to main content
Technology & EngineeringAutomation Workflow Services211 lines

Retool

Build internal tools with Retool using queries, components, transformers, and workflows. Configure resource connections, JavaScript transformers, and event-driven UI logic for admin panels and dashboards.

Quick Summary25 lines
You are an expert in Retool, building internal tools with SQL/API queries, drag-and-drop components, JavaScript transformers, and Retool Workflows. You design maintainable admin panels, dashboards, and operational tools.

## Key Points

1. **Writing raw SQL with unparameterized user input** - Always use `{{ }}` template bindings which Retool parameterizes automatically. Never concatenate strings in SQL queries.
2. **Triggering all queries on page load** - Mark queries as "manual" if they depend on user selection. Only auto-run queries that populate initial page data.
3. **Using temporary state for everything** - Prefer query results and component values as the source of truth. Temporary state should only hold UI-specific state not derivable from queries.
4. **Building one monolithic app** - Split complex tools into modules or separate apps linked via URL params. Large apps become slow and hard to maintain.
- Building admin panels for customer support, order management, or user operations
- Creating dashboards that combine data from multiple databases and REST APIs
- Rapid prototyping of internal tools that would take weeks to build from scratch
- Non-frontend engineers need to build data-driven tools without learning React
- Operational workflows requiring human-in-the-loop approval steps with audit trails

## Quick Example

```typescript
// Table1 settings:
// - Data source: {{ getUsers.data }}
// - Editable columns: name, email, role
// - Save button onClick: updateUser.trigger().then(() => getUsers.trigger())
// - Pagination: server-side, total: {{ countUsers.data[0].total }}
```
skilldb get automation-workflow-services-skills/retoolFull skill: 211 lines
Paste into your CLAUDE.md or agent config

Retool Internal Tools

You are an expert in Retool, building internal tools with SQL/API queries, drag-and-drop components, JavaScript transformers, and Retool Workflows. You design maintainable admin panels, dashboards, and operational tools.

Core Philosophy

Query-First Design

Start every Retool app by defining the data queries. Components bind to query results. Design your SQL queries and API calls first, then build the UI around the data shape. This prevents rework when data requirements change.

Reactive Binding Over Imperative Code

Retool components update reactively when their bound queries or state change. Use {{ }} bindings in component properties instead of writing imperative event handlers to set values. Reserve JavaScript for transformations and complex logic.

Least Privilege Resource Access

Each Retool resource connection should use database credentials scoped to the minimum required permissions. Read-only dashboards should connect via read-only database users. Write operations should use separate resources with audit logging.

Setup

// Retool API for programmatic management
const RETOOL_API = "https://your-org.retool.com/api/v2";
const headers = {
  Authorization: `Bearer ${process.env.RETOOL_API_KEY}`,
  "Content-Type": "application/json",
};

// List all apps
const response = await fetch(`${RETOOL_API}/apps`, { headers });
const { data: apps } = await response.json();

// Create a new folder
await fetch(`${RETOOL_API}/folders`, {
  method: "POST",
  headers,
  body: JSON.stringify({ name: "Admin Tools", parentFolderId: null }),
});
-- Resource configuration: PostgreSQL example
-- Host: db.internal.example.com
-- Port: 5432
-- Database: operations
-- User: retool_readonly (for read queries)
-- SSL: Required

Key Patterns

Bind Components to Query Data

// Do: Use reactive bindings in component properties
// Table component data source: {{ getOrders.data }}
// Text component value: {{ `Total: ${getOrders.data.length} orders` }}
// Select component options: {{ getStatuses.data.map(s => ({ label: s.name, value: s.id })) }}

// Don't: Use event handlers to manually set table data after query runs
// Avoid: query1.trigger() then table1.setData(query1.data) in a script

Use Transformers for Data Shaping

// Do: Create a transformer for complex data manipulation
// Transformer: enrichedOrders
const orders = {{ getOrders.data }};
const users = {{ getUsers.data }};
const userMap = new Map(users.map(u => [u.id, u]));

return orders.map(order => ({
  ...order,
  customerName: userMap.get(order.userId)?.name ?? "Unknown",
  totalFormatted: `$${order.total.toFixed(2)}`,
  isOverdue: new Date(order.dueDate) < new Date(),
}));

// Bind table to: {{ enrichedOrders.value }}

// Don't: Put heavy transformation logic directly in component bindings

Implement Confirmation for Destructive Actions

// Do: Use modal + confirmation for write operations
// Button "Delete Order" onClick:
//   1. modal1.open()
// Modal "Confirm Delete" confirm button onClick:
//   1. deleteOrder.trigger({ additionalScope: { orderId: table1.selectedRow.id } })
//   2. modal1.close()
//   3. getOrders.trigger() // refresh data

// Don't: Wire delete buttons directly to destructive queries without confirmation

Common Patterns

CRUD Table with Inline Editing

-- Query: getUsers (SQL)
SELECT id, name, email, role, created_at
FROM users
WHERE name ILIKE {{ '%' + searchInput.value + '%' }}
ORDER BY created_at DESC
LIMIT {{ pagination1.pageSize }}
OFFSET {{ (pagination1.currentPage - 1) * pagination1.pageSize }};

-- Query: updateUser (SQL, triggered manually)
UPDATE users
SET name = {{ table1.changesetObject.name }},
    email = {{ table1.changesetObject.email }},
    role = {{ table1.changesetObject.role }}
WHERE id = {{ table1.selectedRow.id }};

-- Query: countUsers (SQL, for pagination)
SELECT COUNT(*) as total FROM users
WHERE name ILIKE {{ '%' + searchInput.value + '%' }};
// Table1 settings:
// - Data source: {{ getUsers.data }}
// - Editable columns: name, email, role
// - Save button onClick: updateUser.trigger().then(() => getUsers.trigger())
// - Pagination: server-side, total: {{ countUsers.data[0].total }}

REST API Query with Error Handling

// Query: createTicket (REST API resource)
// Method: POST
// URL: /api/tickets
// Body:
{
  "title": {{ titleInput.value }},
  "priority": {{ prioritySelect.value }},
  "assignee": {{ assigneeSelect.value }},
  "description": {{ descriptionEditor.value }}
}

// Event handlers:
// onSuccess:
//   utils.showNotification({ title: "Success", description: "Ticket created" });
//   getTickets.trigger();
//   titleInput.setValue("");
// onFailure:
//   utils.showNotification({ title: "Error", description: createTicket.error, notificationType: "error" });

Retool Workflow for Background Processing

// Retool Workflow: processRefunds
// Trigger: Webhook or scheduled

// Step 1: Query - getPendingRefunds
// SELECT * FROM refunds WHERE status = 'pending' LIMIT 50;

// Step 2: Loop block over {{ getPendingRefunds.data }}
//   Step 2a: REST API - processRefund
//   POST https://payments.example.com/refunds
//   { "chargeId": {{ value.charge_id }}, "amount": {{ value.amount }} }
//
//   Step 2b: Query - markProcessed
//   UPDATE refunds SET status = 'processed' WHERE id = {{ value.id }};

// Step 3: Code block - summary
return {
  processed: {{ loop1.data.length }},
  timestamp: new Date().toISOString(),
};

// Step 4: Slack notification with summary

Dynamic Form with Conditional Fields

// Form component settings:
// - typeSelect: options ["bug", "feature", "question"]
//
// Conditional visibility using component properties:
// severitySelect.hidden: {{ typeSelect.value !== "bug" }}
// timelineInput.hidden: {{ typeSelect.value !== "feature" }}
//
// Form submit handler:
// submitTicket.trigger({
//   additionalScope: {
//     type: typeSelect.value,
//     severity: typeSelect.value === "bug" ? severitySelect.value : null,
//     timeline: typeSelect.value === "feature" ? timelineInput.value : null,
//   }
// });

Anti-Patterns

  1. Writing raw SQL with unparameterized user input - Always use {{ }} template bindings which Retool parameterizes automatically. Never concatenate strings in SQL queries.
  2. Triggering all queries on page load - Mark queries as "manual" if they depend on user selection. Only auto-run queries that populate initial page data.
  3. Using temporary state for everything - Prefer query results and component values as the source of truth. Temporary state should only hold UI-specific state not derivable from queries.
  4. Building one monolithic app - Split complex tools into modules or separate apps linked via URL params. Large apps become slow and hard to maintain.

When to Use

  • Building admin panels for customer support, order management, or user operations
  • Creating dashboards that combine data from multiple databases and REST APIs
  • Rapid prototyping of internal tools that would take weeks to build from scratch
  • Non-frontend engineers need to build data-driven tools without learning React
  • Operational workflows requiring human-in-the-loop approval steps with audit trails

Install this skill directly: skilldb add automation-workflow-services-skills

Get CLI access →

Related Skills

Superblocks

Build internal tools and workflows with Superblocks using API integrations, UI components, scheduled jobs, and permissions. Configure backend logic, frontend applications, and role-based access control for enterprise-grade admin panels.

Automation Workflow Services271L

Val Town

Create serverless functions with Val Town using vals for HTTP handlers, cron jobs, email handlers, and SQLite storage. Configure public and private vals with TypeScript, leveraging the built-in runtime for rapid API prototyping.

Automation Workflow Services266L

Windmill

Build scripts, flows, and apps with Windmill using TypeScript and Python runtimes. Configure approval steps, schedules, error handlers, and resource types for developer-centric workflow automation.

Automation Workflow Services262L

Zapier

Integrate with the Zapier Platform API to build triggers, actions, and multi-step Zaps programmatically. Configure webhooks, code steps, and authentication flows for automated workflows connecting thousands of apps.

Automation Workflow Services191L

Make Integromat

Build and manage Make (formerly Integromat) scenarios using modules, routers, webhooks, and data stores. Configure complex branching workflows with error handling, iterators, and aggregators for visual automation.

Automation Workflow Services209L

N8n

Build self-hosted and cloud workflow automations with n8n using nodes, expressions, webhooks, and code nodes. Configure credential management, error workflows, and custom node development for event-driven pipelines.

Automation Workflow Services227L