status: accepted date: 2026-03-12

ADR-0013: Compound Multi-Dimensional Filtering as Core Feature

Context and Problem Statement

The central value proposition of the OpenTabletop API is enabling consumers to answer questions like “What are the best worker-placement games for exactly 3 players that play in under 90 minutes with medium-heavy weight?” This requires composable multi-dimensional filtering across six dimensions simultaneously. The filtering system is not just a feature – it is THE core feature that differentiates this API from existing board game data sources.

Decision Drivers

  • Filtering must compose across all six dimensions: player count, playtime, weight, mechanics/type, themes, and metadata
  • Expansion-aware filtering (effective=true) must resolve combined properties per ADR-0007
  • Simple use cases (single filter) must remain simple; complex queries should be possible but not required
  • The query interface must be expressible both as GET query parameters and as structured POST bodies

Considered Options

  • Simple parameter-based filters (player_count=3&max_playtime=90)
  • Custom query language (DSL parsed from a query string)
  • Compound composable filters with GET for simple queries and POST /games/search for complex queries

Decision Outcome

Chosen option: “Compound composable filters with dual GET/POST interface”, because it keeps simple queries simple while enabling arbitrarily complex multi-dimensional filtering. Simple filters use GET query parameters on /games (e.g., ?min_players=3&max_playtime=90&mechanic=worker-placement). Complex queries that combine multiple values per dimension, boolean logic, or expansion-aware resolution use POST /games/search with a structured JSON body. The effective=true parameter triggers expansion-aware property resolution per ADR-0007’s three-tier model. A custom DSL was rejected because it requires clients to learn a proprietary query syntax. Simple parameters alone were rejected because they cannot express compound conditions within a single dimension (e.g., “mechanic is worker-placement AND area-control”).

Consequences

  • Good, because simple filtering via GET parameters requires zero learning curve
  • Good, because POST /games/search enables arbitrarily complex queries with a structured, validatable JSON body
  • Good, because expansion-aware filtering via effective=true is a unique differentiator for this API
  • Bad, because two query interfaces (GET and POST) mean two code paths to maintain and document
  • Bad, because expansion-aware filtering requires additional query complexity and may be slower than base-only filtering