# Conditions and GraphQL operations

> Add conditions to any Cloak Proxy rule so it matches only requests with a certain header, query parameter, JSON body value or GraphQL operation, and see GraphQL operations and errors in the traffic list, filters, sidebar, Insights and Alerts.

Source: https://docs.subcloak.com/cloak-proxy/tools/conditions-and-graphql/

Added in Cloak Proxy 0.4.0.

(added in Cloak Proxy 0.4.0)

A rule's URL pattern can't tell apart requests that share a URL. GraphQL sends every operation to one endpoint, often `/graphql`, and many other APIs say what a request does in a header or a body field. **Conditions** fix that: every rule, in every tool, can also require a header, a query parameter, a value in the JSON body, some text in the body, or a GraphQL operation name or type. "Return a declined card only for the `CreateOrder` mutation from ShopApp 4.x" is one Map Local rule.

Cloak Proxy also understands GraphQL traffic on its own: the traffic list names each operation, a `200` response whose body carries an `errors` array is shown as a failure, the sidebar groups requests by operation, and Insights gives each operation its own endpoint row.

*[Screenshot: A Map Local rule named Card declined on checkout for POST https://api.shopapp.example/graphql, limited to ShopApp, with three conditions: GraphQL operation is CreateOrder, JSON body $.variables.input.payment is card, and Header X-App-Version matches regex ^4\. Below them: Matches 1 of 54 captured requests, with a Show Request button. A Map Local rule for one GraphQL mutation, narrowed by a JSON body value and a header. The tester counts the captured requests it matches.]*

## Add a condition

1. Open the rule in the Tools window (**Tools › Tools…**, or right-click a request and choose **Tools**).

2. Under the URL pattern and **Only these apps**, click **Add Condition** and choose what to check: **Request Header**, **Query Parameter**, **JSON Body Value (JSONPath)**, **Body Text**, **GraphQL Operation Name** or **GraphQL Operation Type**.

3. Fill in the row: the header, parameter or JSONPath (when the condition needs one), the comparison, and the value.

4. Read the line under the conditions. **Matches N of M captured requests** counts the requests already captured that pass the URL, method, app and every condition. Click **Show Requests** to select them in the traffic list. When none match, it names the condition that stopped the most of them, such as **stopped by operation is CreateOrder**.

A rule with conditions matches a request only when its URL, method and app match **and every enabled condition passes**. Conditions never widen a rule: they only narrow what the URL already matches. A rule with no conditions works exactly as before.

In the rule list, a rule with conditions shows the first one under its URL, for example **operation is CreateOrder + 2 more**.

## Kinds of condition

| Condition | Name field | What it checks |
|---|---|---|
| **Header** | The header name, for example `X-Feature` | Every value of that request header. Header names ignore case. |
| **Query parameter** | The parameter name, for example `page` | Every value of that parameter in the URL. A repeated parameter (`?page=2&page=3`) passes if any value does. |
| **JSON body** | A JSONPath, for example `$.variables.input.id` | Every value the path selects in the request's JSON body. See [JSON body values](#json-body-values). |
| **Body text** | None | The request body as text, decoded from gzip, deflate or Brotli, up to 2 MB. |
| **GraphQL operation** | None | The name of the GraphQL operation the request runs, such as `CreateOrder`. A batch passes if any of its operations does. |
| **GraphQL type** | None | **query**, **mutation** or **subscription**. |

## Comparisons

| Comparison | Passes when |
|---|---|
| **is** | A value equals the text. |
| **is not** | No value equals the text. A header or field that isn't there passes. |
| **contains** | A value contains the text. |
| **doesn't contain** | No value contains the text. |
| **matches regex** | A value matches the regular expression (not anchored; add `^` and `$` to anchor it). |
| **exists** | The header, parameter, JSON value or operation name is there, even if it's empty. |
| **is missing** | It isn't there. |

All comparisons ignore case, including regular expressions. A condition's checkbox turns it off without deleting it; turned-off conditions are skipped.

When a condition can't work as written, a warning shows under it: **Enter a header name**, **That isn't a valid JSONPath**, **That isn't a valid regular expression**, or **The type is query, mutation or subscription**. Until you fix it, an invalid regular expression matches nothing.

## JSON body values

The **JSON body** condition reads the request body as JSON (after removing any `Content-Encoding`) and evaluates a JSONPath on it. `$.` at the start is optional: `variables.id` means `$.variables.id`.

| JSONPath | Selects |
|---|---|
| `$.variables.input.total` | One field. |
| `$.items[0].sku` | The first item's `sku`. |
| `$.items[*].sku` | Every item's `sku`; **is** passes if any of them equals the value. |
| `$..price` | Every `price` field at any depth. |

Strings are compared without their quotes; numbers, `true`, `false` and `null` are compared as written (`0`, `true`). Objects and arrays are compared as compact JSON, so **contains** `"sku":"A1"` works on an array of items. A body that isn't JSON, or is larger than 2 MB, has no values: **exists** fails and **is missing** passes.

## GraphQL operations

Cloak Proxy finds the operation in every common way GraphQL is sent over HTTP:

| Request | Example |
|---|---|
| `POST` with a JSON body | `{"query": "mutation CreateOrder(…) {…}", "operationName": "CreateOrder", "variables": {…}}` |
| A batch | A JSON array of those objects; each entry is an operation. |
| `GET` with query parameters | `/graphql?query=query%20GetFeed…&operationName=GetFeed` |
| `application/graphql` | The document as the body. |
| Persisted queries | `{"operationName": "GetCart", "extensions": {"persistedQuery": {…}}}`, with no document. |

The name comes from `operationName`, or from the document. The type comes from the document itself, so a document with several operations, fragments or comments before the operation is read correctly. Persisted queries carry no document, so their type is a guess from the name (`createOrder`, `updateProfile` and similar are mutations). A body like `{"query": "red shoes"}` from a search box isn't treated as GraphQL.

### Make a rule for one operation

Right-click a GraphQL request and choose **Tools** and a tool. The new rule matches the request's URL and has a **GraphQL operation is …** condition, and it's named after the operation, for example **mutation CreateOrder**. The same happens for **Break on Next Request**, from the MCP server's `rule_from_request`, and from **Create Rule for This Operation** on an operation in the sidebar.

[Mock Sets](https://docs.subcloak.com/cloak-proxy/tools/mock-sets/) record the operation with each GraphQL response, so a mock set answers `GetUser` with the `GetUser` recording and `GetCart` with the `GetCart` one, even though both went to `/graphql`. An operation that wasn't recorded gets the set's offline 404. Recordings made before 0.4.0 have no operation and still answer anything at their URL.

## GraphQL errors in a 200 response

GraphQL servers usually answer `200 OK` even when an operation fails, and put the failure in an `errors` array in the body. Cloak Proxy treats a GraphQL request whose 2xx response has a non-empty top-level `errors` array as a failure:

*[Screenshot: The traffic list with GraphQL requests to api.shopapp.example, each with a pink operation badge. The selected mutation CreateOrder row shows a red 200 ✕ status. The response pane starts with a red banner: 1 GraphQL error, PAYMENT_DECLINED: Your card was declined.. A 200 with GraphQL errors shows a red status and dot, and the response explains why.]*

| Where | What you see |
|---|---|
| Traffic list | The status pill shows the code with a cross, such as **200 ✕**, in red, and the status dot is red. Hover for the first error. |
| Inspector | A red banner above the response: the number of errors, the first error's code and message, and **Details** for every error with its path. **(partial data)** means some fields came back as well. |
| Filter bar | A **GraphQL Errors** tab, with a count, appears once a response has had errors. It adds a **GraphQL equals errors** condition to [Filters](https://docs.subcloak.com/cloak-proxy/inspect/filtering/). |
| Sidebar | The operation's row shows how many of its requests had errors, such as **1 ✕**. |
| Insights | The endpoint's **Errors** column counts them, and its details list **GraphQL errors × N**. |
| [Alerts](https://docs.subcloak.com/cloak-proxy/tools/alerts/) | The new **GraphQL errors (in a 2xx response)** condition, and **Any error (4xx, 5xx or failure)** now includes them. |

*[Screenshot: The response pane's Body tab with a red banner reading 1 GraphQL error, PAYMENT_DECLINED: Your card was declined, above a JSON tree with data.createOrder null and the errors array. The banner above a response that carried GraphQL errors.]*

A `4xx` or `5xx` response is already a failure and isn't counted twice. Responses from REST APIs that happen to use an `errors` key aren't flagged: only requests Cloak Proxy recognises as GraphQL are.

## The GraphQL sidebar group

Once Cloak Proxy has seen a GraphQL request, the [sidebar](https://docs.subcloak.com/cloak-proxy/inspect/sidebar/) has a **GraphQL** group listing every operation by name, with a **Q**, **M** or **S** badge for its type, how many requests ran it, and how many came back with errors. Click one to list only its requests, whichever host or URL they went to. Right-click it to create a rule for the operation, pin it, or copy its name.

*[Screenshot: The sidebar's GraphQL group with operations AddToCart, CreateOrder (selected, 1 ✕), GetCart, GetUser, GetViewer, SearchProducts and UpdateProfile (1 ✕), each with a Q or M badge and a count. Each operation, its type, its request count and its errors.]*

## In the traffic list, filters and Insights

- **GraphQL Operation column.** Right-click the list's header to show it. It shows the operation's type and name (red when the response had errors), and sorts by name.
- **GraphQL filter field.** In [Filters](https://docs.subcloak.com/cloak-proxy/inspect/filtering/) and the quick filter's **Match in** menu, **GraphQL** searches the operation (`mutation CreateOrder`, `CreateOrder`, `mutation`) and, for responses with errors, the word `errors`, each error's code and its message.
- **Insights › Endpoints** has one row per operation: `POST api.shopapp.example /graphql` with a **mutation CreateOrder** badge, its own call count, error rate and timings. Schema change detection compares each operation's responses with that operation's earlier responses.

*[Screenshot: Insights Endpoints: six rows for /graphql, one per operation (mutation CreateOrder with 1 error of 2 calls, query GetCart, query GetViewer, mutation AddToCart, mutation UpdateProfile with 1 error, query SearchProducts), then REST endpoints. Insights splits /graphql into one endpoint per operation.]*

## Which tools use conditions

Every tool with a matcher: Breakpoint, Map Local, Map Remote, Rewrite, Scripting, Block List, Allow List, Network Conditions and Alerts. Mock Sets match each recording's GraphQL operation as described above. DNS Spoofing and No Caching have no matcher.

Conditions look at the request at the same point the rule's URL does (see [Which URL each step sees](https://docs.subcloak.com/cloak-proxy/tools/rules-and-matching/#which-url-each-step-sees)): Block List and Rewrite see the request as the app sent it, Map Local sees it after Rewrite, scripts and the request breakpoint, and so on. Response-stage rules match the request as the app sent it. Alerts match the recorded request.

The body is decoded and parsed at most once per request, and only when a rule whose URL already matched has a condition that needs it, so conditions cost nothing on requests they don't apply to.

## Use conditions from an AI assistant or the command line

Every rule's `matcher` takes a `conditions` list in exported rules, the [control API](https://docs.subcloak.com/cloak-proxy/automation/control-api/) and the [MCP server](https://docs.subcloak.com/cloak-proxy/automation/mcp-server/)'s `upsert_rule`:

```json title="A Map Local rule for one GraphQL mutation"
{
  "name": "Card declined on checkout",
  "matcher": {
    "url": "https://api.shopapp.example/graphql",
    "method": "POST",
    "conditions": [
      { "field": "graphQLOperation", "value": "CreateOrder" },
      { "field": "jsonPath", "name": "$.variables.input.payment", "operator": "equals", "value": "card" },
      { "field": "header", "name": "X-App-Version", "operator": "matches", "value": "^4\\." }
    ]
  },
  "statusCode": 200,
  "body": "{\"data\":{\"createOrder\":null},\"errors\":[{\"message\":\"Your card was declined.\"}]}"
}
```

| Key | Values |
|---|---|
| `field` | `header`, `query`, `jsonPath`, `body`, `graphQLOperation`, `graphQLType` |
| `name` | The header, parameter or JSONPath. Not used by the other fields. |
| `operator` | `equals`, `notEquals`, `contains`, `notContains`, `matches`, `exists`, `missing`. Defaults to `equals`, or `exists` when there's no `value`. |
| `value` | The text to compare. |
| `isEnabled` | `false` turns the condition off. Defaults to `true`. |

Sending `conditions` replaces the rule's whole list. An unknown `field` or `operator` is refused with the allowed values.

To find GraphQL traffic:

- MCP `search_requests` takes `graphql_operation` (a name, part of one, or a type) and `graphql_errors: true`, with or without `text`. Each result names its operation (`graphql`) and errors (`graphqlErrors`).
- MCP `list_requests` takes `graphql_operation`, and `errors_only` includes GraphQL errors. Its lines show the operation in braces: `#52 200 POST [ShopApp] https://api.shopapp.example/graphql {mutation CreateOrder} 410ms GraphQL errors: PAYMENT_DECLINED: Your card was declined.`
- `cloakctl list --graphql CreateOrder` does the same from the command line, and `cloakctl list --errors` includes GraphQL errors.

## Limits

- Conditions read the request only. To act on the response, use [Rewrite](https://docs.subcloak.com/cloak-proxy/tools/rewrite/) response actions, a [script](https://docs.subcloak.com/cloak-proxy/tools/scripting/) or an [Alert](https://docs.subcloak.com/cloak-proxy/tools/alerts/) trigger.
- Bodies over 2 MB aren't parsed for JSON values, GraphQL operations or GraphQL errors. **Body text** reads the first 2 MB.
- **Test a URL** checks the URL and method only. When a rule has conditions and the URL matches, it says **URL matches**; the captured-request count above it checks the conditions.
- GraphQL subscriptions over WebSocket aren't read; only GraphQL sent as HTTP requests is.

## Related

  - [Rules and matching](https://docs.subcloak.com/cloak-proxy/tools/rules-and-matching/): URL patterns, app limits and the order rules run in.
  - [WebSocket and GraphQL](https://docs.subcloak.com/cloak-proxy/inspect/websocket-and-graphql/): Read GraphQL queries and variables in the inspector.
  - [Map Local](https://docs.subcloak.com/cloak-proxy/tools/map-local/): Answer a request with a response you write.
  - [Alerts](https://docs.subcloak.com/cloak-proxy/tools/alerts/): Get notified when an operation fails.
