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

How to Set Up Logging in Retool Applications

Retool provides audit logs (Business+) showing all user actions and query executions, accessible in Settings → Audit Logs. For development debugging, use console.log() in JS queries (visible in browser DevTools). Enterprise plans can stream audit logs to Datadog, Splunk, or any SIEM. For custom application-level logging on any plan, create a logging JS query that POSTs events to your preferred logging service or database.

What you'll learn

  • How to access Retool's built-in audit logs for user actions and query execution
  • How to use console.log in JS queries for development-time debugging
  • How to set up external logging to Datadog or Splunk via Retool's log streaming (Enterprise)
  • How to build a custom event logging workflow using webhooks and a logging database
  • How to log query errors to an external service from a query's On Failure event handler
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read15-20 minRetool Cloud and Self-hostedLast updated March 2026RapidDev Engineering Team
TL;DR

Retool provides audit logs (Business+) showing all user actions and query executions, accessible in Settings → Audit Logs. For development debugging, use console.log() in JS queries (visible in browser DevTools). Enterprise plans can stream audit logs to Datadog, Splunk, or any SIEM. For custom application-level logging on any plan, create a logging JS query that POSTs events to your preferred logging service or database.

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

Logging Strategies in Retool

Logging in Retool happens at three levels: infrastructure logging (what Retool itself captures), application-level logging (what your app intentionally records), and development logging (temporary debug output).

Retool's built-in audit logs (Business+) capture all user actions: who opened which app, which queries ran, what data was viewed or modified, login events, and admin actions. These logs are available in Settings → Audit Logs and can be exported or streamed to external tools.

For application-level logging — tracking specific business events like 'order approved' or 'user invited' — you build logging into your app's event handlers and JS queries. This typically involves posting to a logging API (Datadog, Splunk, Logtail, Papertrail) or inserting into a logs table in your database.

This tutorial covers all three levels with practical implementation patterns.

Prerequisites

  • A Retool app with queries and user interactions you want to log
  • Admin access to Retool (required for audit log access)
  • Optional: a logging service API key (Datadog, Splunk, Logtail, or similar)
  • Optional: a database table for custom application event logs

Step-by-step guide

1

Access built-in Audit Logs (Business+ plans)

Retool Business and Enterprise plans include built-in audit logging. Go to Settings (top-right user menu) → Audit Logs. The Audit Logs page shows a searchable, filterable log of all events: - User login/logout events - App views (who opened which app) - Query executions (which queries ran, on which app) - App edits (who changed what) - Resource access (which resources were queried) - Admin actions (permission changes, user management) Filter logs by date range, user, app, or event type. Export logs as CSV for compliance reporting.

Expected result: You can see a filtered view of audit log events for your Retool organization.

2

Use console.log for development debugging

During development, use console.log() in JS queries to inspect data and debug logic. Logs appear in the browser's DevTools Console (F12 → Console tab). Remove console.log statements before deploying to production — they expose potentially sensitive data to anyone with DevTools open in the browser. console.log is not captured by Retool's audit logs or any server-side logging — it's purely client-side development output.

typescript
1// JS Query: processOrderData (development version with logging)
2console.group('processOrderData Debug');
3console.log('Input - query1.data:', query1.data);
4console.log('Input - selectedUser:', table1.selectedRow);
5console.log('Environment:', retoolContext.environment);
6
7const orders = query1.data || [];
8const filtered = orders.filter(o => o.status === 'pending');
9
10console.log('Filtered orders count:', filtered.length);
11console.log('First filtered order:', filtered[0]);
12console.groupEnd();
13
14// Production version — remove all console.log statements:
15// const orders = query1.data || [];
16// return orders.filter(o => o.status === 'pending');

Expected result: Debug output appears in browser DevTools Console, helping diagnose data flow issues during development.

3

Log query errors via On Failure event handlers

