> ## Documentation Index
> Fetch the complete documentation index at: https://archie.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Queries

> Read records from the auto-generated GraphQL schema. Single record, list, filtered, paginated, sorted, aggregated, grouped.

Every table in the [Data Model](/features/backend/data-model/overview) generates a set of queries on the GraphQL schema. This page covers the patterns you'll reach for most often.

The examples assume a `students` table with fields like `firstName`, `email`, `age`, `isActive`, `createdAt`, and a `city` relationship.

## Single record

Fetch one record by its primary key.

```graphql theme={null}
query {
  studentsById(id: "e25c4955-7ea7-4a83-a3de-d4c7427cc9fa") {
    id
    firstName
    email
  }
}
```

```json theme={null}
{
  "data": {
    "studentsById": {
      "id": "e25c4955-7ea7-4a83-a3de-d4c7427cc9fa",
      "firstName": "Robert",
      "email": "robert.williams@example.com"
    }
  }
}
```

Any field marked **Unique** in the Data Model can also be used as a query argument.

## Record list

The list query wraps results in a Connection type with two top-level fields: `items` (the array of records) and `count` (the number of records returned).

```graphql theme={null}
query {
  students {
    count
    items {
      id
      firstName
      email
    }
  }
}
```

## Filtered list

The `filter` argument takes a structured input that mirrors the field types on the table. Each field exposes a set of operators (`equals`, `gt`, `contains`, `starts_with`, etc.).

```graphql theme={null}
query {
  students(
    filter: {
      createdAt: { gt: "2025-12-01T00:00:00.000Z" }
      email: { contains: "williams" }
    }
  ) {
    items {
      id
      firstName
      email
    }
  }
}
```

For the full operator reference and the relational filter quantifiers (`some`, `every`, `none`), see [Standard operations](/features/backend/graphql-api-explorer/standard-operations).

### Combining conditions with AND / OR

Use the `AND` and `OR` keys to compose multiple conditions on the same query.

```graphql theme={null}
query {
  students(
    filter: {
      AND: {
        email: { contains: "johnson" }
        isActive: { equals: true }
      }
    }
  ) {
    items {
      id
      firstName
      email
      isActive
    }
  }
}
```

```graphql theme={null}
query {
  students(
    filter: {
      OR: {
        firstName: { equals: "Mary" }
        lastName: { equals: "Williams" }
      }
    }
  ) {
    items { id firstName lastName }
  }
}
```

## Paginated list

`first` controls the page size; `skip` controls the offset.

```graphql theme={null}
query {
  students(skip: 0, first: 3) {
    items {
      id
      firstName
      email
    }
  }
}
```

For cursor-stable pagination, use `pageInfo.hasNextPage` and `pageInfo.hasPreviousPage` on the Connection wrapper:

```graphql theme={null}
query {
  students(first: 20) {
    pageInfo { hasNextPage hasPreviousPage }
    items { id firstName }
  }
}
```

## Sorted list

Pass an array of field enums to `sort`. Append `_DESC` for descending; the bare enum sorts ascending.

```graphql theme={null}
query {
  students(sort: [CREATEDAT]) {
    items { id createdAt firstName }
  }
}
```

The object-style alternative is `orderBy`, which accepts `ASC` or `DESC` per field:

```graphql theme={null}
query {
  students(orderBy: { firstName: ASC }) {
    items { id firstName }
  }
}
```

