Execute your own JavaScript in a native mobile app and get real Bubble values back: numbers, dates, lists and objects, not just strings. Async/await support, automatic type conversion both ways, a configurable timeout for async code, and rock-solid error handling that never crashes your app. The native alternative to web Toolbox.
Your code runs in the app's own JavaScript engine, so there is no WebView and no slow load.
<aside> ☝️
The plugin ships with a context document you can paste into Claude, ChatGPT, Gemini, or any AI assistant. Once pasted, it knows every action, field, state, event, and common pitfall, so it can pick the right action, write your Bubble expressions, or diagnose an error for you.
Download it here:
Toolbox_Custom_JS_Runner_AI_Context.md
</aside>
Every slot you fill in the action becomes a variable with the same name in your code, already converted to the right JavaScript type:
| Slots | Bubble side | In your code |
|---|---|---|
text1 … text4 |
any text expression | a string |
number1 … number4 |
any number expression | a real number — locale-safe: 1 234,56, 1.234,56, 1,234.56 all parse correctly (no :format as text workaround needed) |
date1, date2 |
any date expression | a real JavaScript Date object |
yesno1, yesno2 |
a yes/no expression | a real boolean (true/false); leave the slot empty and it's null in your code |
list_texts1, list_texts2 |
any list of texts | a real array of strings |
list_numbers1, list_numbers2 |
any list of numbers | a real array of numbers |
list_dates1 |
any list of dates | a real array of Date objects |
Also in scope: params (an object holding all of the above plus params.run_id and params.timeout_seconds), exportToBubble(value), log(...), and a working console (its output is captured — see §5).
Empty slot ⇒ null in your code (empty array for lists). An invalid value (e.g. an unparsable number) also becomes null, with a warning written to console_output — your run never fails because of a bad parameter.
Passing a Bubble thing: extract its fields with Bubble expressions into the slots (text1 = Current cell's Product's Name, number1 = … 's Price). Need more slots? Put JSON in a text slot: text1 = {"a":1,"b":2} then const data = JSON.parse(text1);.
Dates as text: if you type a date manually instead of binding a Bubble date, use ISO format (2026-06-12 or 2026-06-12T10:30:00Z). Formats like 06/12/2026 are refused on purpose — they would parse differently on different devices.
Return (or exportToBubble) a value; the plugin detects its type and fills the right state:
| You return… | result_type |
Read it in… | Notes |
|---|---|---|---|
| a string | text |
result_text |
|
| a finite number | number |
result_number |
NaN/Infinity → clear error instead |
| a boolean | boolean |
result_yes_no |
result_text shows yes/no |
a Date |
date |
result_date |
a REAL Bubble date — format it, compare it, save it |
| an array of numbers | list_numbers |
result_list_numbers |
bind a Repeating Group directly |
| an array of strings | list_texts |
result_list_texts |
|
| an array of dates | list_dates |
result_list_dates |
|
| a mixed array | list_mixed |
result_list_texts / result_json |
items stringified |
| an empty array | list_empty |
(all list states empty) | |
| a plain object | object |
result_json |
clean JSON; dates inside become ISO strings |
nothing / null |
(empty) | — | success with empty states |
Whatever the type, result_text always holds a readable rendering and result_json always holds the JSON rendering. Every list also fills result_list_texts, so a Repeating Group of texts is a universal display.
If you return something that cannot cross into Bubble (a function, undefined inside an object, NaN, a circular structure, a Map…), the run fails with a message that names the exact problem — for the object case, down to the path: [UNSUPPORTED_RETURN] stats.ratio is NaN….