Files
Claude-SharepointLists/.claude/skills/sharepoint-lists/references/graph-api.md
ang3l12 fd957177b9 Initial import: SharePoint Lists skill
Claude Code skill for SharePoint Lists CRUD via Microsoft Graph (app-only auth): reusable graph.mjs client, sp.mjs CLI, and setup + API reference docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 11:12:50 -06:00

4.5 KiB

Graph API reference for SharePoint List items

Quick reference for the parts of the Microsoft Graph Lists API that this skill relies on. Full docs: https://learn.microsoft.com/graph/api/resources/listitem.

Internal vs. display column names

Graph reads and writes use a column's internal name, which is frequently NOT the display name you see in the UI:

  • Spaces and special characters are encoded: "Due Date" → DueDate; a column later renamed keeps its original internal name; some become OData__x005f....
  • Always discover internal names before writing: node scripts/sp.mjs columns --site "<URL>" --list "<NAME>".
  • In the columns output, the name field is the internal name; displayName is what the UI shows. Write to name.

Title is the built-in primary text column on most lists.

Field value formats by column type

When creating/updating, --fields is a JSON object of internal name → value:

Column type JSON value Example
Single line / multi-line text string {"Title":"Hello"}
Number / Currency number {"Estimate":3.5}
Yes/No (boolean) boolean {"Approved":true}
Date / DateTime ISO 8601 string (UTC) {"DueDate":"2026-07-01T00:00:00Z"}
Choice (single) the choice string {"Status":"In Progress"}
Choice (multi) needs an OData type hint (see below)
Hyperlink { "Url": "...", "Description": "..." } {"Link":{"Url":"https://x","Description":"X"}}
Lookup (single) set <Name>LookupId to the target item id {"CategoryLookupId":7}
Lookup (multi) OData type hint + array of ids (see below)
Person/Group (single) set <Name>LookupId to the user's lookup id {"AssignedToLookupId":12}

Multi-value fields need an @odata.type annotation

Graph requires you to declare the collection type alongside the value. Include both keys in the fields object:

{
  "Categories@odata.type": "Collection(Edm.String)",
  "Categories": ["Marketing", "Sales"],

  "RelatedItemsLookupId@odata.type": "Collection(Edm.Int32)",
  "RelatedItemsLookupId": [3, 9, 14]
}

Person / Lookup ids

Person and lookup columns store an integer id, not a name/email. The internal field name is usually <DisplayInternalName>LookupId. Getting the right id:

  • Lookup: the id is the target list item's id. Query the source list to find it.
  • Person: the id is the user's row id in the site's hidden User Information List. This is awkward to obtain via Graph alone; if you need to set people fields by email/name often, that's a good reason to add a resolver helper (or do it via the SharePoint REST ensureUser endpoint). Flag this to the user rather than guessing an id.

If a write returns 400 invalidRequest or 400 generalException, the field name or one of these value formats is almost always the cause.

Querying items (OData)

The CLI maps flags to OData query options on /sites/{site}/lists/{list}/items:

  • --filter$filter. Filter on fields with the fields/ prefix:
    • fields/Status eq 'Open'
    • fields/Priority ge 2
    • fields/Title eq 'Exact' (text is case-sensitive in eq)
    • startswith(fields/Title,'Mig')
    • combine with and / or: fields/Status eq 'Open' and fields/Priority ge 2
  • --orderby$orderby, e.g. fields/DueDate asc or fields/Created desc.
  • --select → projects which fields come back: Title,Status,DueDate.
  • --top → page size; --all follows @odata.nextLink to return every page.

Non-indexed columns

SharePoint only allows server-side filter/sort on indexed columns unless you opt into a best-effort mode. The client automatically sends Prefer: HonorNonIndexedQueriesWarningMayFailRandomly whenever --filter or --orderby is present, which lets queries on non-indexed columns run (they may occasionally fail on very large lists — add a column index in SharePoint for heavy use). If a filter intermittently errors on a big list, that's why.

Escaping quotes

OData string literals use single quotes; a literal single quote is doubled: fields/Title eq 'O''Brien'. In the shell, wrap the whole --filter value in double quotes.

Response shape

Reads return items simplified to:

{
  "id": "42",
  "webUrl": "https://contoso.sharepoint.com/sites/.../DispForm.aspx?ID=42",
  "createdDateTime": "2026-06-01T10:00:00Z",
  "lastModifiedDateTime": "2026-06-10T12:30:00Z",
  "fields": { "Title": "...", "Status": "Open", "...": "..." }
}

id is the item id you pass to get, update, and delete.