Build Query
Build, live-test, and debug monday.com GraphQL queries and mutations. Introspects the actual schema to ensure correctness, formats column values properly, and validates every query by executing it.
Scope
- Helps external developers write correct GraphQL for the monday.com public API
- Complements the monday MCP tools — uses them for schema introspection and live testing
- Does NOT cover SDK setup (use
/monday-api:setup) or production operations (use/monday-api:ops)
Prerequisites
- The monday MCP server must be connected (check
/mcp) - A valid API token must be configured in the MCP server
Workflow
Step 1: Understand the Request
| User says | Action |
|---|---|
| "get items from board X" / "read board data" | Go to Flow 1 (Read query) |
| "create an item" / "add a row" | Go to Flow 2 (Write mutation) |
| "update status column" / "set date value" | Go to Flow 3 (Column value write) |
| "get all items" / "paginate through board" | Go to Flow 4 (Pagination) |
| "my query returns an error" / "this isn't working" | Go to Flow 5 (Debug) |
| General "how do I query X" | Introspect schema first, then choose the right flow |
If the user hasn't specified a board ID, ask for one — most queries require it.
Flow 1: Build a Read Query
Step 1: Introspect the schema
Use mcp__monday__get_graphql_schema with operationType: "read" to find available query fields. The output lists root-level queries (e.g., boards, items, users, me) and their argument types — use this to find the right query entry point.
Then use mcp__monday__get_type_details with the return type name (e.g., Board, Item) to discover available fields. The output lists field names and their types — only request fields the developer actually needs, as every field adds to complexity cost.
Step 2: Build the query
Construct the GraphQL query with proper arguments and field selection.
Rules:
- For board items, ALWAYS use
items_page(not the deprecateditemsfield on boards) - Use
idsparameter as an array:boards(ids: [BOARD_ID]) - Keep field selection minimal — only what's needed
- Use variables for dynamic values (not string interpolation)
Step 3: Live-test the query
Execute via mcp__monday__all_monday_api with the constructed query.
Show the actual response to the developer so they can see the exact shape.
Step 4: Present working code
Show the tested query wrapped in SDK code. Define the query in a .graphql.ts file and use code-generated types:
// src/queries.graphql.ts
export const GET_BOARD_ITEMS = `
query GetBoardItems($boardId: [ID!]!) {
boards(ids: $boardId) {
name
items_page(limit: 25) {
cursor
items {
id
name
column_values { id text value }
}
}
}
}
`;
// src/index.ts
import 'dotenv/config';
import { ApiClient } from '@mondaydotcomorg/api';
import { GET_BOARD_ITEMS } from './queries.graphql';
import type { GetBoardItemsQuery } from './generated/graphql'; // from npm run codegen
const client = new ApiClient({ token: process.env.MONDAY_API_TOKEN! });
const result = await client.request<GetBoardItemsQuery>(GET_BOARD_ITEMS, { boardId: [BOARD_ID] });
Flow 2: Build a Write Mutation
Step 1: Introspect mutation arguments
Use mcp__monday__get_graphql_schema with operationType: "write" to find the mutation.
Use mcp__monday__get_type_details to inspect required and optional arguments.
Step 2: If column values are involved, get the exact JSON format
Use mcp__monday__get_column_type_info for each column type being written. See Flow 3 for details.
The column_values argument takes a JSON string — this is the #1 source of developer errors. The JSON must be stringified and passed as a string value.
Step 3: Build the mutation with variables
Always use GraphQL variables for dynamic values:
mutation ($boardId: ID!, $groupId: String!, $itemName: String!, $columnValues: JSON) {
create_item(
board_id: $boardId
group_id: $groupId
item_name: $itemName
column_values: $columnValues
) {
id
name
}
}
Step 4: Confirm and test with test data
Show the complete mutation and variables to the developer. State clearly: "This mutation will write data. Shall I run it now to verify it works?"
If they confirm, execute via mcp__monday__all_monday_api. If the mutation creates an item or makes a change, that's expected — the goal is to verify the query structure is correct. Note any test items created so the developer can clean them up.
Step 5: Present working code
Show the tested mutation wrapped in SDK code with proper variable handling.
Flow 3: Column Value Formatting
This is where most developers get stuck. Each column type has a specific JSON format for writes.
Step 1: Identify column types needed
Ask which columns the developer wants to set, or inspect the board:
Use mcp__monday__get_board_info with the board ID to see column definitions.
The column IDs returned (e.g., status, date4, numbers0) are the keys to use in column_values. They are NOT the display names shown in the UI.
Example response:
{
"columns": [
{ "id": "status", "title": "Status", "type": "status" },
{ "id": "date4", "title": "Due Date", "type": "date" },
{ "id": "numbers0", "title": "Budget", "type": "numbers" }
]
}
Step 2: Get the exact JSON format
Use mcp__monday__get_column_type_info for each column type. This returns the exact schema.
Step 3: Show the format with examples
Common column value formats (quick reference):
| Type | Write JSON | Example |
|---|---|---|
| status | {"index": N} or {"label": "Label"} |
{"label": "Done"} |
| date | {"date": "YYYY-MM-DD"} |
{"date": "2026-01-15"} |
| date + time | {"date": "YYYY-MM-DD", "time": "HH:MM:SS"} |
{"date": "2026-01-15", "time": "09:00:00"} |
| numbers | "42" (number as string) |
"42" |
| text | "hello" (plain string) |
"hello world" |
| dropdown | {"ids": [1, 2]} |
{"ids": [3]} |
| people | {"personsAndTeams": [{"id": N, "kind": "person"}]} |
{"personsAndTeams": [{"id": 123, "kind": "person"}]} |
| link | {"url": "https://...", "text": "Label"} |
{"url": "https://monday.com", "text": "Monday"} |
For all 40+ column types, read docs/column-types-reference.md in this plugin directory.
Step 4: Build the column_values JSON
The column_values argument is a JSON string containing an object keyed by column ID:
"{\"status_column_id\": {\"label\": \"Done\"}, \"date_column_id\": {\"date\": \"2026-01-15\"}}"
When using GraphQL variables, pass it as:
const variables = {
columnValues: JSON.stringify({
status: { label: "Done" },
date4: { date: "2026-01-15" }
})
};
Step 5: Build and test the mutation
Use change_multiple_column_values for setting multiple columns at once (preferred over calling change_simple_column_value per column).
Follow the dry-run + confirm flow from Flow 2, Steps 4-5.
Flow 4: Pagination
Pagination uses a two-phase cursor-based pattern. This is the most common source of bugs.
Phase 1: Initial query with items_page
items_page is nested inside boards or groups:
query {
boards(ids: [BOARD_ID]) {
items_page(limit: 100, query_params: {
rules: [{column_id: "status", compare_value: ["Done"]}]
}) {
cursor
items {
id
name
column_values { id text value }
}
}
}
}
limit: max 500 items per page (default 25)query_params: optional filtering (cannot combine withcursor)- Save the returned
cursorvalue - If
cursorisnullafter Phase 1, all items fit in one page — no Phase 2 needed
Phase 2: Subsequent pages with next_items_page
CRITICAL:
next_items_pageMUST be a root-level query. Do NOT nest it insideboards {}. This is the single most common pagination bug.
query {
next_items_page(limit: 100, cursor: "YOUR_CURSOR_HERE") {
cursor
items {
id
name
column_values { id text value }
}
}
}
- When
cursorreturnsnull, you have all items - Keep the same field selection as Phase 1
Full pagination loop in SDK code:
Define queries in a .graphql.ts file and use code-generated types:
// src/queries.graphql.ts
export const GET_BOARD_ITEMS_PAGE = `
query GetBoardItemsPage($boardId: [ID!]!) {
boards(ids: $boardId) {
items_page(limit: 100) {
cursor
items { id name }
}
}
}
`;
export const GET_NEXT_ITEMS_PAGE = `
query GetNextItemsPage($cursor: String!) {
next_items_page(limit: 100, cursor: $cursor) {
cursor
items { id name }
}
}
`;
// src/index.ts
import 'dotenv/config';
import { ApiClient } from '@mondaydotcomorg/api';
import { GET_BOARD_ITEMS_PAGE, GET_NEXT_ITEMS_PAGE } from './queries.graphql';
import type { GetBoardItemsPageQuery, GetNextItemsPageQuery } from './generated/graphql';
const client = new ApiClient({ token: process.env.MONDAY_API_TOKEN! });
let allItems: GetBoardItemsPageQuery['boards'][0]['items_page']['items'] = [];
// Phase 1: Initial query
const firstPage = await client.request<GetBoardItemsPageQuery>(GET_BOARD_ITEMS_PAGE, { boardId: [BOARD_ID] });
allItems.push(...firstPage.boards[0].items_page.items);
let cursor = firstPage.boards[0].items_page.cursor;
// Phase 2: Subsequent pages (root-level!)
while (cursor) {
const nextPage = await client.request<GetNextItemsPageQuery>(GET_NEXT_ITEMS_PAGE, { cursor });
allItems.push(...nextPage.next_items_page.items);
cursor = nextPage.next_items_page.cursor;
}
Step: Live-test both phases
Execute the Phase 1 query via mcp__monday__all_monday_api. If items exist and a cursor is returned, execute Phase 2 to verify the full loop works.
For the full pagination reference with filtering and edge cases, read docs/pagination-patterns.md in this plugin directory.
Flow 5: Debug a Query
Step 1: Get the failing query and error
Ask the developer to paste:
- Their GraphQL query/mutation
- The error message or unexpected response
Step 2: Run diagnostics
Check these common issues in order:
| Check | How |
|---|---|
| Field names exist | mcp__monday__get_type_details to verify field names on the type |
Using items instead of items_page |
Look for items { directly on boards — should be items_page |
next_items_page nested in boards |
Must be at root level |
| Column value format wrong | mcp__monday__get_column_type_info for the column type |
| Missing required arguments | mcp__monday__get_type_details to check required args |
column_values not stringified |
Must be a JSON string, not a raw object |
query_params + cursor combined |
Cannot use both in the same items_page call |
| API version mismatch | Field may not exist in the pinned version |
Step 3: Fix and re-test
Apply the fix and execute the corrected query via mcp__monday__all_monday_api.
Step 4: Present the corrected query
Show the before/after diff and explain what was wrong.
Error Handling
| Error | Action |
|---|---|
| "Field 'X' not found" | Introspect schema with get_type_details to find correct field name |
| "Argument 'X' required" | Check mutation signature for required args |
ColumnValueException |
Use get_column_type_info to get correct JSON format |
ComplexityException |
Reduce requested fields, add pagination limits, remove unnecessary nesting |
| "items" deprecation warning | Switch to items_page pattern (Flow 4) |
| MCP not connected | Tell user to check /mcp and ensure the monday server is listed |
| Rate limit / 429 | Suggest using /monday-api:ops for rate limit guidance |
| "How do I find column IDs?" | Use mcp__monday__get_board_info with the board ID — columns { id title type } |