packages/fmodata/skills/typegen-fmodata/SKILL.md
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.
npx skillsauth add proofgeist/proofkit typegen-fmodataInstall 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.
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.
fmodata extended privilege enablednpm add @proofkit/fmodata
# .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
npx @proofkit/typegen@beta init
This creates proofkit-typegen-config.jsonc. Configure for OData mode:
{
"$schema": "https://proofkit.proof.sh/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"
}
}
import { FMServerConnection } from "@proofkit/fmodata";
export const connection = new FMServerConnection({
serverUrl: process.env.FM_SERVER,
auth: {
apiKey: process.env.OTTO_API_KEY,
},
});
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);
}
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
Each generated file exports a fmTableOccurrence with:
textField(), numberField(), etc.)FMFID/FMTID) from FileMaker metadata// 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",
},
);
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.
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>;
{
"config": {
"type": "fmodata",
"envNames": {
"server": "MY_FM_SERVER",
"db": "MY_FM_DATABASE",
"auth": {
"apiKey": "MY_OTTO_KEY"
}
},
"tables": [
{ "tableName": "Customers" }
]
}
}
The config key can be an array mixing fmdapi and fmodata entries, each with its own path and envNames.
{
"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 typeWrong:
// 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
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
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
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
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
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
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
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
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
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
webviewer fmFetch callFMScript WebViewerAdapter globalSettings setWebViewerName SendCallback window.FileMaker browser-only FileMaker Web Viewer script execution fire-and-forget FMScriptOption PerformScript callback fetchId handleFmWVFetchCallback
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")