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:

Copy
{
  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).

  • recordId and modId — always returned as strings, even though you can pass either a string or number as recordId when calling get, update, or delete.

  • 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.

Copy
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.

Copy
const response = await ContactsLayout.get({ recordId: 42 });
const contact = response.data[0]?.fieldData;

find

Performs a FileMaker find.

Copy
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:

Copy
// 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, or null if 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).

Copy
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:

Copy
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/findAll also 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:

Copy
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

Copy
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

Copy
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

Copy
await ContactsLayout.delete({ recordId: 42 });

layoutMetadata

Returns the layout's field and portal metadata — the same information typegen itself reads to build your schema.

Copy
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:

Copy
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 for containerUpload. Other WebViewerAdapter methods ignore it.

  • fetch — passes RequestInit options to adapters that use fetch. WebViewerAdapter ignores it because it calls FileMaker scripts instead of making HTTP requests.