pocketbase/pocketbase · error

[geoDistance] argument %d must be an identifier or number

Error message

[geoDistance] argument %d must be an identifier or number

What it means

Validation error from `geoDistance`: each of the 4 arguments must be a filter-expr identifier (column reference) or a number token. Text literals, booleans, or other token types are rejected before resolution, because coordinates must map to numeric SQL values.

Source

Thrown at tools/search/token_functions.go:36

	// distance between 2 points in kilometres (https://www.movable-type.co.uk/scripts/latlong.html).
	//
	// The accepted arguments at the moment could be either a plain number or a column identifier (including NULL).
	// If the column identifier cannot be resolved and converted to a numeric value, it resolves to NULL.
	//
	// Similar to the built-in SQLite functions, geoDistance doesn't apply
	// a "match-all" constraints in case there are multiple relation fields arguments.
	// Or in other words, if a collection has "orgs" multiple relation field pointing to "orgs" collection that has "office" as "geoPoint" field,
	// then the filter: `geoDistance(orgs.office.lon, orgs.office.lat, 1, 2) < 200`
	// will evaluate to true if for at-least-one of the "orgs.office" records the function result in a value satisfying the condition (aka. "result < 200").
	"geoDistance": func(argTokenResolverFunc func(fexpr.Token) (*ResolverResult, error), args ...fexpr.Token) (*ResolverResult, error) {
		if len(args) != 4 {
			return nil, fmt.Errorf("[geoDistance] expected 4 arguments, got %d", len(args))
		}

		resolvedArgs := make([]*ResolverResult, 4)
		for i, arg := range args {
			if arg.Type != fexpr.TokenIdentifier && arg.Type != fexpr.TokenNumber {
				return nil, fmt.Errorf("[geoDistance] argument %d must be an identifier or number", i)
			}
			resolved, err := argTokenResolverFunc(arg)
			if err != nil {
				return nil, fmt.Errorf("[geoDistance] failed to resolve argument %d: %w", i, err)
			}
			resolvedArgs[i] = resolved
		}

		lonA := resolvedArgs[0].Identifier
		latA := resolvedArgs[1].Identifier
		lonB := resolvedArgs[2].Identifier
		latB := resolvedArgs[3].Identifier

		return &ResolverResult{
			NullFallback: NullFallbackDisabled,
			Identifier: `(6371 * acos(` +
				`cos(radians(` + latA + `)) * cos(radians(` + latB + `)) * ` +
				`cos(radians(` + lonB + `) - radians(` + lonA + `)) + ` +

View on GitHub (pinned to 5d217ddb50)

Solutions

  1. Remove quotes around field identifiers: `office.lon`, not `'office.lon'`
  2. Express fixed coordinates as bare numbers, e.g. `26.0984`
  3. Use only direct column references or numeric literals for all 4 arguments

Example fix

// before
filter := "geoDistance('office.lon', 'office.lat', 26.1, 24.2) < 200"
// after
filter := "geoDistance(office.lon, office.lat, 26.1, 24.2) < 200"
Defensive patterns

Strategy: validation

Validate before calling

// ensure geoDistance args are bare identifiers or numbers
for _, a := range args {
    if !regexp.MustCompile(`^-?\d+(\.\d+)?$`).MatchString(a) && !isIdentifierToken(a) {
        return fmt.Errorf("geoDistance arg %q must be an identifier or number", a)
    }
}

Type guard

null

Try / catch

if err != nil && strings.Contains(err.Error(), "[geoDistance] argument") && strings.Contains(err.Error(), "must be an identifier or number") {
    return apiError400("geoDistance arguments must be unquoted field paths or numeric literals")
}

Prevention

When it happens

Trigger: Passing a quoted string as a coordinate, e.g. `geoDistance('office.lon', office.lat, 1, 2)`; passing a boolean or another function call as an argument.

Common situations: Quoting field paths out of habit from JSON usage; passing dynamic text values instead of numbers; confusion about identifier vs. literal syntax in the filter language.

Related errors


AI-assisted analysis of pocketbase/pocketbase@5d217ddb50 (2026-08-15). Data as JSON: /api/errors/e26f768ebfea0e25. Report an issue: GitHub.