pocketbase/pocketbase · error
unsupported token type %q
Error message
unsupported token type %q
What it means
Returned by tools/search's resolveToken when the filter token's type is none of Text, Number, or Function — the only token types this resolver can turn into dbx parameters/identifiers. It formats token.Type with %q, so the message carries the numeric type constant. Like the unknown-operator guard, this indicates grammar/resolver skew rather than ordinary user input: fexpr produced a token type the search package cannot compile.
Source
Thrown at tools/search/filter.go:319
placeholder := "t" + security.PseudorandomString(8)
return &ResolverResult{
Identifier: "{:" + placeholder + "}",
Params: dbx.Params{placeholder: cast.ToFloat64(token.Literal)},
}, nil
case fexpr.TokenFunction:
fn, ok := TokenFunctions[token.Literal]
if !ok {
return nil, fmt.Errorf("unknown function %q", token.Literal)
}
args, _ := token.Meta.([]fexpr.Token)
return fn(func(argToken fexpr.Token) (*ResolverResult, error) {
return resolveToken(argToken, fieldResolver)
}, args...)
}
return nil, fmt.Errorf("unsupported token type %q", token.Type)
}
// Resolves = and != expressions in an attempt to minimize the COALESCE
// usage and to gracefully handle null vs empty string normalizations.
//
// The expression `a = "" OR a is null` tends to perform better than
// `COALESCE(a, "") = ""` since the direct match can be accomplished
// with a seek while the COALESCE will induce a table scan.
func resolveEqualExpr(equal bool, left, right *ResolverResult) dbx.Expression {
equalOp := "="
nullEqualOp := "IS"
concatOp := "OR"
nullExpr := "IS NULL"
if !equal {
// always use `IS NOT` instead of `!=` because direct non-equal comparisons
// to nullable column values that are actually NULL yields to NULL instead of TRUE, eg.:
// `'example' != nullableColumn` -> NULL even if nullableColumn row value is NULL
equalOp = "IS NOT"View on GitHub (pinned to 5d217ddb50)
Solutions
- Align the fexpr dependency version with the tools/search version (pin both; no replace directives).
- If maintaining a fork that adds token types, extend resolveToken with a case for each new type.
- Sanitize/limit user filter input to the documented syntax so unsupported constructs never reach the resolver.
Example fix
# before (go.mod) replace github.com/pocketbase/fexpr => github.com/myorg/fexpr v0.0.0-custom # after # drop the replace, run: go mod tidy
Defensive patterns
Strategy: validation
Validate before calling
// constrain user filter input to the documented grammar before parsing
var filterRe = regexp.MustCompile(`^[a-zA-Z0-9_.\s'"=\!~<>(),%-]+$`)
if !filterRe.MatchString(rawFilter) {
return errors.New("filter contains unsupported syntax")
} Try / catch
if err != nil && strings.Contains(err.Error(), "unsupported token type") {
// grammar/resolver version skew: verify fexpr dependency pinning; reject input
} Prevention
- Pin fexpr version; no forks/replace directives.
- Whitelist filter syntax at the API boundary.
- Add parser fuzz/property tests after grammar changes.
When it happens
Trigger: Effectively unreachable with the bundled fexpr, whose tokenizer only emits Text/Number/Function token types. It would surface with a custom or newer fexpr emitting new token kinds (e.g. raw regex or date tokens), or if Meta carried an unexpected type into a function path.
Common situations: Forked fexpr grammars; go.mod replace directives on fexpr; experimental branches adding token types ahead of resolver support.
Related errors
- unknown expression operator %q
- invalid left operand %q - %v
- invalid right operand %q - %v
- unknown function %q
- non-filterable field %q
AI-assisted analysis of pocketbase/pocketbase@5d217ddb50 (2026-08-15).
Data as JSON: /api/errors/396dc92619e61d71.
Report an issue: GitHub.