Symptom
A FormView with sharing.allowAnonymous: true and a section field { field: 'job', publicPicker: { displayFields: ['title', 'city'], filter: [{ field: 'status', operator: 'equals', value: 'published' }] } } renders on /_console/f/SLUG, but the picker's search endpoint fails for every query:
GET /api/v1/forms/apply/lookup/job?q=engineer
HTTP 400
{"code":"INVALID_FILTER","error":"Malformed $filter: [{\"field\":\"status\",\"operator\":\"equals\",\"value\":\"published\"},{\"field\":\"title\",\"operator\":\"contains\",\"value\":\"engineer\"}] is not a recognised filter shape. A filter array is a comparison [field, operator, value], a logical node [\"and\"|\"or\", ...conditions], or a list of those. ..."}
Measured on @objectstack/rest + @objectstack/cli 17.3.0 (objectstack dev --database-driver memory), anonymous curl, no cookie.
Where
packages/rest/src/rest-server.ts, registerFormEndpoints, the GET {basePath}/forms/:slug/lookup/:field handler:
const filters: any[] = [];
if (Array.isArray(picker.filter)) filters.push(...picker.filter); // ViewFilterRule objects {field, operator, value}
if (q) filters.push({ field: displayFields[0], operator: 'contains', value: q }); // same object shape
...
await p.findData({ object: referenceTo, query: { limit, offset: 0, filters, select, sort }, context });
query.filters reaches the ObjectQL filter parser as $filter, and the parser accepts only comparison TRIPLES [field, operator, value] / logical nodes. Control measurement on the same server through the authenticated data API:
GET /api/v1/data/ats_job?$filter=[{"field":"status","operator":"equals","value":"published"}] → 400 INVALID_FILTER (same message)
GET /api/v1/data/ats_job?$filter=[["status","=","published"]] → 200
So the failure is not the picker filter alone: the q branch builds the identical object shape, which means the route 400s for ANY non-empty search, with or without a declared publicPicker.filter. Only the degenerate q= + no-filter call (filters: []) can succeed.
Expected
Either the route lowers ViewFilterRule objects to the parser's triple grammar before calling findData (there is already a spec-side normaliser for view filter rules), or findData accepts the ViewFilterRule object dialect on query.filters. The FormFieldPublicPickerSchema.filter describe text promises "Same { field, operator, value } dialect as list-view filters", which is exactly what the parser refuses.
Related
Generated by Claude Code
Symptom
A
FormViewwithsharing.allowAnonymous: trueand a section field{ field: 'job', publicPicker: { displayFields: ['title', 'city'], filter: [{ field: 'status', operator: 'equals', value: 'published' }] } }renders on/_console/f/SLUG, but the picker's search endpoint fails for every query:Measured on
@objectstack/rest+@objectstack/cli17.3.0 (objectstack dev --database-driver memory), anonymouscurl, no cookie.Where
packages/rest/src/rest-server.ts,registerFormEndpoints, theGET {basePath}/forms/:slug/lookup/:fieldhandler:query.filtersreaches the ObjectQL filter parser as$filter, and the parser accepts only comparison TRIPLES[field, operator, value]/ logical nodes. Control measurement on the same server through the authenticated data API:GET /api/v1/data/ats_job?$filter=[{"field":"status","operator":"equals","value":"published"}]→ 400 INVALID_FILTER (same message)GET /api/v1/data/ats_job?$filter=[["status","=","published"]]→ 200So the failure is not the picker
filteralone: theqbranch builds the identical object shape, which means the route 400s for ANY non-empty search, with or without a declaredpublicPicker.filter. Only the degenerateq=+ no-filter call (filters: []) can succeed.Expected
Either the route lowers
ViewFilterRuleobjects to the parser's triple grammar before callingfindData(there is already a spec-side normaliser for view filter rules), orfindDataaccepts theViewFilterRuleobject dialect onquery.filters. TheFormFieldPublicPickerSchema.filterdescribe text promises "Same{ field, operator, value }dialect as list-view filters", which is exactly what the parser refuses.Related
publicPicker.objectanswers 500 against a canonical object schema #7486 (closed) — picker TARGET resolution; different defect, same route.publicPickeris enforced by the REST lookup route but declared nowhere inpackages/spec— no saved form can ever enable it #7467 (closed) —publicPickerspec declaration; the declaration now exists, this is its runtime.Generated by Claude Code