Saltar a contenido

Retool Cheat Sheet

"Clase de la hoja" id="copy-btn" class="copy-btn" onclick="copyAllCommands()" Copiar todos los comandos id="pdf-btn" class="pdf-btn" onclick="generatePDF()" Generar PDF seleccionado/button ■/div titulada

Sinopsis

Retool es una plataforma de código bajo para construir herramientas internas rápidamente. Permite a los desarrolladores crear aplicaciones personalizadas conectando a bases de datos, API y servicios a través de una interfaz de arrastrar y soltar combinada con código JavaScript, permitiendo el rápido desarrollo de paneles de administración, paneles y aplicaciones empresariales.

NOVEDAD Nota: Tier gratis disponible para hasta 5 usuarios. Los planes pagados comienzan en $10/usuario/mes para los equipos.

Comienzo

Configuración de la cuenta

# Sign up process:
# 1. Visit retool.com
# 2. Create account with email or Google/GitHub
# 3. Choose organization name
# 4. Set up first application
# 5. Connect data sources

# Organization setup:
# - Configure SSO (optional)
# - Set up user groups and permissions
# - Configure environment variables
# - Set up version control
# - Configure deployment settings

Primera aplicación

# Create new app:
# 1. Click "Create new" → "App"
# 2. Choose template or start blank
# 3. Name your application
# 4. Select starting components
# 5. Configure data connections

# Basic app structure:
# - Components: UI elements (tables, forms, buttons)
# - Queries: Data operations (API calls, database queries)
# - JavaScript: Custom logic and transformations
# - State: Application data and variables

Interface Overview

# Main interface areas:
# - Component Library: Drag-and-drop UI elements
# - Canvas: Visual app builder
# - Query Editor: Data operations panel
# - State Inspector: Variable and data viewer
# - Code Editor: JavaScript and SQL editor
# - Resource Manager: Data source connections

Componentes

Componentes de visualización de datos

# Table component:
# - Display tabular data
# - Sorting and filtering
# - Pagination
# - Row selection
# - Inline editing
# - Custom columns

# Table configuration:
table1.data = {{ query1.data }}
table1.columns = [
  {
    "id": "name",
    "label": "Name",
    "type": "string"
  },
  {
    "id": "email",
    "label": "Email",
    "type": "email"
  }
]

Componentes de entrada

# Text Input:
textInput1.value          # Get current value
textInput1.setValue("text") # Set value programmatically

# Select component:
select1.value             # Selected value
select1.selectedItem      # Selected item object
select1.data = {{ query1.data }}
select1.valueKey = "id"
select1.labelKey = "name"

# Date Picker:
datePicker1.value         # Selected date
datePicker1.formattedValue # Formatted date string

Componentes de acción

# Button component:
button1.click()           # Trigger click programmatically
button1.loading = {{ query1.isFetching }}
button1.disabled = {{ !form1.isValid }}

# Button event handlers:
// onClick event
if (textInput1.value) {
  query1.trigger();
} else {
  utils.showNotification({
    title: "Error",
    description: "Please enter a value",
    notificationType: "error"
  });
}

Componentes de diseño

# Container:
# - Group related components
# - Apply consistent styling
# - Control visibility and layout

# Tabs:
# - Organize content into sections
# - Dynamic tab creation
# - Conditional tab visibility

# Modal:
modal1.open()             # Open modal
modal1.close()            # Close modal
modal1.opened             # Check if open

Fuentes de datos

Conexiones de bases de datos

# Supported databases:
# - PostgreSQL
# - MySQL
# - MongoDB
# - Redis
# - Elasticsearch
# - BigQuery
# - Snowflake
# - Microsoft SQL Server

# Connection setup:
# 1. Go to Resources tab
# 2. Click "Create new resource"
# 3. Select database type
# 4. Enter connection details
# 5. Test connection
# 6. Save resource

REST API Integration

# REST API resource:
# 1. Create new REST API resource
# 2. Set base URL
# 3. Configure authentication
# 4. Add headers if needed
# 5. Test connection

# API query example:
// GET request
return await api1.run({
  url: "/users",
  method: "GET",
  params: {
    page: table1.pageIndex + 1,
    limit: table1.pageSize
  }
});

// POST request
return await api1.run({
  url: "/users",
  method: "POST",
  body: {
    name: textInput1.value,
    email: textInput2.value
  }
});

GraphQL Integration

# GraphQL setup:
# 1. Create GraphQL resource
# 2. Set endpoint URL
# 3. Configure authentication
# 4. Add headers if needed

# GraphQL query:
query GetUsers($limit: Int!) {
  users(limit: $limit) {
    id
    name
    email
    createdAt
  }
}

# Variables:
{
  "limit": {{ table1.pageSize }}
}

Consultas y Transformadores

SQL Queries

