> ## 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.

# Standard operations

> Filters, relationship traversal, nested mutations, and the standard patterns shared across every query and mutation in the schema.

Most queries and mutations on the GraphQL API share a common shape — the same filter operators, the same Connection wrapper around lists, the same nested-mutation conventions. This page is the reference for those shared patterns.

## The Connection wrapper

Every list field in the schema (root-level lists and nested relationship lists) returns a Connection wrapper:

| Field                      | Type       | Description                                           |
| -------------------------- | ---------- | ----------------------------------------------------- |
| `items`                    | `[T!]!`    | The records on the current page.                      |
| `count`                    | `Int!`     | Total matching records — useful for pagination UIs.   |
| `pageInfo.hasNextPage`     | `Boolean!` | Whether more records exist after this page.           |
| `pageInfo.hasPreviousPage` | `Boolean!` | Whether records exist before this page.               |
| `aggregates`               | `JSON`     | Computed aggregate values when `aggregateBy` is used. |

Because the wrapper is consistent at every depth, you can pass `filter`, `first`, `skip`, and `orderBy` to a nested list the same way you pass them at the root.

```graphql theme={null}
query {
  categoriesList {
    items {
      name
      productList(
        filter: { isFeatured: { equals: true } }
        first: 3
        orderBy: { stock: DESC }
      ) {
        count
        pageInfo { hasNextPage }
        items { id name stock }
      }
    }
  }
}
```

## Filter operators

### Comparison and text

Most fields support a standard set of operators. The exact set depends on the field type — text fields get string operators, numeric fields get range operators, and so on.

| Operator       | Description           | Example                                  |
| -------------- | --------------------- | ---------------------------------------- |
| `equals`       | Exact match           | `{ status: { equals: "active" } }`       |
| `not_equals`   | Negated match         | `{ status: { not_equals: "archived" } }` |
| `gt`           | Greater than          | `{ age: { gt: 18 } }`                    |
| `gte`          | Greater than or equal | `{ age: { gte: 18 } }`                   |
| `lt`           | Less than             | `{ price: { lt: 100 } }`                 |
| `lte`          | Less than or equal    | `{ price: { lte: 100 } }`                |
| `contains`     | Substring match       | `{ name: { contains: "phone" } }`        |
| `starts_with`  | Prefix match          | `{ sku: { starts_with: "PROD-" } }`      |
| `is_empty`     | NULL or empty         | `{ phoneNumber: { is_empty: true } }`    |
| `is_not_empty` | Has any value         | `{ bio: { is_not_empty: true } }`        |

### Logical composition

`AND`, `OR`, and `NOT` compose any number of clauses. They can nest.

```graphql theme={null}
query {
  usersList(filter: {
    NOT: { status: { equals: "BANNED" } }
  }) {
    items { id email }
  }
}
```

```graphql theme={null}
query {
  ordersList(filter: {
    OR: [
      { status: { equals: "completed" } }
      {
        AND: [
          { status: { equals: "pending" } }
          { createdAt: { last_days: 7 } }
        ]
      }
    ]
  }) { items { id status } }
}
```

### Date helpers

Relative date filters save you from computing exact timestamps.

| Operator     | Example                               |
| ------------ | ------------------------------------- |
| `today`      | `{ createdAt: { today: true } }`      |
| `this_week`  | `{ createdAt: { this_week: true } }`  |
| `this_month` | `{ createdAt: { this_month: true } }` |
| `last_days`  | `{ createdAt: { last_days: 30 } }`    |

```graphql theme={null}
query {
  logsList(filter: { timestamp: { last_days: 7 } }) {
    items { message }
  }
}
```

### Relational quantifiers

To filter parent records by the state of their related children, use `some`, `every`, and `none`:

| Quantifier | Matches when...                                                              |
| ---------- | ---------------------------------------------------------------------------- |
| `some`     | At least one child matches the inner filter.                                 |
| `every`    | All children match the inner filter (also matches parents with no children). |
| `none`     | No children match the inner filter.                                          |

```graphql theme={null}
query UsersWithHighRatedPosts {
  usersList(filter: {
    posts: { some: { rating: { gt: 5 } } }
  }) {
    items { id email }
  }
}
```

```graphql theme={null}
query UsersWithOnlyPublishedPosts {
  usersList(filter: {
    posts: { every: { published: { equals: true } } }
  }) {
    items { id }
  }
}
```

```graphql theme={null}
query UsersWithNoDrafts {
  usersList(filter: {
    posts: { none: { status: { equals: "DRAFT" } } }
  }) {
    items { id }
  }
}
```

## Nested mutations

