Triggering the refresh of a local table from a global modal

I have a couple of global modals that are reused across pages, for example an edit contact modal that can be used in various pages that have contacts. After editing the contact I would like the local data to refresh. Both the modal and the query are in global scope so post Contact and modal contact are both global. I have a local table I need to refresh and I’m wondering what the best pattern is to refresh a local table from a global modal / query.

Claude came up with the below, but I thought there might be a more elegant solution that doesn’t involve polling? Is there a way to monitor a variable to that when it changes it triggers an event?
The Automatic watch is greyed out:

**Local Global trigger Pattern

Global setup:**

  • A global variable called varRefreshTrigger with a default value of null

  • A global variable called varPageName that is set to the current page name before the modal is opened

  • In the global query's onSuccess handler, set the trigger like this:

javascript

varRefreshTrigger.setValue({
  page: varPageName.value,
  timestamp: Date.now()
});

Local page setup:

  • Each page has a local JS query named [pageName]Watcher (e.g. tasksWatcher, sequencesWatcher) that acts as a refresh watcher

  • The query runs on a 1000ms polling interval (set via "Run this query periodically" in the Advanced tab) since Retool does not reliably detect global variable dependencies in JS queries automatically

  • The query checks the trigger and fires the local refresh query if the page name matches:

javascript

const trigger = varRefreshTrigger.value;

if (trigger?.page === "[PageName]") {
  varRefreshTrigger.setValue(null);
  localQuery.trigger();
}

When I describe a new page that needs this behavior, generate the watcher query code and remind me to enable periodic polling at 1000ms in the Advanced tab.

Hey there @benjaminfortunato,

The cleanest pattern here is Watched inputs. Add your global variable as a watched input on the local query (Advanced tab), and Retool will automatically re-run it whenever the variable changes. You just need to make sure the variable is updated in your global query's onSuccess handler to trigger it. Let me know if this works!

You can watch this video example
How to use 'Watched Inputs' in Retool

1 Like

I came up with a hack using a Query JSON with SQL watcher. See below. (Its in markdown so if you copy and paste this into Coda Notion etc this should be more legible) I guess this would save me from creating those watchers on each page. I did create a general object that had a page prop and a time stamp. Can I use the pattern outlined in the video with an expression or will I have to create a new variable for every page I want to update?

16. Global-to-Local Refresh Pattern

Use this whenever a **global** modal, drawer, or query must cause a **page-scoped** list/detail query to refresh after success (e.g. shared "Create email template" modal used from multiple pages).

### The problem

1. **Scope:** Global components cannot call local queries — Retool will not resolve page-scoped query names from global `onSuccess` handlers.

2. **JS query limitation:** A local **JS** query set to watch `varRefreshTrigger` often cannot use **Automatic** / "Run when inputs change" — Retool dependency detection for JS + globals is unreliable (option stays greyed out). Do not rely on a JS-only watcher.

### The solution (canonical)

Use a global temp variable as an **intent bus**, and on each host page add a **Query JSON with SQL** dummy query that Retool *does* re-run automatically when the variable changes.

### Global setup

| Piece | Name | Default | Role |

|-------|------|---------|------|

| Caller identity | `varPageName` | `""` | Set **before** opening the global UI so saves know which logical page initiated the flow |

| Refresh signal | `varRefreshTrigger` | `null` | Global query / `jsSave` writes `{ page, timestamp }` after success |

**After any global mutation succeeds** (REST query `onSuccess`, or inside `jsSave{PageName}` after POST/PATCH):

```javascript

await varRefreshTrigger.setValue({

page: varPageName.value,

timestamp: Date.now(), // required — same page twice in a row must still fire change detection

});

```

**Before opening the global modal** (each page — button / row action that opens it):

```javascript

await varPageName.setValue('emailTemplates'); // camelCase string; must match dummy query checks

// ... then open modal / run global query

```

### Local page setup (per page that hosts the list)

1. **Create** a new query named `{pageName}WatcherDummy` (examples: `emailTemplatesWatcherDummy`, `sequencesWatcherDummy`). Naming: **camelCase page token** + literal `WatcherDummy`.