For sorting by a computed aggregate value, use `aggregateSort` — see [Grouping and aggregation](#grouping-and-aggregation).

## Combining arguments

`filter`, `first`, `skip`, `sort`, and `orderBy` all stack on the same query.

```graphql theme={null}
query {
  students(
    filter: { isActive: { equals: true } }
    first: 3
    orderBy: { age: DESC }
  ) {
    items { id firstName age }
  }
}
```

## Combining queries

A single GraphQL request can run multiple top-level queries. They execute in parallel and return as one response.

```graphql theme={null}
query {
  students(filter: { firstName: { equals: "James" } }) {
    count
    items { id firstName }
  }

  citiesById(id: "e14638cb-6d72-4a36-b30f-9b763136a7bb") {
    id
    nameCity
  }
}
```

If you need the same query run twice with different arguments in one request, use **aliases**:

```graphql theme={null}
query {
  cityOne: citiesById(id: "e14638cb-6d72-4a36-b30f-9b763136a7bb") {
    nameCity
  }
  cityTwo: citiesById(id: "0174dc55-d494-4ebc-a0e9-13575461cad4") {
    nameCity
  }
}
```

## Aggregation

Every list query exposes a built-in `count` field on the Connection wrapper. No extra arguments needed.

```graphql theme={null}
query {
  students {
    count
    items { id firstName }
  }
}
```

For sums, averages, and other aggregate functions, see [Grouping and aggregation](#grouping-and-aggregation) below.

## Grouping and aggregation

Four arguments work together to compute analytics in a single query:

| Argument        | Purpose                                                          |
| --------------- | ---------------------------------------------------------------- |
| `groupBy`       | Fields to group by (one row per distinct combination of values). |
| `aggregateBy`   | Aggregate functions to compute on each group.                    |
| `having`        | Filter groups by aggregate results (SQL `HAVING` equivalent).    |
| `aggregateSort` | Sort groups by aggregate values.                                 |

### Group by a field

```graphql theme={null}
query {
  students(groupBy: [ISACTIVE]) {
    items { isActive }
    count
  }
}
```

The enum value is the camelCase field name converted to UPPERCASE: `isActive` → `ISACTIVE`, `paymentMethod` → `PAYMENTMETHOD`.

### Compute aggregates per group

`aggregateBy` takes a list of `{ function, field, alias }` entries. The result lands in the `aggregates` array, parallel to `items`.

```graphql theme={null}
query {
  students(
    groupBy: [ISACTIVE]
    aggregateBy: [
      { function: COUNT, alias: "totalStudents" }
      { function: AVG, field: AGE, alias: "avgAge" }
    ]
  ) {
    items { isActive }
    count
    aggregates
  }
}
```

```json theme={null}
{
  "data": {
    "students": {
      "items": [{ "isActive": true }, { "isActive": false }],
      "count": 2,
      "aggregates": [
        { "totalStudents": 5, "avgAge": 23.4 },
        { "totalStudents": 2, "avgAge": 21.0 }
      ]
    }
  }
}
```

| Function         | Field required?                   |
| ---------------- | --------------------------------- |
| `COUNT`          | No (counts all rows when omitted) |
| `SUM`            | Yes                               |
| `AVG`            | Yes                               |
| `MIN`            | Yes                               |
| `MAX`            | Yes                               |
| `COUNT_DISTINCT` | Yes                               |

### Filter groups with `having`

`having` references the alias defined in `aggregateBy`. Operators are `EQUALS`, `NOT_EQUALS`, `GREATER_THAN`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN`, `LESS_THAN_OR_EQUAL`.

```graphql theme={null}
query {
  students(
    groupBy: [CITYID]
    aggregateBy: [{ function: COUNT, alias: "studentCount" }]
    having: [{ alias: "studentCount", operator: GREATER_THAN, value: 3 }]
  ) {
    items { cityId }
    aggregates
  }
}
```

### Sort by aggregate

```graphql theme={null}
query {
  students(
    groupBy: [CITYID]
    aggregateBy: [{ function: COUNT, alias: "studentCount" }]
    aggregateSort: [{ alias: "studentCount", direction: DESC }]
    first: 3
  ) {
    items { cityId }
    aggregates
  }
}
```

### All four together

```graphql theme={null}
query {
  students(
    filter: { isActive: { equals: true } }
    groupBy: [CITYID]
    aggregateBy: [
      { function: COUNT, alias: "studentCount" }
      { function: AVG, field: AGE, alias: "avgAge" }
    ]
    having: [{ alias: "avgAge", operator: GREATER_THAN, value: 20 }]
    aggregateSort: [{ alias: "avgAge", direction: DESC }]
    first: 5
  ) {
    items { cityId }
    aggregates
  }
}
```

`filter` runs **before** grouping (SQL `WHERE`); `having` runs **after** (SQL `HAVING`).

## Permissions

Every query is filtered by the per-role permissions in [Role-Based Access](/features/backend/app-services/role-based-access). Records and fields a role isn't allowed to read are silently omitted from the response — they don't trigger errors.

## FAQ

<AccordionGroup>
  <Accordion title="Why is `count` separate from the array length?">
    `count` reflects the total number of matching records (subject to permissions), while `items` reflects only the current page. With `first: 10`, `items.length` is at most 10 but `count` may be much larger.
  </Accordion>

  <Accordion title="What's the difference between `sort` and `orderBy`?">
    They do the same thing in different syntaxes. `sort` takes an array of enum values (`[CREATEDAT, FIRSTNAME_DESC]`); `orderBy` takes an object (`{ createdAt: ASC, firstName: DESC }`). Pick whichever reads better in your query.
  </Accordion>

  <Accordion title="How do I filter by a related table's field?">
    Use the relational quantifiers — `some`, `every`, `none`. See [Standard operations](/features/backend/graphql-api-explorer/standard-operations#relational-quantifiers).
  </Accordion>

  <Accordion title="What's the largest page I can request?">
    The Explorer accepts large `first` values, but very large pages are slow. Use pagination with reasonable page sizes (20–100) and combine with sorting to keep results stable across requests.
  </Accordion>

  <Accordion title="Can I run a query against a non-default environment?">
    Yes — switch environments in the Backend Console, or call the environment-specific endpoint from outside the Explorer. See [Environments](/features/backend/environments/overview).
  </Accordion>
</AccordionGroup>
