# Query Syntax

**URL:** https://heroiclabs.com/docs/nakama/concepts/multiplayer/query-syntax/
**Summary:** Matchmaker properties and Match labels can be queried to return only the desired matches. Learn about the query syntax used to filter these results.
**Keywords:** matchmaker query, match listing, matches, query syntax
**Categories:** nakama, query-syntax, multiplayer

---


# Query Syntax

Queries are a powerful tool for enabling your players to find the most relevant opponents when using the [Matchmaker](../matchmaker/) and the most suitable multiplayer matches when [listing available matches](../match-listing/). A query expression defines the user's criteria for finding their opponents or available matches - think of it as a big `AND` statement - with the query then inspecting the Matchmaker `properties` or match `label`, respectively, to deliver the results.

{{< note "important" >}}
Querying the match `label` is only possible if the label is a JSON value.
{{< / note >}}

Queries are composed of one or more query terms using the syntax `field:value`. A query can contain any number of terms with each term separated by a space, for example `field:value field1:value1`.

The same syntax can be used for any value type, for example:

* Strings: `region:europe`
* Numbers: `level:10`
* Numeric Ranges: `rank:<=5`
* Date Ranges: `created:>"2021-12-25"`

See the [Operators](#operators) section below to learn more about writing query terms, and [Boosting](#boosting) for details about ordering results based on the weight given to individual query terms.

## Matching

The standard syntax matches the whole value, meaning there must be equality between the query value and returned result, not just similarity. For example, a query of `mode:free` would not return matches with a mode of `freeforall`.

Queries also inspect individual elements inside an array for a match. For example, an indexed value: `{"field": [5, 10, 15]}` will be returned as a match for any query including a term `field:5`, `field:10`, or `field:15`.

To search for multiple potential values inside an array make use of [regular expressions](#regular-expressions) in your query. See [matchmaking from user's groups](#matchmake-from-users-groups) for an example. 

## Operators

There are three available operators when creating your query terms: SHOULD, MUST, and MUST NOT.

### SHOULD

Should is the default operator used for query terms. In practice it means that the results _should have_ the given value, but it is not a strict requirement.

For example, if you are searching for opponents using a query of `region:europe` the results will include good results - meaning opponents in the Europe region - if available but will also include opponents from any other region if not enough good results can be found.

### MUST

The MUST operator, indicated by a `+` at the start of a query term, enforces a string requirement of the given value. Using the same example as above but with the MUST operator (`+region:europe`) means that only opponents from the Europe region will be returned in the result and, if not enough are available, no result is returned.

### MUST NOT

The MUST NOT operator is indicated by a `-` at the start of the query term. It enforces a string requirement for the _exclusion_ of the query term, i.e. any returned result will absolutely not have that value.

For example, if a user doesn't want to see any matches with a level above 10, the query term could look like `-level:>10`.

## Ranges

Queries can contain both numeric and date ranges as terms.

Numeric ranges can be used in query terms with the `>`, `>=`, `<`, and `<=` operators. These same operators can be used for date ranges with the distinction that the date be in quotes, for example `created:>"2022-01-01"` to return results created after January 1, 2022.

{{< note "important" >}}
For date range queries, we recommend using UTC seconds/milliseconds where possible (e.g. `+created:>="2023-01-01T09:00:00+00:00"`).
{{< / note >}}

## Regular expressions

Queries can use regular expressions as part of the query terms by wrapping the expression in forward slashes (`/`). To learn more about regular expressions, see the [regexr](https://regexr.com/) and [regex101](https://regex101.com/) websites.

For example, to find matches with a game `mode` of either `freeforall` or `capturetheflag` the following query can be used: `mode:/(freeforall|capturetheflag)/`. 

## Boosting

Using the operators detailed above you can craft queries with multiple terms to describe if a given result is returned or not returned. In addition, you can optionally define an ordering for the returned results by "boosting" the relative importance of the terms in your query.

This is done using `^` and an arbitrary booster number at the end of your query term(s).

For example, if using the query `mode:freeforall^2 mode:capturetheflag` to search for matches, your results will include both Free for All and Capture the Flag matches, but the Free for All results will be listed higher (i.e. be a "better" match) than the Capture the Flag results.

Note that since we didn't use the `+` operator this query may also return results that are neither Free for All or Capture the Flag matches, but those results will be lowest of all.

## Escaping

The following character set must be escaped for accurate query results: `+-=&|><!(){}[]^\"~*?:\\/ `.

Note that the space character is included. For example, if you want to query for a "Capture The Flag" match use `mode:Capture\ The\ Flag` as opposed to `mode:Capture The Flag`. The latter query may still be valid but will produce the wrong results as it is searching for a match with a `mode` of "Capture", and which contains "The" anywhere, and "Flag" anywhere.

## Examples

### Find a friend's match

If the match `label` contains an array `players` with the user IDs of all players currently in the match, we can use the following query to find any match a given friend is a part of:

{{< code type="server" >}}
```lua
local nk = require("nakama")

local query = "label.groups:<friend_user_id>"
local matches = nk.match_list(query)

for _, match in ipairs(matches) do
  nk.logger_info(string.format("Match id %s", match.match_id))
end
```
{{< / code >}}

{{< code type="server" >}}
```go
query := "+label.players:<friend_user_id>"
matches, err := nk.MatchList(ctx, query)

if err != nil {
    logger.WithField("err", err).Error("Match listings error.")
    return
}

for _, match := range matches {
    logger.Info("Match id %s", match.MatchId)
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
function findFriendMatch(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama) {
  const query = "+label.players:<friend_user_id>";
  var matches = nk.matchList(query);

  matches.forEach(function (match) {
    logger.info("Match id '%s'", match.matchId);
  });
}
```
{{< / code >}}

### Matchmake from user's groups

If the match `label` contains an array `groups` with the IDs of all groups the user is part of, we can use the following query to find matches only from those groups:

{{< code type="server" >}}
```lua
local nk = require("nakama")

local query = "label.groups:/(<groupID>|<groupID2>|<groupID3)/"
local matches = nk.match_list(query)

for _, match in ipairs(matches) do
  nk.logger_info(string.format("Match id %s", match.match_id))
end
```
{{< / code >}}

{{< code type="server" >}}
```go
query := "label.groups:/(<groupID>|<groupID2>|<groupID3)/"
matches, err := nk.MatchList(ctx, query)

if err != nil {
    logger.WithField("err", err).Error("Match listings error.")
    return
}

for _, match := range matches {
    logger.Info("Match id %s", match.MatchId)
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
function findGroupsMatch(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama) {
  const query = "label.groups:/(<groupID>|<groupID2>|<groupID3)/";
  var matches = nk.matchList(query);

  matches.forEach(function (match) {
    logger.info("Match id '%s'", match.matchId);
  });
}
```
{{< / code >}}

### Find an open match

In this example, the match `label` contains an `open` value to indicate whether or not the match is accepting new players. This value would be updated in the match handler once the appropriate criteria has been met (e.g. enough players have joined or indicated that they are ready to begin).

{{< note "important" "Boolean Queries" >}}
Note than when querying boolean values, 'true' is keyed to `T` and 'false' is keyed to `F`.
{{< / note >}}

{{< code type="server" >}}
```lua
local nk = require("nakama")

local query = "+label.open:T label.game_mode:deathmatch"
local matches = nk.match_list(query)

for _, match in ipairs(matches) do
  nk.logger_info(string.format("Match id %s", match.match_id))
end
```
{{< / code >}}

{{< code type="server" >}}
```go
query := "+label.open:T label.game_mode:deathmatch"
matches, err := nk.MatchList(ctx, query)

if err != nil {
    logger.WithField("err", err).Error("Match listings error.")
    return
}

for _, match := range matches {
    logger.Info("Match id %s", match.MatchId)
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
function findOpenMatches(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama) {
  const query = "+label.open:T label.game_mode:deathmatch";
  var matches = nk.matchList(query);

  matches.forEach(function (match) {
    logger.info("Match id '%s'", match.matchId);
  });
}
```
{{< / code >}}

### Matchmake around player level

In this example, we create a matchmaker ticket for a player with `Skill` level of 15 looking for 2-4 opponents with levels between 13 and 17:

{{< code type="client" >}}
```javascript
const query = "+properties.skill:>=13 +properties.skill:<=17";
const minCount = 2;
const maxCount = 4;

const numericProperties = {
  skill: 15
};

var ticket = await socket.addMatchmaker(query, minCount, maxCount, numericProperties);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var query = "+properties.skill:>=13 +properties.skill:<=17";

var numericProperties = new Dictionary<string, int>() {
    {"skill", 15}
};

var matchmakerTicket = await socket.AddMatchmakerAsync(query, 2, 4, numericProperties);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let query = "+properties.skill:>=13 +properties.skill:<=17"

let numericProperties: [String: Double] = ["skill": 15]

let matchmakerTicket = try await socket.addMatchmaker(query: query, minCount: 2, maxCount: 4, numericProperties: numericProperties)
```
{{< / code >}}

{{< code type="client" >}}
```dart
const query = '+properties.skill:>=13 +properties.skill:<=17';

const Map<String, double> numericProperties = {
  'skill': 15,
};

final ticket = await socket.addMatchmaker(
  query: query,
  minCount: 2,
  maxCount: 4,
  numericProperties: numericProperties,
  countMultiple: 5,
);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NMatchmakerTicket& ticket)
{
    std::cout << "Matchmaker ticket: " << ticket.ticket << std::endl;
};

int32_t minCount = 2;
int32_t maxCount = 4;
string query = "+properties.skill:>=13 +properties.skill:<=17";
NStringDoubleMap numericProperties;

numericProperties.emplace("skill", 15.0);

rtClient->addMatchmaker(minCount, maxCount, query, numericProperties, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String query = "+properties.skill:>=13 +properties.skill:<=17";
int minCount = 2;
int maxCount = 4;

Map<String, Double> numericProperties = new HashMap<String, Double>() {{
    put("skill", 15.0);
}};

MatchmakerTicket matchmakerTicket = socket.addMatchmaker(query, minCount, maxCount, numericProperties).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var query = "+properties.skill:>=13 +properties.skill:<=17"
var numeric_properties = { "skill": 15 }

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, 2, 4, numeric_properties),
    "completed"
)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var query = "+properties.skill:>=13 +properties.skill:<=17"
var numeric_properties = { "skill": 15 }

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = await socket.add_matchmaker_async(query, 2, 4, numeric_properties)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])
```
{{< / code >}}

{{< code type="client" lang="lua" framework="defold" >}}
```lua
local min_players = 2
local max_players = 4
local query = "+properties.skill:>=13 +properties.skill:<=17"
local ticket = socket.matchmaker_add(min_players, max_players, query)
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

### Boosting regional preference

In this example our player is located in the Europe region and prefers to matchmake with other players in the same region, followed by those in the Asia and Africa regions:

{{< code type="client" >}}
```javascript
const query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa";
const minCount = 2;
const maxCount = 4;

const stringProperties = {
  region: "europe"
};

var ticket = await socket.addMatchmaker(query, minCount, maxCount, stringProperties);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa";

var stringProperties = new Dictionary<string, string>() {
    {"region", "europe"}
};

var matchmakerTicket = await socket.AddMatchmakerAsync(query, 2, 4, stringProperties);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa"

let stringProperties = ["region": "europe"]

let matchmakerTicket = try await socket.addMatchmaker(query: query, minCount: 2, maxCount: 4, stringProperties: stringProperties)
```
{{< / code >}}

{{< code type="client" >}}
```dart
const query = 'properties.region:europe^3 properties.region:asia^2 properties.region:africa';

const stringProperties = {
  'region': 'europe',
};

final ticket = await socket.addMatchmaker(
  query,
  minCount: 2,
  maxCount: 4,
  stringProperties: stringProperties,
  countMultiple: 5,
);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NMatchmakerTicket& ticket)
{
    std::cout << "Matchmaker ticket: " << ticket.ticket << std::endl;
};

int32_t minCount = 2;
int32_t maxCount = 4;
string query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa";
NStringMap stringProperties;

stringProperties.emplace("region", "europe");

rtClient->addMatchmaker(minCount, maxCount, query, stringProperties, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa";
int minCount = 2;
int maxCount = 4;

Map<String, String> stringProperties = new HashMap<String, String>() {{
    put("region", "europe");
}};

MatchmakerTicket matchmakerTicket = socket.addMatchmaker(query, minCount, maxCount, stringProperties).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa"
var string_properties = { "region": "europe" }

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, 2, 4, string_properties),
    "completed"
)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa"
var string_properties = { "region": "europe" }

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = await socket.add_matchmaker_async(query, 2, 4, string_properties)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])
```
{{< / code >}}

{{< code type="client" lang="lua" framework="defold" >}}
```lua
local min_players = 2
local max_players = 4
local query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa"
local string_properties = { region = "europe" }
local ticket = socket.matchmaker_add(min_players, max_players, query, string_properties)
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}
