Record methods and pagination
Every generated <Schema>Layout client — ContactsLayout, for example — is a DataApi instance built with the WebViewerAdapter. This page is the full reference for the methods and options it exposes. See Generate types with Typegen to configure layouts and generate the client. See WebViewerAdapter for how requests reach FileMaker.
The response shape
list, get, and find all resolve to the same shape:
{
data: [{ fieldData, recordId, modId, portalData }],
dataInfo: { foundCount, returnedCount, totalRecordCount, database, layout, table },
}
data is always an array, even for get() on a single record. Each record has:
-
fieldData— your typed field values (validated against the typegen-generated schema when one is attached, which it is for every generated client). -
recordIdandmodId— always returned as strings, even though you can pass either astringornumberasrecordIdwhen callingget,update, ordelete. -
portalData— an object keyed by portal name, empty if the layout has no portals.
create and update don't return data/dataInfo at all — create resolves to { recordId, modId }, update to { modId }, and delete to {} (plus any FileMaker script result fields, if the layout's scripts return one).
listAll and findAll are the exception: they resolve directly to a plain array of records, not a { data, dataInfo } wrapper.
Reading records
list
Returns records from a layout with no find criteria — the equivalent of browsing the layout.
const response = await ContactsLayout.list({
limit: 25,
offset: 1,
sort: { fieldName: "Name", sortOrder: "ascend" },
});
Accepts limit, offset, and sort (a single { fieldName, sortOrder } or an array for a multi-field sort — sortOrder is "ascend" or "descend").
get
Returns a single record by internal recordId.
const response = await ContactsLayout.get({ recordId: 42 });
const contact = response.data[0]?.fieldData;
find
Performs a FileMaker find.
const response = await ContactsLayout.find({
query: { Status: "Active" },
sort: { fieldName: "Name", sortOrder: "ascend" },
limit: 25,
});
query takes an object of field/value pairs to match, or an array of objects to OR multiple criteria sets together — the same way multiple FileMaker find requests combine:
// Status is "Active" OR "Pending"
await ContactsLayout.find({
query: [{ Status: "Active" }, { Status: "Pending" }],
});
Add omit: "true" to a query object to exclude matches instead of including them. A find that matches nothing throws a FileMaker error (code 401) unless you pass ignoreEmptyResult: true.
Finding a single record
Three helpers wrap find to hand back a single record instead of an array:
-
findFirst— returns the first matching record. Still throws if nothing matches. -
maybeFindFirst— returns the first matching record, ornullif nothing matches. Never throws on an empty result. -
findOne— returns the one matching record, and throws unless exactly one record is found (zero or more than one is an error).
const active = await ContactsLayout.findFirst({ query: { Status: "Active" } });
const maybeArchived = await ContactsLayout.maybeFindFirst({ query: { Status: "Archived" } });
// maybeArchived is null if nothing matched, otherwise the record
Each returns the same record shape as an entry in find's data array — { fieldData, recordId, modId, portalData } — but as a single object, not wrapped in an array.
Paginating through everything
listAll and findAll page through an entire layout or an entire find result automatically, returning one flat array of records:
const allActive = await ContactsLayout.findAll({ query: { Status: "Active" } });
Warning Use with caution on large datasets Both methods keep requesting pages until every matching record has been fetched. On a layout with tens of thousands of records this means tens of thousands of records loaded into memory — prefer find/list with an explicit limit and offset when you only need a page at a time.
limit and offset
list and find (and their listAll/findAll counterparts internally) accept:
-
limit— maximum number of records to return. If you omit it, up to 100 records are returned per request;listAll/findAllalso fetch 100 records per underlying page unless you specify otherwise. -
offset— 1-based position of the first record to return.offset: 1(or omitting it) starts from the beginning; anything greater skips ahead.
If a list or find call finds more records than it returned and you didn't pass a limit or offset, the client logs a console warning suggesting listAll/findAll or explicit pagination — it does not throw.
ignoreEmptyResult
By default, a find (and findFirst) that matches zero records throws a FileMaker error with code 401. Pass ignoreEmptyResult: true to get an empty result back instead:
const response = await ContactsLayout.find({
query: { Status: "Archived" },
ignoreEmptyResult: true,
});
// response.data is [] when nothing matches, instead of throwing
maybeFindFirst and findAll already handle the empty case for you internally (returning null or [] respectively) — you don't need to pass ignoreEmptyResult yourself when using them.
Writing records
create
const { recordId, modId } = await ContactsLayout.create({
fieldData: { Name: "Jane Doe", Status: "Active" },
});
fieldData is a Partial of the generated field type — you only need to supply the fields you're setting.
update
await ContactsLayout.update({
recordId: 42,
fieldData: { Status: "Inactive" },
});
Pass modId alongside recordId so FileMaker rejects the update when the record has changed since you last read it.
delete
await ContactsLayout.delete({ recordId: 42 });
layoutMetadata
Returns the layout's field and portal metadata — the same information typegen itself reads to build your schema.
const meta = await ContactsLayout.layoutMetadata();
// meta.fieldMetaData -> field definitions (type, displayType, valueList, maxCharacters, ...)
// meta.portalMetaData -> field metadata for each portal, keyed by portal name
You'll rarely need this at runtime since typegen already generates types from it — it's most useful for tooling or for inspecting a layout you haven't configured yet.
containerUpload
Uploads a browser File to a container field on an existing FileMaker record:
const file = fileInput.files?.[0];
if (file) {
await ContactsLayout.containerUpload({
containerFieldName: "Photo",
file,
recordId: 42,
});
}
The promise resolves with no value after FileMaker reports a successful upload.
| Argument | Type | Required | Description |
|---|---|---|---|
containerFieldName
|
A field name from the client schema | Yes | The target container field. |
file
|
Blob
|
Yes | The content to upload. It must be a File, or another Blob with a name that includes a file extension. |
recordId
|
string | number
|
Yes | The internal FileMaker record ID. |
modId
|
number
|
No | The expected modification ID for the target record. |
containerFieldRepetition
|
string | number
|
No | The container repetition. Only repetition 1, the default, is currently supported. |
timeout
|
number
|
No | Overrides the adapter's container-upload timeout for this call, in milliseconds. |
WebViewerAdapter converts the file to base64 and calls ADT_container_upload. Its default file-size limit is 20 MiB and its default timeout is 60 seconds. Both can be changed when constructing the adapter.
Warning A timeout has an unknown outcome If the upload script does not respond before the timeout, the client throws ContainerUploadTimeoutError. FileMaker may still complete the write after the client stops waiting, so check the record before trying the upload again.
See WebViewerAdapter for adapter configuration and error handling.
executeScript
Warning Not available in Web Viewer executeScript is part of the DataApi client's type signature, but the WebViewerAdapter that every ADT project uses throws if you call it — running arbitrary scripts through the Data API script action isn't how the Web Viewer bridge works. To call a FileMaker script from your app, use fmFetch or callFMScript instead. See Script Calls.
Options every method accepts
Alongside their own arguments, client methods accept shared adapter options:
-
batch— overrides whether a record or metadata request is grouped with other calls into a single round trip to FileMaker. See WebViewerAdapter for how request batching works. -
timeout— sets the per-call timeout forcontainerUpload. OtherWebViewerAdaptermethods ignore it. -
fetch— passesRequestInitoptions to adapters that usefetch.WebViewerAdapterignores it because it calls FileMaker scripts instead of making HTTP requests.
Related
-
Generate types with Typegen — configuring layouts in
adt.config.jsonand generating the client these methods live on. -
WebViewerAdapter — how these calls travel from your app to FileMaker and back.
-
Script Calls — calling FileMaker scripts directly with
fmFetch/callFMScript. -
Validation & Transformation — what happens when a record's
fieldDatafails schema validation.