-- Basic SELECT query
SELECT * FROM users 
WHERE created_at >= {{ datePicker1.value }}
ORDER BY created_at DESC
LIMIT {{ table1.pageSize }}
OFFSET {{ table1.pageIndex * table1.pageSize }};

-- Parameterized query
SELECT * FROM orders 
WHERE user_id = {{ select1.value }}
| AND status = {{ textInput1.value |  | 'active' }} |
  AND created_at BETWEEN {{ startDate.value }} AND {{ endDate.value }};

-- Insert query
INSERT INTO users (name, email, department)
VALUES (
  {{ textInput1.value }},
  {{ textInput2.value }},
  {{ select1.value }}
);

Transformadores de JavaScript

// Transform API response
const transformedData = query1.data.map(item => ({
  id: item.id,
  fullName: `${item.firstName} ${item.lastName}`,
  email: item.email,
  status: item.isActive ? 'Active' : 'Inactive',
  joinDate: new Date(item.createdAt).toLocaleDateString()
}));

return transformedData;

// Aggregate data
const summary = query1.data.reduce((acc, item) => {
  acc.totalRevenue += item.revenue;
  acc.totalOrders += 1;
  acc.averageOrderValue = acc.totalRevenue / acc.totalOrders;
  return acc;
}, { totalRevenue: 0, totalOrders: 0, averageOrderValue: 0 });

return summary;

Query Chaining

// Sequential queries
const userData = await query1.trigger();
const userOrders = await query2.trigger({
  additionalScope: {
    userId: userData.id
  }
});

return {
  user: userData,
  orders: userOrders
};

// Parallel queries
const [users, orders, products] = await Promise.all([
  query1.trigger(),
  query2.trigger(),
  query3.trigger()
]);

return { users, orders, products };

JavaScript en Retool

Funciones mundiales

// Utility functions
utils.showNotification({
  title: "Success",
  description: "Record saved successfully",
  notificationType: "success",
  duration: 3000
});

utils.openUrl("https://example.com", { newTab: true });

utils.copyToClipboard(textInput1.value);

utils.downloadPage("report.pdf");

utils.goToApp("app-uuid", { 
  userId: select1.value 
});

State Management

// Set temporary state
tempState.setValue({
  selectedUser: table1.selectedRow.data,
  editMode: true,
  originalData: { ...table1.selectedRow.data }
});

// Get state values
const currentUser = tempState.value.selectedUser;
const isEditing = tempState.value.editMode;

// Clear state
tempState.setValue({});

// Local storage
localStorage.setValue("userPreferences", {
  theme: "dark",
  language: "en"
});

const preferences = localStorage.getValue("userPreferences");

Manejo de eventos

// Component event handlers
// Button onClick
async function handleSubmit() {
  try {
    button1.setLoading(true);

    const result = await query1.trigger();

    utils.showNotification({
      title: "Success",
      description: "Data saved successfully"
    });

    modal1.close();
    table1.refresh();

  } catch (error) {
    utils.showNotification({
      title: "Error",
      description: error.message,
      notificationType: "error"
    });
  } finally {
    button1.setLoading(false);
  }
}

// Table row selection
function handleRowSelect() {
  const selectedData = table1.selectedRow.data;

  textInput1.setValue(selectedData.name);
  textInput2.setValue(selectedData.email);
  select1.setValue(selectedData.department);
}

Características avanzadas

Componentes personalizados

