packages/webviewer/skills/webviewer-integration/SKILL.md
webviewer fmFetch callFMScript WebViewerAdapter globalSettings setWebViewerName SendCallback window.FileMaker browser-only FileMaker Web Viewer script execution fire-and-forget FMScriptOption PerformScript callback fetchId handleFmWVFetchCallback
npx skillsauth add proofgeist/proofkit webviewer-integrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This package assumes the user has already added specific layouts, scripts, etc into their FileMaker file. This is accomplished using a FileMaker add-on, downloaded during the setup process if the project was created using @proofkit/cli, but the user must still manually install the add-on in their FileMaker file.
To download the latest version of the add-on to the user's computer, run npx -y @proofkit/cli@beta add addon webviewer in any directory.
Install the package and set the webviewer name before calling any scripts.
import { fmFetch, globalSettings } from "@proofkit/webviewer";
// Must match the Layout Object Name of the Web Viewer in FileMaker
globalSettings.setWebViewerName("web");
// Call a FM script and await the result
const result = await fmFetch("MyScript", { key: "value" });
To generate @proofkit/fmdapi clients that work with WebViewerAdapter, run typegen with FM MCP mode enabled (see the typegen-fmdapi skill in @proofkit/fmdapi):
npx @proofkit/typegen@beta
The setWebViewerName call is required for fmFetch to work. It tells the FM SendCallback script which webviewer to call back into. Set it once at app initialization.
fmFetch calls a FileMaker script via window.FileMaker.PerformScript and returns a Promise that resolves when the FM script calls SendCallback with the matching fetchId.
import { fmFetch } from "@proofkit/webviewer";
// With type parameter (unsafe cast -- validate with zod if needed)
type Customer = { id: string; name: string };
const customer = await fmFetch<Customer>("GetCustomer", { id: "123" });
The FM script receives a JSON parameter with two keys:
data -- whatever you passed as the second argumentcallback -- object containing fetchId, fn, and webViewerNameThe FM script must extract callback, attach the result, and call SendCallback:
Set Variable [ $json ; Get(ScriptParameter) ]
Set Variable [ $callback ; JSONGetElement($json ; "callback") ]
Set Variable [ $data ; JSONGetElement($json ; "data") ]
# ... do work, build $result ...
Set Variable [ $callback ; JSONSetElement($callback ;
["result"; $result; JSONObject];
["webViewerName"; "web"; JSONString])
]
Perform Script [ "SendCallBack" ; $callback ]
callFMScript calls a FileMaker script with no callback. Use when you don't need a return value.
import { callFMScript, FMScriptOption } from "@proofkit/webviewer";
// Basic call
callFMScript("NavigateToLayout", { layout: "Customers" });
// With script execution option
callFMScript("RunReport", { id: "42" }, FMScriptOption.HALT);
FMScriptOption values: CONTINUE ("0"), HALT ("1"), EXIT ("2"), RESUME ("3"), PAUSE ("4"), SUSPEND_AND_RESUME ("5").
WebViewerAdapter implements the @proofkit/fmdapi Adapter interface, routing Data API calls through a FM script that uses the Execute FileMaker Data API script step. No network auth needed.
import { DataApi } from "@proofkit/fmdapi";
import { WebViewerAdapter } from "@proofkit/webviewer/adapter";
const client = DataApi({
adapter: new WebViewerAdapter({ scriptName: "ExecuteDataApi" }),
layout: "API_Customers",
});
// Now use standard fmdapi methods
const records = await client.list();
const found = await client.findOne({ query: { id: "===1234" } });
The adapter supports: list, find, get, create, update, delete, layoutMetadata. It does NOT support executeScript or containerUpload -- these throw.
For fmFetch to resolve, the FM side needs two scripts:
SendCallbackPerform JavaScript in Web Viewer targeting the webviewer named in the callback, invoking handleFmWVFetchCallback(result, fetchId).The JS side registers callbacks in a map keyed by UUID (fetchId). When handleFmWVFetchCallback fires, it looks up and invokes the matching callback, resolving the Promise.
Wrong:
// app/api/route.ts (Next.js API route -- runs on server)
import { fmFetch } from "@proofkit/webviewer";
export async function GET() {
const data = await fmFetch("GetData", {});
return Response.json(data);
}
Correct:
// components/DataLoader.tsx (client component -- runs in Web Viewer)
"use client";
import { fmFetch } from "@proofkit/webviewer";
export function DataLoader() {
const load = async () => {
const data = await fmFetch("GetData", {});
};
// ...
}
@proofkit/webviewer depends on window.FileMaker, which is only injected by FileMaker into Web Viewer contexts. Importing it in server code or standard browsers will fail at runtime.
Source: packages/webviewer/src/main.ts lines 111-115, apps/docs/content/docs/webviewer/index.mdx
Wrong:
# FM Script -- missing SendCallback call
Set Variable [ $json ; Get(ScriptParameter) ]
Set Variable [ $data ; JSONGetElement($json ; "data") ]
# ... do work ...
Exit Script [ $result ]
Correct:
# FM Script -- calls SendCallback with matching webViewerName
Set Variable [ $json ; Get(ScriptParameter) ]
Set Variable [ $callback ; JSONGetElement($json ; "callback") ]
Set Variable [ $data ; JSONGetElement($json ; "data") ]
# ... do work, build $result ...
Set Variable [ $callback ; JSONSetElement($callback ;
["result"; $result; JSONObject];
["webViewerName"; "web"; JSONString])
]
Perform Script [ "SendCallBack" ; $callback ]
The FM script must call SendCallback with the correct webViewerName and the original fetchId (embedded in $callback). If SendCallback is never called, or called with the wrong webviewer name, the JS Promise hangs forever. Also ensure the script does not exit/halt early or navigate away from the layout containing the webviewer.
Source: apps/docs/content/docs/webviewer/troubleshooting.mdx, packages/webviewer/src/main.ts lines 79-87
Wrong:
// Runs immediately on page load
import { callFMScript } from "@proofkit/webviewer";
callFMScript("Init", {});
Correct:
import { callFMScript } from "@proofkit/webviewer";
// Wait for FileMaker to inject the JS bridge
function waitForFileMaker(): Promise<void> {
return new Promise((resolve) => {
if (window.FileMaker) return resolve();
const interval = setInterval(() => {
if (window.FileMaker) {
clearInterval(interval);
resolve();
}
}, 100);
});
}
await waitForFileMaker();
callFMScript("Init", {});
FileMaker injects window.FileMaker after the page loads. Calling callFMScript or fmFetch before it exists throws: "window.FileMaker was not available at the time this function was called".
Source: packages/webviewer/src/main.ts lines 111-115, apps/docs/content/docs/webviewer/troubleshooting.mdx
Wrong:
import { DataApi } from "@proofkit/fmdapi";
import { WebViewerAdapter } from "@proofkit/webviewer/adapter";
const client = DataApi({
adapter: new WebViewerAdapter({ scriptName: "ExecuteDataApi" }),
layout: "API_Customers",
});
// Throws at runtime
await client.executeScript("SomeScript", "param");
Correct:
import { DataApi } from "@proofkit/fmdapi";
import { WebViewerAdapter } from "@proofkit/webviewer/adapter";
import { fmFetch, callFMScript } from "@proofkit/webviewer";
const client = DataApi({
adapter: new WebViewerAdapter({ scriptName: "ExecuteDataApi" }),
layout: "API_Customers",
});
// Use fmdapi client for CRUD operations
const records = await client.list();
// Use webviewer functions for script execution
const result = await fmFetch("SomeScript", { param: "value" });
callFMScript("FireAndForget", {});
WebViewerAdapter.executeScript() and WebViewerAdapter.containerUpload() explicitly throw. Use fmFetch or callFMScript from @proofkit/webviewer to run scripts directly.
Source: packages/webviewer/src/adapter.ts lines 133-141
Wrong:
// Bypasses callback tracking, loses type safety
window.FileMaker.PerformScript("MyScript", JSON.stringify({ key: "value" }));
Correct:
import { callFMScript, fmFetch } from "@proofkit/webviewer";
// Fire-and-forget
callFMScript("MyScript", { key: "value" });
// Or with response
const result = await fmFetch("MyScript", { key: "value" });
Calling window.FileMaker.PerformScript directly bypasses the callback registration system. fmFetch won't work because the script parameter won't contain the callback object with fetchId. The library also handles JSON serialization and the PerformScriptWithOption variant automatically.
Source: packages/webviewer/src/main.ts lines 79-87, 92-122
Wrong:
import { DataApi } from "@proofkit/fmdapi";
import { WebViewerAdapter } from "@proofkit/webviewer/adapter";
const customersClient = DataApi({
adapter: new WebViewerAdapter({ scriptName: "ExecuteDataApi" }),
layout: "API_Customers",
});
const ordersClient = DataApi({
adapter: new WebViewerAdapter({ scriptName: "ExecuteDataApi" }),
layout: "API_Orders",
});
// Concurrent calls -- each waits for the previous to finish via SendCallback
// FileMaker executes scripts sequentially, causing compounding delays
const [customers, orders] = await Promise.all([
customersClient.list(),
ordersClient.list(),
]);
Correct:
// Sequential calls -- explicit about execution order
const customers = await customersClient.list();
const orders = await ordersClient.list();
In Web Viewer local mode, FM scripts execute single-threaded. Promise.all with multiple fmFetch-based calls does not parallelize -- each script must complete and call back before the next runs. Concurrent calls can queue up and cause UI freezes. Use sequential awaits and minimize the number of round-trips.
Source: apps/docs/content/docs/webviewer/fmdapi.mdx, packages/webviewer/src/main.ts
WebViewerAdapter implements the Adapter interface from @proofkit/fmdapi. CRUD methods (list, find, get, create, update, delete) work identically to the network-based adapter. See the fmdapi-client skill for method signatures and query patterns.development
FileMaker WebDirect ProofKit Web Viewer runtime behavior refresh resilience session state localStorage browser resize reload same deployment embedded bundle avoid separate deployment avoid separate web server @proofkit/webviewer fmFetch callFMScript WebViewerAdapter WebDirect page refresh
development
ENTRY POINT for @proofkit/fmodata projects. Generate TypeScript table schemas with entity IDs from FileMaker OData metadata using @proofkit/typegen. Covers proofkit-typegen-config.jsonc for OData mode, npx @proofkit/typegen setup, fmTableOccurrence generation, entity IDs (FMFID/FMTID), generated output structure, field exclusion, type overrides, InferTableSchema, env var configuration, OData prerequisites, fmodata privilege, and why typegen is required for entity ID correctness.
development
OData performance patterns for @proofkit/fmodata. Covers defaultSelect schema vs all, select() for minimal field fetching, select("all") override, pagination with top/skip, default 1000 record limit, batch operations for reducing round trips, entity IDs FMFID FMTID for rename resilience, null field query performance, getQueryString() debugging, relationship query performance testing, FileMaker OData optimization, avoiding OData service overload during testing.
tools
fmodata OData FMServerConnection fmTableOccurrence field builders textField numberField dateField timestampField containerField calcField listField query builder execute() filter operators eq ne gt gte lt lte contains startsWith endsWith matchesPattern inArray notInArray isNull isNotNull and or not tolower toupper trim CRUD insert update delete byId where navigate expand relationships batch Result error handling Effect.ts pattern FMODataError HTTPError ODataError ValidationError BatchTruncatedError entity IDs FMTID FMFID defaultSelect readValidator writeValidator orderBy asc desc top skip single maybeSingle count getSingleField FileMaker OData API schema management webhooks getTableColumns select("all")