For production error logging, add On Failure event handlers to critical queries. When a query fails, the event handler runs and can send the error to your logging service. In a query's Inspector → Interaction → On Failure → Run Script, access `{{ error.message }}` and `{{ error.statusCode }}` to get error details.

typescript
1// Query Inspector → On Failure → Run Script:
2// This script runs when the query fails
3
4const errorEvent = {
5 timestamp: new Date().toISOString(),
6 appName: 'Orders Dashboard',
7 queryName: 'fetchOrders', // or use dynamic: {{ currentQuery.name }}
8 errorMessage: error.message,
9 errorStatus: error.statusCode,
10 userId: current_user.email,
11 environment: retoolContext.environment,
12 // Include relevant context data
13 queryParameters: {
14 status: statusFilter.value,
15 dateRange: dateRange.value
16 }
17};
18
19// Option 1: Log to console (dev only)
20console.error('Query Error:', JSON.stringify(errorEvent));
21
22// Option 2: Post to logging API (production)
23try {
24 await logErrorToService.trigger({
25 additionalScope: { errorEvent }
26 });
27} catch (loggingError) {
28 // Never let logging failures break the app
29 console.error('Failed to log error:', loggingError);
30}
31
32// Option 3: Show user notification regardless
33await utils.showNotification({
34 title: 'Error loading data',
35 description: 'Please refresh and try again. If the issue persists, contact support.',
36 notificationType: 'error'
37});

Expected result: Query failures are captured and logged to your logging service, with user-friendly notifications shown in the UI.

4

Create a custom event logging query

For business event logging (tracking user actions like approving orders, sending emails, updating records), create a dedicated JS query named logEvent that posts to your logging service. This query accepts an event object and sends it to Datadog, Logtail, Papertrail, or any HTTP-based logging service. Call it from other queries' success/failure handlers or button event handlers.

typescript
1// JS Query: logEvent
2// Call this from any event handler to log business events
3// Parameters passed via additionalScope
4
5const event = {
6 // Core fields
7 timestamp: new Date().toISOString(),
8 level: additionalScope.level || 'info', // 'info', 'warn', 'error'
9 app: 'orders-dashboard',
10 environment: retoolContext.environment,
11
12 // User context
13 userId: current_user.email,
14 userName: current_user.fullName,
15 userGroups: current_user.groups,
16
17 // Event data
18 eventType: additionalScope.eventType || 'unknown',
19 eventData: additionalScope.eventData || {},
20 message: additionalScope.message || ''
21};
22
23// Send to Logtail (Better Stack) — replace with your logging service
24const response = await fetch('https://in.logtail.com', {
25 method: 'POST',
26 headers: {
27 'Content-Type': 'application/json',
28 'Authorization': 'Bearer YOUR_LOGTAIL_SOURCE_TOKEN'
29 },
30 body: JSON.stringify(event)
31});
32
33if (!response.ok) {
34 throw new Error(`Logging failed: ${response.status}`);
35}
36
37return { logged: true, eventType: event.eventType };
38
39// USAGE — call from event handlers:
40// await logEvent.trigger({
41// additionalScope: {
42// eventType: 'order_approved',
43// message: 'Order approved by manager',
44// eventData: { orderId: table1.selectedRow.id }
45// }
46// });

Expected result: Business events are logged to the external logging service with user context, timestamps, and event data.

5

Log events to a database table

If you prefer logging to your own database rather than an external service, create an event_logs table and insert records from Retool queries. This is the simplest approach if you don't have a dedicated logging service. Create the table in your database, then create a Retool query to insert log records:

