ALWAYS use
@proofkit/typegento set up an @proofkit/fmdapi project. Typegen generates type-safe, schema-validated clients directly from your FileMaker layouts. It is the preferred and recommended path — manual client setup is only a fallback. Before writing any DataApi code, run typegen first.
Prerequisites
- FileMaker Server 19.6+ accessible via port 443
- OttoFMS 4.7+ installed on the FM server (for API key auth)
- FileMaker account with the
fmrestextended privilege enabled - Data API enabled on the FileMaker server
Setup
1. Install the runtime package
pnpm add @proofkit/fmdapi
2. Set environment variables
# .env
FM_SERVER=https://your-server.com # must start with https://
FM_DATABASE=MyFile.fmp12 # must end with .fmp12
# OttoFMS Data API key (recommended)
OTTO_API_KEY=dk_123456...789
# OR username/password (requires token store in production)
FM_USERNAME=admin
FM_PASSWORD=password
3. Initialize and run typegen
npx @proofkit/typegen@beta init
This creates proofkit-typegen-config.jsonc at your project root. Configure your layouts:
{
"$schema": "https://proofkit.dev/typegen-config-schema.json",
"config": {
"type": "fmdapi",
"path": "schema",
"validator": "zod/v4",
"clientSuffix": "Layout",
"clearOldFiles": true,
"generateClient": true,
"layouts": [
{ "layoutName": "api_Customers", "schemaName": "Customers" },
{
"layoutName": "api_Invoices",
"schemaName": "Invoices",
"valueLists": "strict",
"strictNumbers": true
}
]
}
}
Run typegen to generate clients:
npx @proofkit/typegen@beta
Add a convenience script to package.json:
{
"scripts": {
"typegen": "npx @proofkit/typegen@beta"
"typegen:ui": "npm run typegen ui"
}
}
4. First query
import { CustomersLayout } from "./schema/client";
const { data } = await CustomersLayout.findOne({
query: { id: "==123" },
});
console.log(data.fieldData);
The generated client includes full type safety and runtime schema validation — no manual type definitions needed.
Core Patterns
Generated output structure
schema/
generated/ # Auto-generated. NEVER edit.
Customers.ts
Invoices.ts
client/ # Auto-generated. NEVER edit.
Customers.ts
Invoices.ts
index.ts
Customers.ts # Override file. Safe to edit.
Invoices.ts # Override file. Safe to edit.
Override files for customization
Override files live at schema/<schemaName>.ts. They re-export from generated/ and are never overwritten by typegen. Customize by modifying the Zod schema:
// schema/Customers.ts (override file)
import { z } from "zod/v4";
import {
ZCustomers as ZCustomers_generated,
} from "./generated/Customers";
export const ZCustomers = ZCustomers_generated
.omit({ active: true })
.extend({
active: z.coerce.boolean(),
});
export type TCustomers = z.infer<typeof ZCustomers>;
The generated client automatically imports from the override file, so transformations apply at runtime.
Type inference helpers
For portals, use InferZodPortals from @proofkit/fmdapi:
import type { InferZodPortals } from "@proofkit/fmdapi";
export const ZInvoicesPortals = {
lineItems: ZLineItems,
};
export type TInvoicesPortals = InferZodPortals<typeof ZInvoicesPortals>;
Custom env variable names
{
"config": {
"type": "fmdapi",
"envNames": {
"server": "MY_FM_SERVER",
"db": "MY_FM_DATABASE",
"auth": {
"apiKey": "MY_OTTO_KEY"
}
},
"layouts": [
{ "layoutName": "api_Users", "schemaName": "Users" }
]
}
}
Default env variable names when envNames is omitted: FM_SERVER, FM_DATABASE, OTTO_API_KEY (or FM_USERNAME/FM_PASSWORD for Fetch adapter).
Multiple configs
The config key can be an array mixing fmdapi and fmodata entries, each with its own path and envNames.
FM MCP mode (local WebViewer development — no credentials)
FM MCP mode lets typegen fetch layout metadata from a locally running FileMaker file via the FM MCP proxy, without needing OttoFMS, a hosted server, or any credentials. Generated clients use WebViewerAdapter — FM MCP is only used during typegen.
{
"$schema": "https://proofkit.dev/typegen-config-schema.json",
"config": {
"type": "fmdapi",
"fmMcp": { "enabled": true },
"layouts": [
{ "layoutName": "api_Contacts", "schemaName": "Contacts" }
]
}
}
fmMcp accepts an object with optional overrides:
enabled—trueto enable (default when object is present)scriptName— FM script the proxy calls for Data API operations. Resolution:fmMcp.scriptName>webviewerScriptName>"execute_data_api"baseUrl— FM MCP server URL (default:http://127.0.0.1:1365). Can also be set viaFM_HTTP_BASE_URLenv varconnectedFileName— FileMaker file name. If omitted, auto-discovered fromGET /connectedFilesand written back to config
The generated client uses WebViewerAdapter with webviewerScriptName if set, otherwise "execute_data_api".
Prerequisites:
- FM MCP daemon running locally (
GET http://127.0.0.1:1365/healthshould return OK) - FileMaker file open on the local machine
- "Connect to MCP" script run in the FileMaker file (opens a WebViewer window that bridges HTTP requests)
- That WebViewer window must stay open in Browse mode (not Layout mode)
No .env file needed for typegen in fmMcp mode — baseUrl defaults, connectedFileName is auto-discovered.
Common Mistakes
CRITICAL: Manually redefining TypeScript types instead of using typegen
Wrong:
// Hand-writing types that duplicate your FM layout
interface Customer {
name: string;
email: string;
phone: string;
}
const client = DataApi<Customer>({
adapter: new OttoAdapter({ /* ... */ }),
layout: "API_Contacts",
});
Correct:
# Generate types and clients from FileMaker
npx @proofkit/typegen@beta
// Use the generated client
import { ContactsLayout } from "./schema/client";
const { data } = await ContactsLayout.find({ query: { email: "==test@example.com" } });
Hand-written types drift when FileMaker fields change, with no runtime protection. The typegen-generated client bundles a Standard Schema validator that catches field renames at runtime.
Source: packages/typegen/src/buildSchema.ts
CRITICAL: Editing generated or client directories
Wrong:
// schema/generated/Customers.ts <-- editing this file
export const ZCustomers = z.object({
name: z.string(),
active: z.coerce.boolean(), // manually added
});
Correct:
// schema/Customers.ts <-- edit the override file instead
import { ZCustomers as ZCustomers_generated } from "./generated/Customers";
export const ZCustomers = ZCustomers_generated
.omit({ active: true })
.extend({ active: z.coerce.boolean() });
export type TCustomers = z.infer<typeof ZCustomers>;
Files in generated/ and client/ are overwritten on every typegen run; all customizations belong in the override files at the schema root.
Source: apps/docs/content/docs/typegen/customization.mdx
HIGH: Using old config file format
Wrong:
// fmschema.config.mjs
export default {
schemas: [{ layout: "Customers", schemaName: "Customers" }],
};
Correct:
// proofkit-typegen-config.jsonc
{
"$schema": "https://proofkit.dev/typegen-config-schema.json",
"config": {
"type": "fmdapi",
"layouts": [
{ "layoutName": "Customers", "schemaName": "Customers" }
]
}
}
The config migrated from fmschema.config.mjs to proofkit-typegen-config.jsonc in v5. Run npx @proofgeist/fmdapi@latest upgrade to auto-migrate.
Source: apps/docs/content/docs/fmdapi/version-5.mdx
CRITICAL: Putting env var values instead of names in config
Wrong:
{
"envNames": {
"server": "https://my-server.com",
"auth": {
"apiKey": "dk_abc123secret"
}
}
}
Correct:
{
"envNames": {
"server": "MY_FM_SERVER",
"auth": {
"apiKey": "MY_OTTO_KEY"
}
}
}
envNames expects the names of environment variables (e.g. "MY_OTTO_KEY"), not their secret values; typegen reads the actual values from the environment at runtime.
Source: apps/docs/content/docs/typegen/config.mdx
HIGH: Not running typegen after FileMaker schema changes
Wrong:
// Manually adding a new field that was added in FileMaker
const ZCustomers = ZCustomers_generated.extend({
newField: z.string(),
});
Correct:
npx @proofkit/typegen@beta
After changing layouts in FileMaker, re-run typegen to regenerate types; the generated schemas are the source of truth for field names and types.
Source: packages/typegen/src/typegen.ts
HIGH: Mixing Zod v3 and v4 in the same project
Wrong:
{
"config": [
{ "type": "fmdapi", "validator": "zod/v4", "layouts": [] },
{ "type": "fmdapi", "validator": "zod/v3", "layouts": [] }
]
}
Correct:
{
"config": [
{ "type": "fmdapi", "validator": "zod/v4", "layouts": [] },
{ "type": "fmdapi", "validator": "zod/v4", "layouts": [] }
]
}
Zod v3 and v4 have incompatible APIs (z.infer vs z.output, different extend behavior); use one version across all configs to avoid runtime conflicts.
Source: apps/docs/content/docs/typegen/config.mdx
CRITICAL: Using FmMcpAdapter in production application code
Wrong:
import { FmMcpAdapter } from "@proofkit/fmdapi/adapters/fm-mcp";
const client = DataApi({
adapter: new FmMcpAdapter({
baseUrl: "http://127.0.0.1:1365",
connectedFileName: "MyFile",
}),
layout: "Contacts",
});
Correct:
// Use the typegen-generated client (uses WebViewerAdapter internally)
import { ContactsLayout } from "./schema/client";
const { data } = await ContactsLayout.find({ query: { name: "==John" } });
FmMcpAdapter is internal to typegen's metadata fetching process. It only runs during code generation, never in production. Generated clients use WebViewerAdapter for runtime data access inside FileMaker WebViewer.
HIGH: Setting standard FM env vars when using fmMcp mode
Wrong:
# Agent configures both standard and fmMcp vars
FM_SERVER=https://fm.example.com
FM_DATABASE=MyFile.fmp12
OTTO_API_KEY=dk_abc123
FM_HTTP_BASE_URL=http://127.0.0.1:1365
Correct:
# fmMcp mode only — no server/db/auth needed
# baseUrl defaults to http://127.0.0.1:1365 if not set
# connectedFileName is auto-discovered if not set
FM_CONNECTED_FILE_NAME=MyFile
fmMcp mode bypasses the standard FM_SERVER/FM_DATABASE/auth env vars entirely. Setting both causes confusion when standard validation reports missing values.
HIGH: FM MCP connection failures — troubleshooting
If typegen fails to connect in fmMcp mode, do NOT suggest falling back to OttoFMS or FetchAdapter. The developer chose fmMcp because they don't have hosted credentials or are working with a local-only file.
Troubleshooting checklist:
- Daemon running?
curl http://127.0.0.1:1365/health— should return{"service":"fm-mcp","status":"ok"} - File connected?
curl http://127.0.0.1:1365/connectedFiles— should list the target file - File not listed? Open the FileMaker file and run the "Connect to MCP" script
- Still not working? Ensure the WebViewer window opened by "Connect to MCP" is in Browse mode, not Layout mode. Closing this window or switching to Layout mode silently breaks the proxy.
CRITICAL: FM_SERVER without https:// prefix
Wrong:
FM_SERVER=filemaker.example.com
Correct:
FM_SERVER=https://filemaker.example.com
@proofkit/fmdapi expects FM_SERVER to be a full URL including the https:// protocol prefix. Without it, requests fail with connection errors or malformed URL exceptions.
Source: apps/docs/content/docs/fmdapi/quick-start.mdx
CRITICAL: Missing fmrest privilege on FM account
The Data API requires the fmrest extended privilege on the FileMaker account. Without it, all API calls return authorization errors. Enable via File > Manage > Security > Privilege Sets > Extended Privileges.
Source: apps/docs/content/docs/cli/guides/getting-started.mdx
References
- fmdapi-client: After generating clients with typegen, see the fmdapi-client skill for full CRUD method usage, adapter configuration, portal data, and script execution patterns.
- webviewer-integration: For WebViewer projects using FM MCP mode, the webviewer-integration skill covers fmFetch, callFMScript, and WebViewerAdapter runtime usage.