# How to Create Reusable Components in Retool with Modules

- Tool: Retool
- Difficulty: Beginner
- Time required: 20-30 min
- Compatibility: Retool Cloud and Self-hosted
- Last updated: March 2026

## TL;DR

Retool Modules are reusable app fragments — they contain components, queries, and logic packaged with typed inputs and outputs. Build a module once, embed it across dozens of apps, and update all instances instantly by editing the module. Modules are the no-code alternative to Custom Component Libraries: no React or CLI required.

## What Are Retool Modules and When to Use Them

A Retool Module is a special app type that acts as a reusable building block. You build a Module just like a regular app — adding components, writing queries, setting up event handlers — but instead of being used directly, it gets embedded inside other apps via the Module component.

Modules are ideal for UI patterns that repeat across your org: a standardized user lookup widget, a consistent status badge bar, a shared navigation header, or a reusable form section. When you update the module, every app embedding it instantly reflects the change — no copy-pasting required.

Modules support typed inputs (String, Number, Boolean, Enum, Array, Object) that host apps pass in, and typed outputs that host apps can read back. This makes modules behave like true components with a defined API.

## Before you start

- Access to a Retool Cloud or self-hosted instance
- Editor or Admin permissions in Retool
- Basic familiarity with the Retool canvas and Inspector panel
- An understanding of what queries and temporary state variables are

## Step-by-step guide

### 1. Create a new Module in Retool

From the Retool home page, click '+ New' and select 'Module' (not 'App'). Give your module a descriptive name like 'UserLookupWidget'. You'll land in the standard Retool editor, but you'll notice a new 'Module Inputs' and 'Module Outputs' section in the left sidebar under the canvas. This is where you define the typed interface your module exposes to embedding apps. Start by building the UI you want to make reusable — for example, a text input, a button, and a table to display search results.

**Expected result:** A new Module opens in the Retool editor with an empty canvas and visible Module Inputs/Outputs panels.

### 2. Define typed Module Inputs

Click '+ Add Input' in the Module Inputs panel. For each input, set a name and type. Common types: String (for text like a user ID or search term), Number (for numeric filters), Boolean (for toggle flags), Enum (for dropdown-constrained values), Array (for data lists), Object (for structured data). For the UserLookupWidget, add a String input named 'defaultEmail' with a default value of '' (empty string). You can reference this input anywhere inside the module using {{ moduleInputs.defaultEmail }}.

```
// Inside the module, reference inputs like this:
{{ moduleInputs.defaultEmail }}
{{ moduleInputs.filterRole }}
{{ moduleInputs.showInactiveUsers }}
```

**Expected result:** Module inputs appear in the left panel with their types. Inside the module, {{ moduleInputs.yourInput }} resolves to the value passed by the host app.

### 3. Build the module UI and internal queries

Build your UI inside the module as you would in any Retool app. Add a Text Input component named 'emailInput' with its Default value set to {{ moduleInputs.defaultEmail }}. Add a query named 'searchUsers' that uses {{ emailInput.value }} as a parameter. Add a Table component with Data source set to {{ searchUsers.data }}. The module's internal components work exactly like a regular app — the only difference is that some values come from moduleInputs instead of hard-coded defaults.

```
-- searchUsers SQL query inside the module
SELECT id, name, email, role, created_at
FROM users
WHERE email ILIKE {{ '%' + emailInput.value + '%' }}
ORDER BY created_at DESC
LIMIT 50;
```

**Expected result:** The module displays a working user search UI with a text input, search button, and results table.

### 4. Define Module Outputs

Click '+ Add Output' in the Module Outputs panel. Outputs expose internal state to the embedding app. Add an output named 'selectedUser' of type Object, and set its Value to {{ table1.selectedRow.data }}. Add a Number output named 'resultCount' with Value set to {{ searchUsers.data.length }}. Now when the module is embedded in a host app, those values are accessible as {{ module1.selectedUser }} and {{ module1.resultCount }} in the host app's expressions.

```
// In the host app, after embedding the module:
{{ module1.selectedUser.email }}
{{ module1.resultCount }}
```

**Expected result:** Module Outputs panel shows selectedUser and resultCount entries. The host app can reference them after embedding.

### 5. Embed the Module in a host app

Open an existing Retool app (or create a new one). In the component panel on the left, search for 'Module' and drag it onto your canvas. In the Inspector, you'll see a 'Module' dropdown — search for and select your 'UserLookupWidget' module. The module renders inline in your host app. In the Inspector's Inputs section, set 'defaultEmail' to a value from the host app, for example {{ textInput1.value }} or a hard-coded string like 'admin@company.com'.

