This guide explains FileMaker ideas easy-fm builds on.
Request flow usually looks like this:
DatabaseProvider -> Database -> Layout -> Record
And sometimes:
LayoutRecord -> Portal -> PortalRecord
DataApiProvider represents a FileMaker Data API connection. Custom providers can
implement other transports.
Example:
const provider = new DataApiProvider({
hostname: 'https://example.com',
database: 'Contacts',
credentials: { method: 'filemaker', username: 'api-user', password: 'secret' },
externalSources: [],
});
Provider owns formatting rules and translates semantic database operations to its transport.
Database represents one FileMaker file.
Example:
const database = Database.create({ provider });
Database object handles session management, requests, layout caching, and helper methods
like script().
This part surprises many new users:
FileMaker Data API works through layouts, not directly through tables.
That means:
Good practice:
LayoutRecord is one record returned from a layout.
You read and write through record.fields:
const record = await layout.records.get(123);
record.fields.Status.value = 'Active';
await record.commit();
Important:
commit() sends edits to FileMakerget() re-fetches latest version from serverPortals represent related rows returned inside parent layout record.
Portal data is not fetched automatically. You must ask for it:
const records = await layout.records
.list({
portals: {
LineItems: {
offset: 1,
limit: 25,
},
},
})
.fetch();
Then read portal rows from:
records[0].portals.LineItems.records;
FileMaker gives each record:
recordId: internal unique identifiermodId: internal modification counterThese are useful for API operations.
recordId should usually not be used as a business identifier because:
CustomerNumbermodId is different. It is still internal, but it can be useful for change detection,
optimistic locking, or checking whether a record has been modified since you last read it.
TypeScript users can describe layout shape to get better autocomplete and compile-time checks.
Minimal example:
import { type LayoutInterface, type Field } from '@jd-data-limited/easy-fm';
interface ContactsLayout extends LayoutInterface {
fields: {
FirstName: Field<string>;
LastName: Field<string>;
};
portals: {};
}
Then:
const layout = database.layout<ContactsLayout>('Contacts_API');
Now record.fields.FirstName is typed.
getting-started.md for first connection and first readworking-with-records.md for create/update/delete examplesquery-recipes.md for finding recordsauthentication-and-sessions.md for login behaviortimezones.md for date/time handling