2. **Resource:** **Query JSON with SQL** (built-in Retool resource — runs **client-side**, no database round-trip). Do **not** use Supabase or any real DB connection for this signal-only query.

3. **SQL** (mustache forces dependency on the global variable):

```sql

SELECT '{{ varRefreshTrigger.value }}' AS refresh_signal

```

> **Do not** alias the column `trigger` — it is a reserved word in Retool's SQL parser and will throw a parse error. Use e.g. `refresh_signal`.

4. **Run behavior:** **Automatic** — unlike JS queries, this resource type reliably re-runs when `varRefreshTrigger` changes.

5. **Success handler** on that query (Response tab → Event handlers → **Success** → **Run script**):

```javascript

if (varRefreshTrigger.value?.page === 'emailTemplates') {

await varRefreshTrigger.setValue(null); // reset so the same payload does not loop

await getEmailTemplates.trigger(); // your local list/detail query — replace name + page string

}

```

Match the string `'emailTemplates'` to the value you set on `varPageName` for that page, and replace `getEmailTemplates` with the actual local query.

### Why this works

Global code **writes intent** (`varRefreshTrigger`); local code **reads** globals and **owns** triggering page queries. The dummy SQL query exists only so Retool's reactive engine notices `varRefreshTrigger` — no network I/O, identical in dev and production.

### Naming convention

| Piece | Pattern | Example |

|-------|---------|---------|

| Dummy watcher query | `{pageName}WatcherDummy` | `emailTemplatesWatcherDummy` |

| `varPageName` / `page` on trigger object | Same camelCase token | `'emailTemplates'` |

| Local refresh query | Your existing GET / list query | `getEmailTemplates`, `getTableSequences` |

### Relationship to §15

The multi-array drawer uses the same `varRefreshTrigger` + `varPageName` contract. Each host page still needs its own `{pageName}WatcherDummy` (or equivalent) wired as above — not `jsWatch{PageName}Refresh`.

### Common pitfalls

| Pitfall | Symptom | Fix |

|---------|---------|-----|

| JS query as watcher with Automatic greyed out | Local table never refreshes | Use **Query JSON with SQL** dummy per §16 |

| `AS trigger` in dummy SQL | Parse error | Rename alias to `refresh_signal` (or any non-reserved name) |

| Real DB resource for dummy query | Wasted network calls; env drift | Use **Query JSON with SQL** only |

| Missing `timestamp` in trigger object | Second save on same page does not refresh | Always `timestamp: Date.now()` |

| `varPageName` not set before open | Wrong page refreshes or none | Set `varPageName` in the same handler chain before opening the global UI |

| Page string mismatch | Dummy runs but skips refresh | Use identical string in `varPageName.setValue(...)` and `if (varRefreshTrigger.value?.page === '...')` |

I don’t know if this works with my use case? Maybe I’m misunderstanding how this works.

I’m trying to understand how I can add fields to the watched inputs. It looks like a drop down list. I think these are fields that are directly linked to the inputs I have in the query. What I’m looking to do is trigger the query based on a change in a variable that is NOT part of the query.

The reason I would want to do that is because I have nested data, so lets say I change a contact’s info on a contact modal. That is the global modal because I don’t want to create 10 local contact pages, I have a global edit contact modal and then I can edit contacts in multiple places through out the app.

The contact name is a value that is shown on my leads page, the contact page, the contact lists page the email page etc. When I’m on the leads page and open the global contacts modal the leads page won’t update when I change the contact through a patch api.

I don’t want the leads page to show Stan when I just opened up a modal to rename Stan → Steve. In order to re trigger the get leads query I have a general refreshQuery variable that I’m watching through my Post SQL Json hack. I also create a varPageName that corresponds to the page I’m on. That way the leads page watcher triggers the lead page query only if the name of the refreschQuery is LedsPage.

There is nothing in the leadsPeople query parameters that has changed, what changed is a data item in a linked table that the leadPeople api references from a global modal and a global post / patch request. I need some of of triggering a local table (needs to be local for server side pagination, sorting, filtering) from a global modal selectively, it when i call the global modal from page A refresh page A’s queries not all the possible pages this global modal is associated with.