**Expected result:** The UserLookupWidget module renders inside the host app with the configured default email pre-filled.

### 6. Read Module Outputs from the host app

In your host app, you can now use {{ module1.selectedUser }} to reference the user selected inside the embedded module. For example, bind a Text component's value to 'Selected: {{ module1.selectedUser.name }}' to show who was selected. You can also trigger queries in the host app based on module output changes by adding an event handler on a component that listens for a module event. Use this pattern to build complex UI flows that span module boundaries.

**Expected result:** Host app components correctly display values from the embedded module's outputs.

### 7. Update the module and verify propagation

Make a change to your UserLookupWidget module — for example, add a new column to the table or change the search placeholder text. Save the module. All apps that embed this module immediately reflect the change the next time they load — you do not need to update each host app individually. This instant propagation is the core value of modules: maintain UI consistency across your entire Retool org from one place.

**Expected result:** Changes to the module are immediately visible in all host apps without any manual updates.

## Complete code example

File: `Module: searchUsers query (inside UserLookupWidget)`

```sql
-- searchUsers query inside the UserLookupWidget module
-- References moduleInputs for external configuration
-- References internal components for runtime filter values

SELECT
  u.id,
  u.name,
  u.email,
  u.role,
  u.status,
  u.created_at
FROM users u
WHERE
  (
    {{ emailInput.value === '' }}
    OR u.email ILIKE {{ '%' + emailInput.value + '%' }}
  )
  AND (
    {{ moduleInputs.filterRole === '' || moduleInputs.filterRole === undefined }}
    OR u.role = {{ moduleInputs.filterRole }}
  )
  AND (
    {{ !moduleInputs.showInactiveUsers }}
    OR u.status IN ('active', 'inactive')
  )
ORDER BY u.created_at DESC
LIMIT {{ moduleInputs.pageSize || 50 }};
```

## Common mistakes

- **Trying to write to {{ moduleInputs.xxx }} from inside the module** — undefined Fix: moduleInputs are read-only inside the module. They flow from the host app into the module. Use a Temporary State variable inside the module to store mutable state, and expose it via Module Outputs if the host app needs to read it.
- **Referencing {{ module1.outputName }} in the host app before the module's internal query has run** — undefined Fix: Module outputs reflect the current state of internal components and queries. If the query hasn't run yet, the output may be empty or undefined. Add a loading check: {{ module1.selectedUser?.email || 'None selected' }}.
- **Building a module so large it contains 10+ queries and covers multiple use cases** — undefined Fix: Split large modules into smaller, focused modules. A module with 10+ queries is difficult to maintain and debug. One module per UI concern is the recommended pattern.
- **Forgetting that event handlers in Retool run in parallel — expecting sequential execution** — undefined Fix: If you need sequential actions after a module event fires, use a JS Query with await query.trigger() chains instead of stacking multiple event handlers on the same event.

## Best practices

- Define all module inputs with explicit types and meaningful default values so embedding apps work without configuration
- Keep modules focused on one UI concern — a user picker, a date filter, a KPI card — not a full page
- Use Enum inputs for values that must come from a known set (e.g., role: 'admin' | 'editor' | 'viewer')
- Document your module inputs and outputs in the module's Description field — this helps teammates understand the API
- Test your module standalone before embedding to ensure internal queries and state work correctly
- Avoid deep nesting of modules inside modules — one level deep is the recommended maximum

## Frequently asked questions

### Can a Retool Module contain its own database queries?

Yes — modules support the full range of Retool queries including SQL, REST API, GraphQL, and JavaScript queries. The module's internal queries use resources configured in your Retool org. Host apps do not need to know about or configure the module's data sources.

### What is the difference between a Retool Module and a Retool App?

A Module is an app fragment designed to be embedded inside other apps. It cannot be opened directly as a standalone URL. An App is a complete, user-facing application with its own URL. Modules have typed inputs/outputs; apps do not have this concept.

### Can I use the same module in both a web app and Retool Mobile?

Modules built in the Retool web editor are for web apps only. Retool Mobile has a separate builder and does not support embedding web-based modules. Build separate mobile components or reuse the underlying data queries.

---

Source: https://www.rapidevelopers.com/retool-tutorial/how-to-create-reusable-components-in-retool
© RapidDev — https://www.rapidevelopers.com/retool-tutorial/how-to-create-reusable-components-in-retool