Every relationship field on a mutation input accepts exactly one of three operations:

| Operation         | What it does                                  |
| ----------------- | --------------------------------------------- |
| `create`          | Insert a new related record inline.           |
| `connect`         | Link to an existing record by a unique field. |
| `connectOrCreate` | Find by a unique field; create if missing.    |

The whole nested mutation runs as a single transaction. If any step fails, the entire operation rolls back.

```graphql theme={null}
mutation {
  createDeal(input: {
    title: "Enterprise License"
    amount: 50000

    contact: { create: { firstName: "Juan", email: "juan@acme.com" } }
    user: { connect: { id: "user-uuid-123" } }
    organization: {
      connectOrCreate: {
        where: { domain: "acme.com" }
        create: { name: "Acme Corp", domain: "acme.com" }
      }
    }
  }) {
    id
    contact { id }
    organization { id }
  }
}
```

For the full mutations reference, see [Mutations](/features/backend/graphql-api-explorer/mutations).

## Atomic upserts

Tables with at least one unique column expose an `upsert<TableName>` mutation. It atomically checks the unique field and runs `create` or `update`:

```graphql theme={null}
mutation {
  upsertContact(
    where: { email: "juan@example.com" }
    create: {
      firstName: "Juan"
      email: "juan@example.com"
      status: "LEAD"
    }
    update: {
      status: "ACTIVE"
    }
  ) {
    id status
  }
}
```

Use upsert in place of "select then create or update" to eliminate the race condition.

## Modifying relationships

The `updateRelationship` mutation changes how a foreign key behaves — cascade behavior, nullability, descriptive metadata. Names map to the underlying database constraint name.

```graphql theme={null}
mutation EnableCascade {
  updateRelationship(
    tableName: "organization_user"
    relationship: {
      name: "organization_user_users_id_fk"
      onDelete: "CASCADE"
      description: "Reference to the user belonging to the organization"
    }
  ) {
    name
    relationships { name onDelete fromColumn }
  }
}
```

| Property      | Accepts                                        | What it does                                                                 |
| ------------- | ---------------------------------------------- | ---------------------------------------------------------------------------- |
| `onDelete`    | `CASCADE`, `SET NULL`, `RESTRICT`, `NO ACTION` | What happens to child rows when the parent is deleted.                       |
| `onUpdate`    | Same set                                       | What happens to child rows when the parent's key changes.                    |
| `isNullable`  | `true` / `false`                               | Whether the relationship column can be null. Required `true` for `SET NULL`. |
| `description` | String                                         | Documentation for the relationship.                                          |

For modeling cardinality and many-to-many patterns at the schema level, see [Relationships](/features/backend/data-model/relationships).

## Permissions and authentication

Filters, nested mutations, and upserts all run through the same authorization checks. Read access is required for filter clauses that touch a related table; write access is required for every step of a nested mutation. See [Role-Based Access](/features/backend/app-services/role-based-access). Authentication tokens come from [App Services → Authentication Providers](/features/backend/app-services/authentication-providers/overview).

## FAQ

<AccordionGroup>
  <Accordion title="When should I use `every` vs `some`?">
    `some` is "at least one child matches" — finds parents that have any matching child. `every` is stricter — all children must match. Note that `every` also matches parents with **zero** children, which is sometimes surprising.
  </Accordion>

  <Accordion title="What's the difference between `connect` and passing a foreign key id directly?">
    `connect` is the schema-level way to link by a unique field — including non-id columns like an email. Passing a raw foreign key id is shorter, but only works when you already have the id and the column is the primary key.
  </Accordion>

  <Accordion title="Are nested mutations slower than separate operations?">
    Faster, in practice. They run in one round-trip and one database transaction, where separate operations require a round-trip per step and don't share a transaction. They also avoid orphaned records when a later step fails.
  </Accordion>

  <Accordion title="Can I combine `having` with `filter`?">
    Yes. `filter` runs before grouping (SQL `WHERE`); `having` runs after (SQL `HAVING`). Use `filter` to scope the rows that participate in the aggregation, and `having` to drop groups whose aggregate doesn't meet a threshold. See [Queries → Grouping and aggregation](/features/backend/graphql-api-explorer/queries#grouping-and-aggregation).
  </Accordion>

  <Accordion title="What's the maximum nesting depth on filters?">
    Deep nesting works for typical use cases. For very deep filter trees, consider the `_query` POST endpoint on the [REST API](/features/backend/rest-api-explorer/queries) — it accepts a JSON body that's easier to compose in code than a deeply nested GraphQL filter literal.
  </Accordion>
</AccordionGroup>
