Sometimes a Page API needs input from the caller that isn't a filter on the source table itself. This is a pattern for passing typed input parameters to a Page API on a plain GET request, using FlowFilter fields, no bound actions or workarounds required.

FlowFilters as input parameters

FlowFilter fields are a specialized field class that are never stored in the database, they exist only in memory for the duration of the current context. That characteristic makes them semantically right for exactly this job: a value the caller provides, used to shape the response, persisted nowhere.

The problem

The use case came from Visual Tracking: a Page API that returns Documents filtered by their related Tags, not by any direct property of the Document table. A Page API queries at the root table level, so passing in that extra context from the outside needs a creative route.

Create the table structures

Two tables carry the pattern: a Tag table that stores relationships between documents and business entities, and a Document table that gets two FlowFilter fields, one for Tag Type and one for Tag No.

Expose the FlowFilters on the API page

The Page API, "Document By Tag", exposes the two FlowFilter fields as layout fields. That is all it takes for them to become usable as query parameters in the OData request.

Execute the filters in OnOpenPage

The OnOpenPage trigger reads the incoming filter parameters, validates them, and translates them into a filter on the page source:

trigger OnOpenPage()
var
    Tag: Record "VTS Tag";
    DocumentFilter: Text;
begin
    if Rec.GetFilter("Tag Type Filter") = '' then
        Error('Tag Type Filter must be provided.');

    if Rec.GetFilter("Tag No. Filter") = '' then
        Error('Tag No. Filter must be provided.');

    Tag.SetRange(Type, Rec.GetRangeMin("Tag Type Filter"));
    Tag.SetRange("No.", Rec.GetRangeMin("Tag No. Filter"));

    if Tag.FindSet() then
        repeat
            if DocumentFilter <> '' then
                DocumentFilter += '|';
            DocumentFilter += Tag."Document No.";
        until Tag.Next() = 0;

    if DocumentFilter <> '' then
        Rec.SetFilter("File No.", DocumentFilter)
    else
        Rec.SetRange("File No.", '');
end;

GetFilter validates that the caller actually provided each parameter, GetRangeMin extracts the value, and the loop builds a pipe-delimited filter of matching document numbers that is applied to the page source.

Construct the OData request

On the calling side, the parameters travel as ordinary OData $filter conditions:

$filter: `tagType eq 'Item' and tagNo eq '${item.no}'`

Results and limitations

The API returns only the Documents linked to the requested Tag, exactly as intended. The limitation: the pattern is clean only for single-value equality filters. If a caller needs several values of the same type, you have to parse the raw filter string yourself to handle the pipe-delimited case.

Conclusion

This is an undocumented pattern, but it leans on a Business Central primitive doing exactly what it was designed for: typed, in-memory input on the current context. It gives Page APIs real input parameters on GET requests, without bound actions and without abusing real table fields.