2026-06-16 11:12:50 -06:00
|
|
|
# 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:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"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
|
2026-07-08 19:02:25 -06:00
|
|
|
List*. Resolve it by email with the built-in resolver — never guess:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
node scripts/sp.mjs person --site "<SITE_URL>" --email nashotay@pescoinc.biz
|
|
|
|
|
# → { "lookupId": 53, "displayName": "Nashota Yazzie", "email": "..." }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Then write `{"ProjectManagerLookupId": 53}`. Limitation: the hidden list only
|
|
|
|
|
contains people who have accessed the site before. SharePoint's `ensureUser`
|
|
|
|
|
(which force-adds someone) needs certificate-based app-only auth that client
|
|
|
|
|
secrets can't provide — so if the resolver reports "not found", grant the
|
|
|
|
|
person site access (or assign them once in the SharePoint UI), then retry.
|
2026-06-16 11:12:50 -06:00
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"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`.
|