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.
| Fact | Value |
|---|---|
| Tool | Retool |
| Difficulty | Beginner |
| Time required | 15-20 min |
| Compatibility | Retool Cloud and Self-hosted |
| Last updated | March 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
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.
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.
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);67const orders = query1.data || [];8const filtered = orders.filter(o => o.status === 'pending');910console.log('Filtered orders count:', filtered.length);11console.log('First filtered order:', filtered[0]);12console.groupEnd();1314// 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.
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.
1// Query Inspector → On Failure → Run Script:2// This script runs when the query fails34const 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 data13 queryParameters: {14 status: statusFilter.value,15 dateRange: dateRange.value16 }17};1819// Option 1: Log to console (dev only)20console.error('Query Error:', JSON.stringify(errorEvent));2122// 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 app29 console.error('Failed to log error:', loggingError);30}3132// Option 3: Show user notification regardless33await 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.
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.
1// JS Query: logEvent2// Call this from any event handler to log business events3// Parameters passed via additionalScope45const event = {6 // Core fields7 timestamp: new Date().toISOString(),8 level: additionalScope.level || 'info', // 'info', 'warn', 'error'9 app: 'orders-dashboard',10 environment: retoolContext.environment,11 12 // User context13 userId: current_user.email,14 userName: current_user.fullName,15 userGroups: current_user.groups,16 17 // Event data18 eventType: additionalScope.eventType || 'unknown',19 eventData: additionalScope.eventData || {},20 message: additionalScope.message || ''21};2223// Send to Logtail (Better Stack) — replace with your logging service24const 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});3233if (!response.ok) {34 throw new Error(`Logging failed: ${response.status}`);35}3637return { logged: true, eventType: event.eventType };3839// 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.
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:
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);1415CREATE 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);1819-- Retool SQL query: insertEventLog20-- (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)23VALUES24 (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.
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.
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}1415// For Datadog, configure:16// Destination: https://http-intake.logs.datadoghq.com/api/v2/logs17// API Key: your Datadog API key18// Tags: source:retool, env:production, service:internal-tools1920// For Splunk HEC:21// Destination: https://your-splunk.company.com:8088/services/collector22// Token: your Splunk HEC tokenExpected result: Audit logs stream to Datadog/Splunk in real-time, searchable alongside your other infrastructure logs.
Complete working example
1// JS Query: logEvent2// Universal event logger for Retool apps3// Usage: await logEvent.trigger({ additionalScope: { eventType, message, eventData, level } })45// Get logging config from configVars6const loggingEndpoint = retoolContext.configVars.LOGGING_ENDPOINT;7const loggingEnabled = retoolContext.configVars.LOGGING_ENABLED !== 'false';89if (!loggingEnabled) {10 return { skipped: true, reason: 'Logging disabled via LOGGING_ENABLED configVar' };11}1213const logEntry = {14 timestamp: new Date().toISOString(),15 level: additionalScope.level || 'info',16 17 // App context18 app: 'orders-dashboard',19 environment: retoolContext.environment,20 21 // User context22 userId: current_user.email,23 userName: current_user.fullName,24 userGroups: current_user.groups,25 26 // Event27 eventType: additionalScope.eventType,28 message: additionalScope.message || '',29 data: additionalScope.eventData || {}30};3132// Development: always log to console33console.log('[logEvent]', logEntry.level.toUpperCase(), logEntry.eventType, logEntry);3435// Production: send to external service36if (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_KEY43 },44 body: JSON.stringify(logEntry)45 });46 47 if (!response.ok) {48 // Don't throw — logging failures should never break app functionality49 console.error('[logEvent] Failed to send log:', response.status);50 return { success: false, status: response.status };51 }52}5354return { 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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation