This guide explains FileMaker ideas easy-fm builds on.
Request flow usually looks like this:
FMHost -> Database -> Layout -> Record
And sometimes:
LayoutRecord -> Portal -> PortalRecord
FMHost represents FileMaker Server or FileMaker Cloud base address.
Example:
const host = new FMHost("https://example.com")
Host also owns timezone formatting rules used when date/time values are sent to FileMaker.
Database represents one FileMaker file.
Example:
const database = host.database({
database: "Contacts",
credentials: {
method: "filemaker",
username: "api-user",
password: "secret"
},
externalSources: []
})
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