// Custom React component
const CustomChart = ({ data, title }) => {
  const chartData = data.map(item => ({
    name: item.month,
    value: item.revenue
  }));

  return (
    <div>
      <h3>{title}</h3>
      <ResponsiveContainer width="100%" height={300}>
        <LineChart data={chartData}>
          <XAxis dataKey="name" />
          <YAxis />
          <CartesianGrid strokeDasharray="3 3" />
          <Line type="monotone" dataKey="value" stroke="#8884d8" />
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
};

// Use custom component
<CustomChart 
  data={query1.data} 
  title="Monthly Revenue" 
/>

Flujos de trabajo y automatización

// Workflow example: User onboarding
async function onboardNewUser() {
  try {
    // Step 1: Create user account
    const user = await createUserQuery.trigger({
      additionalScope: {
        name: nameInput.value,
        email: emailInput.value,
        department: departmentSelect.value
      }
    });

    // Step 2: Send welcome email
    await sendEmailQuery.trigger({
      additionalScope: {
        to: user.email,
        template: "welcome",
        variables: { name: user.name }
      }
    });

    // Step 3: Create default permissions
    await createPermissionsQuery.trigger({
      additionalScope: {
        userId: user.id,
        role: "employee"
      }
    });

    // Step 4: Add to team
    await addToTeamQuery.trigger({
      additionalScope: {
        userId: user.id,
        teamId: teamSelect.value
      }
    });

    utils.showNotification({
      title: "Success",
      description: "User onboarded successfully"
    });

  } catch (error) {
    console.error("Onboarding failed:", error);
    utils.showNotification({
      title: "Error",
      description: "Failed to onboard user",
      notificationType: "error"
    });
  }
}

Permisos y seguridad

// Role-based access control
const userRole = current_user.groups[0];

// Hide components based on role
if (userRole !== 'admin') {
  deleteButton.hidden = true;
  editButton.hidden = true;
}

// Conditional queries
if (userRole === 'admin') {
  return await adminQuery.trigger();
} else {
  return await userQuery.trigger({
    additionalScope: {
      userId: current_user.id
    }
  });
}

// Data filtering by user
SELECT * FROM orders 
WHERE user_id = {{ current_user.id }}
  OR {{ current_user.groups.includes('admin') }}

Responsabilidades móviles

Diseño responsable

// Screen size detection
const isMobile = window.innerWidth < 768;
const isTablet = window.innerWidth >= 768 && window.innerWidth < 1024;
const isDesktop = window.innerWidth >= 1024;

// Conditional styling
if (isMobile) {
  container1.style = {
    padding: "10px",
    flexDirection: "column"
  };
} else {
  container1.style = {
    padding: "20px",
    flexDirection: "row"
  };
}

Componentes Móviles Específicos

// Mobile navigation
const MobileNav = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="mobile-nav">
      <button onClick={() => setIsOpen(!isOpen)}>
        Menu
      </button>
      {isOpen && (
        <div className="nav-menu">
          <a href="#dashboard">Dashboard</a>
          <a href="#users">Users</a>
          <a href="#settings">Settings</a>
        </div>
      )}
    </div>
  );
};

Despliegue y Medio Ambiente

Environment Management

# Environment types:
# - Development: Testing and development
# - Staging: Pre-production testing
# - Production: Live application

# Environment variables:
# {{ retool.environment }} - Current environment
# {{ retool.configVars.API_URL }} - Environment-specific config

Control de versiones

# Git integration:
# 1. Connect to GitHub/GitLab repository
# 2. Configure branch protection
# 3. Set up pull request workflow
# 4. Enable automatic deployments

# Branching strategy:
# - main: Production code
# - staging: Pre-production testing
# - feature/*: Feature development
# - hotfix/*: Critical bug fixes

Administración de módulos

// Release checklist automation
const releaseChecklist = [
  { task: "Run tests", completed: false },
  { task: "Update documentation", completed: false },
  { task: "Review security", completed: false },
  { task: "Performance testing", completed: false },
  { task: "Stakeholder approval", completed: false }
];

// Deployment script
async function deployToProduction() {
  const incompleteItems = releaseChecklist.filter(item => !item.completed);

  if (incompleteItems.length > 0) {
    utils.showNotification({
      title: "Deployment Blocked",
      description: `Complete: ${incompleteItems.map(i => i.task).join(', ')}`,
      notificationType: "warning"
    });
    return;
  }

  // Proceed with deployment
  await deployQuery.trigger();
}

Optimización del rendimiento

Optimización de consultas

// Debounced search
let searchTimeout;

function handleSearchInput() {
  clearTimeout(searchTimeout);
  searchTimeout = setTimeout(() => {
    searchQuery.trigger({
      additionalScope: {
        searchTerm: searchInput.value
      }
    });
  }, 500);
}

// Cached queries
const cachedData = localStorage.getValue("userList");
const cacheExpiry = localStorage.getValue("userListExpiry");

if (cachedData && cacheExpiry > Date.now()) {
  return cachedData;
} else {
  const freshData = await userQuery.trigger();
  localStorage.setValue("userList", freshData);
  localStorage.setValue("userListExpiry", Date.now() + 300000); // 5 minutes
  return freshData;
}

Optimización de componentes

// Lazy loading
const LazyTable = React.lazy(() => import('./HeavyTable'));

function DataView() {
  const [showTable, setShowTable] = useState(false);

  return (
    <div>
      <button onClick={() => setShowTable(true)}>
        Load Data Table
      </button>
      {showTable && (
        <React.Suspense fallback={<div>Loading...</div>}>
          <LazyTable data={query1.data} />
        </React.Suspense>
      )}
    </div>
  );
}

// Memoization
const expensiveCalculation = useMemo(() => {
  return query1.data.reduce((sum, item) => sum + item.value, 0);
}, [query1.data]);

Pruebas y depuración

Modo de depuración

// Console logging
console.log("Query result:", query1.data);
console.log("Component state:", table1.selectedRow);
console.log("User info:", current_user);

// Error handling
try {
  const result = await riskyQuery.trigger();
  console.log("Success:", result);
} catch (error) {
  console.error("Query failed:", error);
  utils.showNotification({
    title: "Error",
    description: error.message,
    notificationType: "error"
  });
}

