ProofKit

Run JS from FileMaker

How to use the Perform JavaScript in Web Viewer script step to trigger functions inside your web viewer app.

FileMaker can call a global Web Viewer namespace. Your app registers typed handlers on that namespace.

Initialize

Call initWebViewerCommands() once in browser code.

import { initWebViewerCommands } from "@proofkit/webviewer/commands";

initWebViewerCommands();

The module is import-safe on the server. initWebViewerCommands() returns undefined when window is unavailable.

Type commands

Declare commands in a .d.ts file. Extending DefineWebViewerCommandRegistry makes TypeScript check that every command is a function that accepts only string parameters.

import type { DefineWebViewerCommandRegistry } from "@proofkit/webviewer/commands";

export {};

declare module "@proofkit/webviewer/commands" {
  interface WebViewerCommandRegistry
    extends DefineWebViewerCommandRegistry<{
    openCustomer: (recordId: string) => void;
    refreshDashboard: () => void;
  }> {}
}

FileMaker passes string parameters, so command parameters must be strings.

You can also wrap individual entries with WebViewerCommandHandler.

import type { WebViewerCommandHandler } from "@proofkit/webviewer/commands";

declare module "@proofkit/webviewer/commands" {
  interface WebViewerCommandRegistry {
    openCustomer: WebViewerCommandHandler<(recordId: string) => void>;
  }
}

Static commands

Use registerWebViewerCommand for handlers that do not depend on component state. This example lets FileMaker ask the Web Viewer to load a customer snapshot through fmFetch.

import { fmFetch } from "@proofkit/webviewer";
import { registerWebViewerCommand } from "@proofkit/webviewer/commands";

registerWebViewerCommand("loadCustomerSummary", async (recordId) => {
  const summary = await fmFetch("Load Customer Summary", { recordId });
  window.dispatchEvent(
    new CustomEvent("customer-summary-loaded", {
      detail: summary,
    })
  );
});

React commands

Use useWebViewerCommand when a handler depends on UI state, routing, or component data.

import { useWebViewerCommand } from "@proofkit/webviewer/react";

export function CustomerScreen() {
  useWebViewerCommand("openCustomer", (recordId) => {
    console.log(recordId);
  });

  return null;
}

The hook registers on mount, unregisters on unmount, and keeps the latest callback after rerenders.

Next.js

Initialize from Client Components only.

"use client";

import { initWebViewerCommands } from "@proofkit/webviewer/commands";
import { useEffect } from "react";

export function WebViewerCommandBootstrap() {
  useEffect(() => {
    initWebViewerCommands();
  }, []);

  return null;
}

Use import-time initialization only in client-only modules.

"use client";

import { initWebViewerCommands } from "@proofkit/webviewer/commands";

initWebViewerCommands();

Early calls

Missing commands buffer by default. If FileMaker calls before React mounts, the call replays when the command registers.

initWebViewerCommands({
  missingCommand: "buffer",
  maxBufferedCallsPerCommand: 100,
});

Other missing-command modes are drop, warn, and throw.

Migration

Avoid direct window.proofkit assignment. It bypasses buffering, cleanup, and type checks.

Before:

useEffect(() => {
  window.proofkit = {
    openDialog: () => setOpen(true),
  };
}, []);

After:

useWebViewerCommand("openDialog", () => setOpen(true));

On this page