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

Copy
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 ):

Copy
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:

Copy
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:

Copy
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:

Copy
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:

Copy
// Wrong — result is always undefined
const result = callFMScript("Calculate Total", { orderId: "123" });
Copy
// 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:

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

Copy
type CreateInvoiceResult = {
  invoiceId: string;
  status: "created";
};

const result = await fmFetch<CreateInvoiceResult>("Create Invoice With Line Items", {
  invoice,
  lineItems,
});