Estrategias de ensayo

// Mock data for testing
const mockUsers = [
  { id: 1, name: "John Doe", email: "john@example.com" },
  { id: 2, name: "Jane Smith", email: "jane@example.com" }
];

// Use mock data in development
const userData = retool.environment === 'development' 
  ? mockUsers 
  : query1.data;

// Test utilities
function runTests() {
  const tests = [
    {
      name: "User creation",
      test: () => createUserQuery.data.id > 0
    },
    {
      name: "Email validation",
      test: () => /\S+@\S+\.\S+/.test(emailInput.value)
    }
  ];

  tests.forEach(({ name, test }) => {
    try {
      const result = test();
      console.log(`✅ ${name}: ${result ? 'PASS' : 'FAIL'}`);
    } catch (error) {
      console.log(`❌ ${name}: ERROR - ${error.message}`);
    }
  });
}

Patrones de integración

Webhook Handling

// Webhook receiver
app.post('/webhook', (req, res) => {
  const { event, data } = req.body;

  switch (event) {
    case 'user.created':
      // Trigger user sync
      syncUserQuery.trigger({
        additionalScope: { userData: data }
      });
      break;

    case 'order.completed':
      // Update dashboard
      refreshDashboardQuery.trigger();
      break;

    default:
      console.log('Unknown webhook event:', event);
  }

  res.status(200).json({ received: true });
});

Integración de terceros

// Slack integration
async function sendSlackNotification(message) {
  return await slackAPI.run({
    url: "/chat.postMessage",
    method: "POST",
    body: {
      channel: "#notifications",
      text: message,
      username: "Retool Bot"
    }
  });
}

// Google Sheets integration
async function updateSpreadsheet(data) {
  return await googleSheetsAPI.run({
    url: `/spreadsheets/${sheetId}/values/A1:append`,
    method: "POST",
    body: {
      values: [data],
      valueInputOption: "RAW"
    }
  });
}

Buenas prácticas

Code Organization

// Utility functions module
const utils = {
  formatCurrency: (amount) => {
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD'
    }).format(amount);
  },

  formatDate: (date) => {
    return new Date(date).toLocaleDateString();
  },

  validateEmail: (email) => {
    return /\S+@\S+\.\S+/.test(email);
  }
};

// Constants
const CONSTANTS = {
  API_ENDPOINTS: {
    USERS: '/api/users',
    ORDERS: '/api/orders',
    PRODUCTS: '/api/products'
  },
  STATUS_COLORS: {
    active: '#10B981',
    inactive: '#EF4444',
    pending: '#F59E0B'
  }
};

Manejo de errores

// Global error handler
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
  utils.showNotification({
    title: "Unexpected Error",
    description: "An error occurred. Please try again.",
    notificationType: "error"
  });
});

// Query error handling
async function safeQueryExecution(query, fallbackData = []) {
  try {
    const result = await query.trigger();
| return result |  | fallbackData; |
  } catch (error) {
    console.error(`Query ${query.name} failed:`, error);
    return fallbackData;
  }
}

Prácticas óptimas de seguridad

// Input sanitization
function sanitizeInput(input) {
  return input
    .replace(/[<>]/g, '') // Remove HTML tags
    .trim()
    .substring(0, 1000); // Limit length
}

// SQL injection prevention
// Use parameterized queries instead of string concatenation
// ❌ Bad:
// SELECT * FROM users WHERE id = ${userInput.value}

// ✅ Good:
// SELECT * FROM users WHERE id = {{ userInput.value }}

// XSS prevention
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

Solución de problemas

Cuestiones comunes

// Component not updating
// Solution: Check data binding and refresh triggers
table1.setData(newData);
table1.refresh();

// Query not triggering
// Solution: Check query dependencies and error logs
console.log("Query state:", query1.isFetching);
console.log("Query error:", query1.error);

// Performance issues
// Solution: Optimize queries and use pagination
SELECT * FROM large_table 
LIMIT {{ table1.pageSize }}
OFFSET {{ table1.pageIndex * table1.pageSize }};

Herramientas de depuración

// State inspector
function debugAppState() {
  console.log("App State:", {
    queries: Object.keys(queries).map(key => ({
      name: key,
      data: queries[key].data,
      isFetching: queries[key].isFetching,
      error: queries[key].error
    })),
    components: Object.keys(components).map(key => ({
      name: key,
      value: components[key].value,
      hidden: components[key].hidden
    }))
  });
}

// Performance monitoring
const performanceTimer = {
  start: (label) => console.time(label),
  end: (label) => console.timeEnd(label)
};

performanceTimer.start('query-execution');
await heavyQuery.trigger();
performanceTimer.end('query-execution');

Recursos

Documentación

Comunidad

Capacitación