ALWAYS use
@proofkit/typegento set up an @proofkit/fmodata project. Typegen generatesfmTableOccurrencedefinitions with correct field types and entity IDs (FMFID/FMTID) from FileMaker metadata. These entity IDs are opaque identifiers that cannot be guessed — they MUST come from typegen. Without typegen, queries usinguseEntityIds: truewill silently fail or return wrong data. Before writing any fmodata query code, run typegen first.
Prerequisites
- FileMaker Server 22.0.4+ accessible via port 443
- OttoFMS 4.11+ installed on the FM server (only required for API key auth)
- FileMaker account with the
fmodataextended privilege enabled - OData API enabled on the FileMaker server
Setup
1. Install the runtime package
npm add @proofkit/fmodata
2. Set environment variables
# .env
FM_SERVER=https://your-server.com # must start with https://
FM_DATABASE=MyFile.fmp12 # must end with .fmp12
# Option A: API key auth (requires OttoFMS 4.11+)
OTTO_API_KEY=dk_123456...789
# Option B: username/password auth
FM_USERNAME=admin
FM_PASSWORD=password
3. Initialize and run typegen
npx @proofkit/typegen@beta init
This creates proofkit-typegen-config.jsonc. Configure for OData mode:
{
"$schema": "https://proofkit.dev/typegen-config-schema.json",
"config": {
"type": "fmodata",
"path": "schema/odata",
"tables": [
{
"tableName": "Customers",
"fields": [
{ "fieldName": "InternalID", "exclude": true },
{ "fieldName": "Status", "typeOverride": "boolean" }
]
},
{ "tableName": "Orders", "variableName": "OrdersTable" }
]
}
}
Run typegen to generate schemas:
npx @proofkit/typegen@beta
Add a convenience script to package.json:
{
"scripts": {
"typegen": "npx @proofkit/typegen@beta"
"typegen:ui": "npm run tyepgen ui"
}
}
4. Create a server connection
import { FMServerConnection } from "@proofkit/fmodata";
export const connection = new FMServerConnection({
serverUrl: process.env.FM_SERVER,
auth: {
apiKey: process.env.OTTO_API_KEY,
},
});
5. First query
import { connection } from "./connection";
import { Customers } from "./schema/odata/generated/Customers";
const db = connection.database(process.env.FM_DATABASE);
const { data, error } = await db.from(Customers).list().execute();
if (error) {
console.error(error);
} else {
console.log(data);
}
Core Patterns
Generated output structure
For OData configs ("type": "fmodata"), typegen generates fmTableOccurrence definitions:
schema/odata/
generated/ # Auto-generated. NEVER edit fields or entity IDs.
Customers.ts # fmTableOccurrence with fields, entity IDs from FM metadata
Orders.ts
What typegen generates
Each generated file exports a fmTableOccurrence with:
- Field definitions with correct types (
textField(),numberField(), etc.) - Entity IDs (
FMFID/FMTID) from FileMaker metadata - Primary key designation
- Read-only markers for auto-enter fields
// schema/odata/generated/Customers.ts — generated by typegen, do NOT edit
import { fmTableOccurrence, textField, numberField, timestampField } from "@proofkit/fmodata";
export const Customers = fmTableOccurrence(
"Customers",
{
id: textField().primaryKey().entityId("FMFID:1039485"),
name: textField().notNull().entityId("FMFID:3432343"),
email: textField().notNull().entityId("FMFID:1223242"),
age: numberField().entityId("FMFID:4323435"),
createdAt: timestampField().readOnly().entityId("FMFID:5938271"),
},
{
entityId: "FMTID:1243253",
defaultSelect: "schema",
},
);
Safe customizations on generated schemas
You may add or edit these options on generated schemas — they will NOT be overwritten by typegen:
readValidator— transform data on read (e.g.,z.coerce.boolean())writeValidator— transform data on writedefaultSelect— control default field selectionnavigationPaths— define relationship navigation
// Safe to add to generated file:
active: numberField()
.readValidator(z.coerce.boolean())
.writeValidator(z.boolean().transform((v) => (v ? 1 : 0)))
.entityId("FMFID:6"),
Do NOT manually add new fields or change entity IDs — these must come from typegen.
Type inference
Use InferTableSchema to extract row types from generated tables:
import type { InferTableSchema } from "@proofkit/fmodata";
import { Customers } from "./schema/odata/generated/Customers";
type CustomerRow = InferTableSchema<typeof Customers>;
Custom env variable names
{
"config": {
"type": "fmodata",
"envNames": {
"server": "MY_FM_SERVER",
"db": "MY_FM_DATABASE",
"auth": {
"apiKey": "MY_OTTO_KEY"
}
},
"tables": [
{ "tableName": "Customers" }
]
}
}
Multiple configs
The config key can be an array mixing fmdapi and fmodata entries, each with its own path and envNames.
Config options for OData tables
{
"config": {
"type": "fmodata",
"path": "schema/odata",
"reduceMetadata": true,
"tables": [
{
"tableName": "Customers",
"variableName": "CustomersTable",
"fields": [
{ "fieldName": "InternalID", "exclude": true },
{ "fieldName": "Status", "typeOverride": "boolean" },
{ "fieldName": "Notes", "typeOverride": "string" }
]
}
]
}
}
variableName— custom export name (default: table name)fields[].exclude— omit field from generated schemafields[].typeOverride— override the inferred field type
Common Mistakes
CRITICAL: Inventing entity IDs or guessing field names
Wrong:
// Agent adds a field with a guessed entity ID
const contacts = fmTableOccurrence("contacts", {
...existingFields,
newField: textField().entityId("FMFID:99"), // guessed ID — will silently fail
});
Entity IDs (FMFID/FMTID) are opaque and MUST come from FileMaker metadata via typegen. Guessed entity IDs cause silent query failures — wrong data or empty results with no error.
To add new fields, use the fmodata CLI to discover real field names programmatically, then add them to the tables[].fields array in proofkit-typegen-config.jsonc, and re-run typegen to generate the correct definitions with entity IDs:
# 1. Discover available fields via the CLI
npx @proofkit/fmodata@beta metadata fields --table <TableName>
# Add --details for field types, nullability, etc.
# 2. Edit proofkit-typegen-config.jsonc to include the new field(s)
# 3. Re-run typegen to regenerate schemas with correct entity IDs
npx @proofkit/typegen@beta
Never manually edit entity IDs or field definitions in generated files — always re-run typegen after config changes.
Source: packages/typegen/src/fmodata/typegen.ts
CRITICAL: Manually redefining types instead of using generated/inferred types
Wrong:
interface Customer {
name: string;
email: string;
phone: string;
}
Correct:
import type { InferTableSchema } from "@proofkit/fmodata";
import { Customers } from "./schema/odata/generated/Customers";
type CustomerRow = InferTableSchema<typeof Customers>;
fmodata infers all types from fmTableOccurrence definitions. Use InferTableSchema<typeof table> if you need an explicit type alias.
Source: packages/typegen/src/buildSchema.ts
CRITICAL: Writing fmTableOccurrence from scratch without typegen
Wrong:
// Manually defining schema without entity IDs
const users = fmTableOccurrence("users", {
id: textField().primaryKey(),
name: textField().notNull(),
email: textField().notNull(),
});
Correct:
npx @proofkit/typegen@beta
Manual schemas lack entity IDs, meaning useEntityIds: true (the recommended database option) won't work. Queries will use field names instead of stable IDs, breaking silently when fields are renamed in FileMaker. Always generate schemas with typegen.
Source: apps/docs/content/docs/fmodata/entity-ids.mdx
HIGH: Omitting type discriminator for OData config
Wrong:
{
"config": {
"tables": [
{ "tableName": "Customers" }
]
}
}
Correct:
{
"config": {
"type": "fmodata",
"tables": [
{ "tableName": "Customers" }
]
}
}
Without "type": "fmodata", the config defaults to "fmdapi" and expects a layouts array instead of tables, causing a validation error.
Source: packages/typegen/src/types.ts:238-243
HIGH: Not running typegen after FileMaker schema changes or config edits
Wrong:
// Manually editing a generated file to add a new field
// OR editing proofkit-typegen-config.jsonc without re-running typegen
Correct:
npx @proofkit/typegen@beta
After changing table schemas in FileMaker or editing proofkit-typegen-config.jsonc, re-run typegen to regenerate types. The generated schemas are the source of truth for field names, types, and entity IDs.
Source: packages/typegen/src/fmodata/typegen.ts
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, not their secret values; typegen reads the actual values from the environment at runtime.
Source: apps/docs/content/docs/typegen/config.mdx
HIGH: Mixing Zod v3 and v4 in the same project
Use one Zod version consistently (v4 recommended). Zod v3 and v4 have incompatible APIs; mixing them causes runtime validation failures.
Source: apps/docs/content/docs/typegen/config.mdx
CRITICAL: FM_SERVER without https:// prefix
Wrong:
FM_SERVER=filemaker.example.com
Correct:
FM_SERVER=https://filemaker.example.com
@proofkit/fmodata expects FM_SERVER to be a full URL including the https:// protocol prefix.
Source: apps/docs/content/docs/fmdapi/quick-start.mdx
CRITICAL: Missing fmodata privilege on FM account
The OData API requires the fmodata 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
- fmodata-client: After generating schemas with typegen, see the fmodata-client skill for full query builder usage, CRUD operations, filter operators, relationships, batch operations, and error handling.
- odata-query-optimization: Performance patterns for fmodata queries — defaultSelect, pagination, batch operations, and entity ID usage.