Script calls
Not everything belongs in Data API CRUD. Multi-record mutations, related-record edits, and anything that needs a FileMaker transaction should run as a FileMaker script instead. @adt/fmdapi exports two functions for calling scripts from your app:
-
fmFetch— call a script and await its result. -
callFMScript— fire a script and don't wait for anything back.
Note Upgrading from before ADT 0.8? fmFetch used to wrap your data in a callback envelope and rely on globalSettings.setWebViewerName and ADT_send_callback to get a result back. That flow is gone: fmFetch now calls the script directly and reads its Exit Script result. setWebViewerName is a no-op today, kept only so old apps keep compiling. Scripts written against the old callback template need to be rewritten — see Upgrade to ADT 0.8.
Both wrap window.FileMaker.PerformScriptAsync (the API FileMaker Pro injects into a Web Viewer), so they only work once the app is running inside a FileMaker Web Viewer or WebDirect — not in a plain browser tab. See Call FileMaker Scripts from an App for the broader pattern these two functions support.
fmFetch: Call a script and get a result
type DashboardResult = {
openInvoices: number;
overdueInvoices: number;
};
const dashboard = await fmFetch<DashboardResult>("Get Dashboard", {
accountId: "123",
});
fmFetch(scriptName, data) sends data to the script as its script parameter, unchanged — an object arrives as that object (parsed from JSON), an array as that array, a string verbatim. There is no wrapper and no callback id to manage. If window.FileMaker isn't available yet — the Web Viewer is still initializing — fmFetch retries a few times with a short backoff before giving up; callFMScript does not, so prefer fmFetch for calls that might run early in the app's lifecycle.
What the script receives
Read inputs from the top level of Get ( ScriptParameter ):
Set Variable [ $param ; Get ( ScriptParameter ) ]
Set Variable [ $accountId ; JSONGetElement ( $param ; "accountId" ) ] // correct — top level
There's no data wrapper to unwrap — JSONGetElement ( $param ; "data.accountId" ) returns "" against a script called through fmFetch.
How the result comes back
fmFetch performs the script through window.FileMaker.PerformScriptAsync and resolves with whatever the script exits with:
-
A result that parses as JSON is parsed and handed back as an object, array, string, number, or boolean.
-
A result that doesn't parse as JSON is passed through as a bare string.
-
An empty result resolves as
undefined.
The script must exit with a result on every code path — including validation failures and trapped errors:
Warning Always exit with a result A script that halts, or sits on a dialog, before reaching Exit Script leaves the JavaScript promise pending forever — fmFetch has no timeout. If the script delegates work (Perform Script on Server, a transaction subscript), check the result and fold failure into the value you exit with instead of exiting success unconditionally. A script that doesn't exist rejects the promise.
Warning Never navigate the web viewer's window A script called through fmFetch runs in the context of the Web Viewer's window. Don't navigate to a different layout or change the found set in that window — it can disrupt the Web Viewer and the app running in it. If the script needs record context to do its work, open an off-screen New Window and operate there, or hand the work to Perform Script on Server instead.
Under pnpm dev, fmFetch works the same way: it still calls window.FileMaker.PerformScriptAsync, but that function is supplied by fmBridge() instead of a real Web Viewer — see fmBridge().
Typing the result
Pass a type parameter to get a typed result back:
const dashboard = await fmFetch<DashboardResult>("Get Dashboard", {
accountId: "123",
});
Warning Unsafe casting The type parameter isn't validated against what FileMaker actually returns. Parse the result with a runtime schema instead of trusting the cast:
const saveResultSchema = z.object({
ok: z.boolean(),
message: z.string().optional(),
});
const result = saveResultSchema.parse(
await fmFetch("Save Contact Preferences", { contactId: "123" }),
);
Each script's result shape is app-specific — there's no universal fmFetch envelope to assume. Define and validate the contract your FileMaker script actually returns.
callFMScript: Fire and forget
Use callFMScript when your app doesn't need anything back — closing a dialog, logging an event, navigating away from the Web Viewer:
callFMScript("Navigate Away From Web Viewer", { layout: "Contacts" });
The script parameter is passed as the second argument — a JSON object or a string — and is stringified automatically. callFMScript returns void; it does not install a callback, so don't call it expecting a result:
// Wrong — result is always undefined
const result = callFMScript("Calculate Total", { orderId: "123" });
// Correct — use fmFetch when you need the result
const result = await fmFetch<{ total: number }>("Calculate Total", {
orderId: "123",
});
FMScriptOption
An optional third argument controls how FileMaker starts the script — the same Continue/Halt/Exit/Resume/Pause/Suspend-and-Resume options available when starting a script from FileMaker itself. Import the FMScriptOption constant instead of guessing at the numeric codes:
callFMScript("Navigate Away From Web Viewer", { layout: "Contacts" }, FMScriptOption.RESUME);
FMScriptOption
|
Value |
|---|---|
CONTINUE
|
"0"
|
HALT
|
"1"
|
EXIT
|
"2"
|
RESUME
|
"3"
|
PAUSE
|
"4"
|
SUSPEND_AND_RESUME
|
"5"
|
See Claris's documentation on script options for what each one does.
Use scripts for transactional writes
Data API CRUD calls each hit FileMaker independently, so multi-record writes and record-lock-sensitive workflows that must succeed or fail together should run as one FileMaker script called with fmFetch instead of chaining generated-client calls. See Splitting App Logic for when to reach for a script over Data API CRUD.
type CreateInvoiceResult = {
invoiceId: string;
status: "created";
};
const result = await fmFetch<CreateInvoiceResult>("Create Invoice With Line Items", {
invoice,
lineItems,
});