Call FileMaker scripts from an app

A Web Viewer app has two ways to talk to FileMaker: the Data API path (typed CRUD against a layout, covered in Use a typed data client) and FileMaker scripts. Scripts are the backend for everything that isn't a straightforward record read or write — the Web Viewer handles the interface, and a script performs privileged work inside FileMaker on its behalf. For guidance on when to reach for a script instead of Data API CRUD, see Splitting App Logic.

Calling a script from the Web Viewer

@adt/fmdapi exports two functions for this, both re-exported from the top-level package:

  • fmFetch(scriptName, data) — calls a script and returns a Promise that resolves with whatever the script sends back. Use this whenever the app needs a result.

  • callFMScript(scriptName, data, option?) — fires a script and returns immediately, with no result. Use it for navigation or "just do it" actions, with an optional FMScriptOption (CONTINUE, HALT, EXIT, RESUME, PAUSE, SUSPEND_AND_RESUME) when the script needs a specific pause/resume behavior.

Copy
import { fmFetch } from "@adt/fmdapi";

type DashboardResult = {
  openInvoices: number;
  overdueInvoices: number;
};

const dashboard = await fmFetch<DashboardResult>("Get Dashboard", {
  accountId: "123",
});
Copy
import { FMScriptOption, callFMScript } from "@adt/fmdapi";

callFMScript(
  "Navigate Away From Web Viewer",
  { layout: "Contacts" },
  FMScriptOption.RESUME,
);

fmFetch hands your data object straight to the script as its script parameter — no envelope, no wrapper. An object arrives as that object, an array as that array, a string verbatim:

Copy
{ "accountId": "123" }

That means the script reads its inputs from the top level, not nested under a data key:

Copy
Set Variable [ $param   ; Get ( ScriptParameter ) ]
Set Variable [ $account ; JSONGetElement ( $param ; "accountId" ) ]         // correct — top level
Set Variable [ $wrong   ; JSONGetElement ( $param ; "data.accountId" ) ]    // WRONG — returns ""

The script sends its result back with Exit Script [ Result ]. fmFetch performs the script through window.FileMaker.PerformScriptAsync and resolves with whatever the script exits with — a JSON result is parsed, a bare string is passed through as-is, and an empty result resolves as undefined.

This request/response plumbing — full signatures, the FMScriptOption values, and the result contract in detail — is covered in Script Calls. This page stays at the "how to think about it" level.

What the script must do

A script called through fmFetch owns the app-specific success, validation-error, and unexpected-error contract. A few rules keep that contract honest:

  1. Read inputs from the top level, as shown above.

  2. Exit with a result on every code path — including validation failures and trapped errors. A script that halts before reaching Exit Script leaves the Web Viewer's fmFetch promise pending forever; fmFetch has no timeout.

  3. Never return success unconditionally. If the script delegates work — Perform Script on Server, a transaction subscript — check the result and Get(LastError) before reporting success. A wrapper that ignores a failed delegated call and reports success anyway produces "success with no write," which is very hard to debug from the JavaScript side.

  4. Return a consistent result your app can validate, such as { ok, message } or { messages: [...], response: {...} }. There's no universal fmFetch result shape — each app defines its own, and the calling code should validate it rather than assume it.

  5. Never navigate the Web Viewer's own window. The script runs in the context of the Web Viewer's window — don't change its layout or found set. If the work needs record context, do it in an off-screen New Window, or hand it to Perform Script on Server.

Copy
import { fmFetch } from "@adt/fmdapi";
import { z } from "zod/v4";

const saveResultSchema = z.object({
  ok: z.boolean(),
  message: z.string().optional(),
});

const result = saveResultSchema.parse(
  await fmFetch("Save Contact Preferences", { contactId: "123" }),
);

Shape of a script called from the app

A script written to be called through fmFetch follows the same basic skeleton, whatever it does:

Copy
Set Variable [ $param ; Get ( ScriptParameter ) ]

# Read whatever fields the call needs, at the top level
Set Variable [ $accountId ; JSONGetElement ( $param ; "accountId" ) ]

# Do the work. If record context is needed, do it in an off-screen
# New Window rather than the Web Viewer's own window — or hand it
# to Perform Script on Server.

Set Variable [ $result ; JSONSetElement ( "{}" ; "ok" ; True ; JSONBoolean ) ]

Exit Script [ Text Result: $result ]

Write your business logic in place of the middle step, and make sure every branch — including error and validation paths — reaches its own Exit Script [ Result ] rather than falling through to a halt.