typescript
1-- Create logging table in your database:
2CREATE TABLE retool_event_logs (
3 id SERIAL PRIMARY KEY,
4 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
5 app_name VARCHAR(255) NOT NULL,
6 event_type VARCHAR(100) NOT NULL,
7 event_data JSONB,
8 user_email VARCHAR(255),
9 user_groups TEXT[],
10 environment VARCHAR(50),
11 message TEXT,
12 level VARCHAR(20) DEFAULT 'info'
13);
14
15CREATE INDEX idx_event_logs_created_at ON retool_event_logs (created_at DESC);
16CREATE INDEX idx_event_logs_user_email ON retool_event_logs (user_email);
17CREATE INDEX idx_event_logs_event_type ON retool_event_logs (event_type);
18
19-- Retool SQL query: insertEventLog
20-- (Create this as a SQL query in the Code panel)
21INSERT INTO retool_event_logs
22 (app_name, event_type, event_data, user_email, user_groups, environment, message, level)
23VALUES
24 (
25 'orders-dashboard',
26 {{ eventType }},
27 {{ JSON.stringify(eventData) }}::jsonb,
28 {{ current_user.email }},
29 {{ JSON.stringify(current_user.groups) }}::text[],
30 {{ retoolContext.environment }},
31 {{ message }},
32 {{ level || 'info' }}
33 )

Expected result: Event logs are inserted into the database table. Query the table to view activity history, filter by user or event type.

6

Stream audit logs to Datadog or Splunk (Enterprise)

Enterprise Retool can stream built-in audit logs to external SIEM systems in real-time. Configure log streaming in Settings → Audit Logs → Configure Log Streaming. Supported destinations: - Datadog (HTTP API) - Splunk HEC (HTTP Event Collector) - Custom webhook URL (for any compatible logging system) Retool sends logs as JSON events in near real-time. Configure the destination URL and API key in the streaming settings.

typescript
1// Retool audit log streaming sends JSON events like this:
2// (Example log entry format for Datadog/Splunk)
3{
4 "timestamp": "2026-03-30T14:22:11.000Z",
5 "eventType": "QUERY_EXECUTION",
6 "appName": "Orders Dashboard",
7 "userId": "user@company.com",
8 "resourceId": "postgres-production",
9 "queryName": "fetchOrders",
10 "status": "SUCCESS",
11 "duration": 142,
12 "environment": "production"
13}
14
15// For Datadog, configure:
16// Destination: https://http-intake.logs.datadoghq.com/api/v2/logs
17// API Key: your Datadog API key
18// Tags: source:retool, env:production, service:internal-tools
19
20// For Splunk HEC:
21// Destination: https://your-splunk.company.com:8088/services/collector
22// Token: your Splunk HEC token

Expected result: Audit logs stream to Datadog/Splunk in real-time, searchable alongside your other infrastructure logs.

Complete working example

JS Query: logEvent (production-ready logger)
1// JS Query: logEvent
2// Universal event logger for Retool apps
3// Usage: await logEvent.trigger({ additionalScope: { eventType, message, eventData, level } })
4
5// Get logging config from configVars
6const loggingEndpoint = retoolContext.configVars.LOGGING_ENDPOINT;
7const loggingEnabled = retoolContext.configVars.LOGGING_ENABLED !== 'false';
8
9if (!loggingEnabled) {
10 return { skipped: true, reason: 'Logging disabled via LOGGING_ENABLED configVar' };
11}
12
13const logEntry = {
14 timestamp: new Date().toISOString(),
15 level: additionalScope.level || 'info',
16
17 // App context
18 app: 'orders-dashboard',
19 environment: retoolContext.environment,
20
21 // User context
22 userId: current_user.email,
23 userName: current_user.fullName,
24 userGroups: current_user.groups,
25
26 // Event
27 eventType: additionalScope.eventType,
28 message: additionalScope.message || '',
29 data: additionalScope.eventData || {}
30};
31
32// Development: always log to console
33console.log('[logEvent]', logEntry.level.toUpperCase(), logEntry.eventType, logEntry);
34
35// Production: send to external service
36if (retoolContext.environment === 'production' && loggingEndpoint) {
37 const response = await fetch(loggingEndpoint, {
38 method: 'POST',
39 headers: {
40 'Content-Type': 'application/json',
41 // Add auth header if needed (store token in configVar, not hardcoded)
42 // 'Authorization': 'Bearer ' + retoolContext.configVars.LOGGING_API_KEY
43 },
44 body: JSON.stringify(logEntry)
45 });
46
47 if (!response.ok) {
48 // Don't throw — logging failures should never break app functionality
49 console.error('[logEvent] Failed to send log:', response.status);
50 return { success: false, status: response.status };
51 }
52}
53
54return { success: true, eventType: logEntry.eventType, timestamp: logEntry.timestamp };

