Praxsuite

Queries

Vincent Depassier · August 29, 2026

PraxQL — Query Syntax

A query body reads data from one primary table, with optional filtering, sorting, pagination and nested relations.


Minimal query

{
  "refs": {
    "Customers": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  },
  "query": {
    "from": "Customers"
  }
}

This returns every permitted column from Customers, up to the default limit.


from (required)

The primary table alias. It must exist as a key in refs.

"from": "Customers"

select (optional)

An array of column names to return. Omit it to get all permitted columns.

"select": ["Name", "Email", "Phone"]

Nested relation objects (joins) mix freely with plain column names:

"select": [
  "Name",
  "Email",
  { "table": "Orders", "select": ["Total", "Date"] }
]

See PraxQL — Relations for the full nested syntax.

When two joined tables share a column name, prefix it with the alias:

"select": ["Customers.Name", "Orders.Name"]

where (optional)

An array of filter conditions. Conditions at the same level are AND'd together by default.

"where": [
  { "field": "Status", "op": "eq", "value": "Active" },
  { "field": "Region", "op": "eq", "value": "EU" }
]

That produces WHERE Status = 'Active' AND Region = 'EU'.

Each condition has three fields:

Field

Type

Description

field

string

Column name

op

string

Operator — see PraxQL — Operators

value

any

The comparison value: string, number, boolean, null or array

For OR logic use the or key; for explicit AND grouping use and. Both are covered in PraxQL — Operators.


orderBy (optional)

An array of sort specifications, applied in order — the first item is the primary sort.

"orderBy": [
  { "field": "LastName", "dir": "asc" },
  { "field": "CreatedDate", "dir": "desc" }
]

Field

Values

field

Column name

dir

"asc" or "desc"

The column needs CanSort = true in the credential's column scope. A credential with no column scopes defined can sort by anything.


limit and offset (optional)

"limit": 25,
"offset": 50

That returns rows 51–75, the third page of 25.

Value

Default when limit is omitted

50

Default cap on a newly created table scope

200 (MaxLimitOverride)

Absolute maximum, which nothing raises

1,000

The effective ceiling is the lower of the scope's cap and the absolute maximum — so out of the box that is 200, not 1,000, and somebody has to raise the scope before a bigger page is possible.

Asking for more than the ceiling does not fail: it silently gives you the ceiling. Always read meta.limit in the response rather than assuming you got what you asked for.


Pagination

Page 1:

{
  "refs": { "Orders": "..." },
  "query": {
    "from": "Orders",
    "orderBy": [{ "field": "CreatedDate", "dir": "desc" }],
    "limit": 50,
    "offset": 0
  },
  "includeTotalCount": true
}

The response carries meta.total, which tells you how many pages exist.

Page 2 is the same body with "offset": 50. Only ask for includeTotalCount on the first page — every later page would pay for a second count that returns a number you already have.

Always send an orderBy when paginating. Without one, nothing guarantees row order between requests, so a row can appear on two pages or on none.


groupBy and having

For totals, averages and counts per group, use groupBy with aggregate functions in select, and optionally having to filter the groups. See PraxQL — Aggregations.


Complete example

{
  "refs": {
    "Customers": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  },
  "query": {
    "from": "Customers",
    "select": ["Name", "Email", "Status", "Region"],
    "where": [
      { "field": "Status", "op": "eq", "value": "Active" },
      {
        "or": [
          { "field": "Region", "op": "eq", "value": "EU" },
          { "field": "Region", "op": "eq", "value": "LATAM" }
        ]
      }
    ],
    "orderBy": [{ "field": "Name", "dir": "asc" }],
    "limit": 50,
    "offset": 0
  }
}

Active customers in EU or LATAM, sorted by name, fifty at a time.