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>

1. Passing data in: the typed parameters

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
text1text4 any text expression a string
number1number4 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.


2. Getting data out: the typed results

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….