Common mistakes

Why it's a problem: Leaving console.log() statements in production queries that log query data to the browser DevTools console

How to avoid: Before publishing a release, search for console.log in all JS queries (use the Code panel search) and remove them. Alternatively, use a conditional: if (retoolContext.environment !== 'production') { console.log(...); }

Why it's a problem: Letting logging errors propagate and break the app's main functionality

How to avoid: Wrap all logging calls in try/catch. Logging is secondary functionality — a failed log POST to Datadog should never prevent an order from being saved. Always catch logging errors and fail silently (or show a non-blocking warning).

Why it's a problem: Hardcoding logging API keys in JS queries where they're visible in the Retool editor to all editors

How to avoid: Store logging API keys in secret configuration variables (Settings → Configuration Variables → check Secret). Reference them in resource configurations. For HTTP logging endpoints called from JS queries, the key is exposed client-side anyway — use a backend proxy or Retool workflow to keep it server-side.

Best practices

  • Never let logging failures break your app — wrap all log sending code in try/catch and fail silently if the logging service is unavailable
  • Remove console.log() statements before publishing to production — they expose data to anyone with DevTools open
  • Store logging API keys in secret configVars, never hardcoded in JS queries or visible in the Retool editor
  • Create a logEvent JS query as the single logging entry point — all other queries call it rather than implementing logging inline
  • Log business events (order approved, user invited) separately from technical events (query error, API timeout) — they have different retention and alerting needs
  • For compliance-sensitive apps (finance, healthcare), use Retool's built-in audit logs (Business+) — they're tamper-proof unlike custom database logging
  • Add the environment to every log event — logs from staging and production mixed together are difficult to analyze

Still stuck?

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

ChatGPT Prompt

I'm building a Retool internal tool for my operations team and need to set up logging. I want: (1) to log when users approve orders (including which order and which user), (2) to capture query errors and send them to Datadog, (3) to use console.log for development but remove it in production. Write a Retool JS query called logEvent that I can call from event handlers with an eventType and eventData, sends to a logging API endpoint from a configVar, and handles errors without breaking the app.

Retool Prompt

Add logging to this Retool app: (1) create a JS query named logEvent that accepts eventType, message, and eventData via additionalScope, logs to console in non-production, and POSTs to retoolContext.configVars.LOGGING_ENDPOINT in production, (2) add On Failure handlers to the top 3 queries that call logEvent with level:'error', (3) add an On Click handler to the 'Approve Order' button that calls logEvent with eventType:'order_approved' and eventData: { orderId: table1.selectedRow.id }.

Frequently asked questions

Does Retool's free plan include audit logs?

No. Audit logs are available on Business and Enterprise plans. On the free and Team plans, you don't have access to Settings → Audit Logs. To get logging on lower-tier plans, build custom application-level logging using JS queries that post to an external service (Logtail, Papertrail, or your own database) from event handlers.

How long are Retool audit logs retained?

Retool retains audit logs for 90 days on Business plans and longer on Enterprise plans (configurable). For compliance requirements needing longer retention, use Retool's audit log streaming (Enterprise) to export logs to a SIEM like Splunk or Datadog where you control retention policies.

Can I see which user deleted or modified specific records using Retool audit logs?

Retool's audit logs capture which queries ran, who ran them, and when — but they don't capture the actual data values (what was inserted/updated/deleted). To log the actual data changes, add INSERT statements to your event_logs table from the query's On Success handler, capturing the specific record IDs and new values at the application level.

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.