Guide
Microsoft Graph Mail Folders API
The Microsoft Graph mailFolder resource models the folder tree in an Outlook or Microsoft 365 mailbox. This guide covers listing top-level folders, addressing well-known folder names instead of IDs, walking nested child folders, reading item and unread counts, and filtering with OData query parameters. The Nylas CLI gives you the same folder list across Gmail, Outlook, and IMAP without an Azure AD app.
Written by Prem Keshari Senior SRE
Command references used in this guide: nylas email folders list, nylas email list.
What is the Microsoft Graph mailFolder resource?
A mailFolder is the Graph object that represents one folder in an Outlook or Microsoft 365 mailbox, such as Inbox, Sent Items, or a custom folder. Each folder exposes an id, a displayName, a parentFolderId, and three counters. Folders nest to arbitrary depth through child folders.
According to the Microsoft Learn mailFolder resource reference, every folder reports childFolderCount (how many subfolders it holds), totalItemCount (messages in the folder), and unreadItemCount (unread messages). A boolean isHidden marks folders that clients normally suppress. These five fields cover most folder-management tasks without ever opening a message, so a dashboard can show per-folder unread badges from a single list call.
How do you list top-level mail folders in Graph?
Issue a GET against /me/mailFolders to list the top-level folders in the signed-in user's mailbox. One request returns folders with five counter fields each, page size 10 by default. The Microsoft Learn List mailFolders page documents the call. The 401 and 403 codes that block this call resolve in under 5 minutes once you fix the token or scope.
The request below lists folders for the current user with a delegated access token. Each entry includes the counters described above, so you can read unread totals straight from the response. Hidden folders are excluded unless you opt in with the includeHiddenFolders query parameter, covered in the nesting section below.
# List top-level mail folders for the signed-in user
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
| jq '.value[] | {displayName, totalItemCount, unreadItemCount, childFolderCount}'
# Example element from the .value array:
# {
# "displayName": "Inbox",
# "totalItemCount": 2847,
# "unreadItemCount": 31,
# "childFolderCount": 4
# }The default page size is 10, so a mailbox with more than 10 top-level folders returns an @odata.nextLink URL. Call that link to fetch the next page. To pull more folders in one request, raise the page size with $top, which the filtering section explains.
What are the Graph well-known folder names?
Graph defines 17 well-known folder names you can put directly in the path instead of a folder id. For example, GET /me/mailFolders/sentitems resolves the Sent Items folder in any mailbox without first looking up its ID. According to the mailFolder reference, these constants stay stable across every mailbox and language.
These names matter because a folder's displayName is localized to the mailbox language and is not guaranteed unique. The Microsoft Learn reference states a well-known name resolves regardless of the mailbox's language
. All 17 names work across every mailbox in the tenant, and one well-known name replaces an ID lookup that would otherwise cost an extra request. Reserve displayName for user-created folders that have no constant.
| Well-known name | Folder |
|---|---|
inbox | Inbox |
archive | Archive |
drafts | Drafts |
sentitems | Sent Items |
deleteditems | Deleted Items |
junkemail | Junk Email |
outbox | Outbox |
clutter | Clutter |
conflicts | Sync Conflicts |
conversationhistory | Conversation History |
localfailures | Local Failures |
recoverableitemsdeletions | Recoverable Items (purges) |
scheduled | Scheduled |
searchfolders | Search Folders |
serverfailures | Server Failures |
syncissues | Sync Issues |
msgfolderroot | Top of Information Store (mailbox root) |
How do you read child and nested folders?
Mail folders nest to arbitrary depth, and each folder reports its direct subfolder count in childFolderCount. To list a folder's immediate children, call GET /me/mailFolders/{id}/childFolders, documented in the Microsoft Learn List childFolders reference. Because nesting has no fixed limit, walking a deep tree means recursing through child folders whose childFolderCount is greater than 0. A two-level tree costs 2 commands without a shortcut, so the parameters below collapse the walk into fewer calls.
Two shortcuts reduce round-trips. The $expand=childFolders parameter returns one level of children inline with the parent folders, and the includeHiddenFolders=true query parameter surfaces folders whose isHidden flag is set, which the default list call omits. The example expands children of the well-known inbox folder and also lists hidden top-level folders.
# List the direct child folders of the Inbox (addressed by well-known name)
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/childFolders" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
| jq '.value[] | {displayName, childFolderCount}'
# Expand one level of children inline with the top-level folders
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders?\
\$expand=childFolders" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value[].displayName'
# Include hidden folders (isHidden = true) that are normally suppressed
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders?includeHiddenFolders=true" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value | length'Each child folder is itself a full mailFolder with the same five fields, so the counters work at every level. A folder with childFolderCount of 0 is a leaf, which is the stop condition for a recursive walk.
How do you filter and page mail folders?
Graph folder queries use the OData system query options documented in Microsoft Learn's query parameters reference: $select trims the returned fields, $top sets the page size, $filter restricts results, and $orderby sorts them. The default page size is 10; follow the @odata.nextLink URL Graph returns to page through additional folders. Trimming to 3 fields with $select can cut response size by 70%.
You can filter on displayName for user-created folders, but keep the localization caveat in mind: displayName values change with the mailbox language, so a hard-coded English name can miss a German or Japanese mailbox. The example pages 50 folders, selects three fields, and filters by display name. To list messages inside a folder, append /messages to the folder path.
# Page 50 folders, select 3 fields, sort by name
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders?\
\$top=50&\
\$select=displayName,unreadItemCount,totalItemCount&\
\$orderby=displayName" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value[]'
# Filter folders whose displayName equals "Receipts"
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders?\
\$filter=displayName eq 'Receipts'" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value[].id'
# List messages in the Inbox by well-known name
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages?\
\$top=5&\$select=subject,receivedDateTime" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value[].subject'Filtering by displayName works for ad-hoc lookups, but for stable scripts, resolve a folder once and store its id. The ID never changes, while the display name can be renamed or localized out from under you.
How do you list folders without an Azure AD app?
Graph folder access needs an Azure AD app registration, OAuth scopes, and token refresh logic before the first call returns. For scripts, CI jobs, and quick checks, the nylas email folders list command returns the folder list directly. It works against Outlook, Gmail, and IMAP, normalizing Gmail labels and Graph folders into one shape, so the same command covers every connected account.
Setup takes about 2 minutes through nylas init, with no Azure portal, no client secret, and no scope selection. The first command lists folders as JSON for scripting; the second scopes a message listing to a single folder by name, which mirrors a Graph /mailFolders/{id}/messages call without the ID lookup.
# Install (macOS or Linux)
brew install nylas/nylas-cli/nylas
# List every folder in the connected mailbox as JSON
nylas email folders list --json
# Scope a message listing to one folder by name
nylas email list --folder "Inbox"
nylas email list --folder "Sent Items"The folder list returns the same display names and IDs Graph exposes, so you can prototype against the CLI and move to direct Graph calls later without relearning the folder model. For other install methods, see the getting started guide.
How do you create, rename, move, and delete mail folders?
Graph mail folders are managed with four write operations against the mailFolder resource. POST /me/mailFolders with a displayName creates a top-level folder, PATCH renames it, POST /me/mailFolders/{id}/move reparents it, and DELETE removes it. Each call needs the Mail.ReadWrite permission and finishes in one request.
To create a folder, send a POST to /me/mailFolders with a JSON body. The Microsoft Learn Create mailFolder reference states that displayName and isHidden are the only writable property for a mailFolder object
, so the body needs only those two fields. To nest a folder, POST to /me/mailFolders/{id}/childFolders instead, which is documented on the Create childFolder page. A successful create returns 201 Created with the new folder's id.
# Create a top-level folder named "Receipts"
curl -s -X POST "https://graph.microsoft.com/v1.0/me/mailFolders" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"displayName": "Receipts"}' | jq '{id, displayName}'
# Create a nested folder under an existing parent
curl -s -X POST "https://graph.microsoft.com/v1.0/me/mailFolders/$PARENT_ID/childFolders" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"displayName": "2026"}' | jq '{id, parentFolderId}'Renaming a folder is a PATCH against the folder's ID with a new displayName, documented in the Update mailFolder reference. Moving a folder is a POST to /me/mailFolders/{id}/move with a destinationId, which reparents the folder and every subfolder under the new parent in 1 call (see the move mailFolder page). Deleting is a DELETE on the folder ID and returns 204 No Content.
# Rename a folder (PATCH the displayName)
curl -s -X PATCH "https://graph.microsoft.com/v1.0/me/mailFolders/$FOLDER_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"displayName": "Receipts 2026"}' | jq '.displayName'
# Move a folder under a new parent
curl -s -X POST "https://graph.microsoft.com/v1.0/me/mailFolders/$FOLDER_ID/move" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg id "$NEW_PARENT_ID" '{destinationId: $id}')"
# Delete a folder (and everything inside it)
curl -s -X DELETE "https://graph.microsoft.com/v1.0/me/mailFolders/$FOLDER_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" -w "%{http_code}\n"Deleting a folder isn't scoped to the folder shell. It removes the folder and every message inside it, and on Outlook those messages move to Deleted Items rather than vanishing. Confirm a folder is empty with the totalItemCount field before issuing the DELETE, because there is no undo prompt and the call returns 204 in well under 5 seconds whether the folder held 0 or thousands of emails.
How are Graph mail folders different from Gmail labels?
Graph mail folders and Gmail labels solve the same problem with opposite models. Graph uses a folder tree where every message lives in exactly one folder and nesting happens through child folders. Gmail uses labels, where a single message can carry many labels at once and the "folders" you see in the UI are labels presented as a tree. The two models differ on cardinality, nesting, and how a message is reclassified.
The practical consequence is reclassification cost. In Graph, moving a message between folders is one move action that changes its parentFolderId. In Gmail, the same effect means adding one label and removing another, because a message has no single home. The Gmail API documents Inbox itself as the system label INBOX, so archiving a message is removing that one label rather than moving it. The Gmail labels API guide covers the label side in full.
| Dimension | Graph mail folders | Gmail labels |
|---|---|---|
| Messages per container | One folder per message | Many labels per message |
| Nesting | childFolders tree, arbitrary depth | / in label name (display only) |
| Reclassify a message | move changes parentFolderId | Add label, remove label |
| System containers | 17 well-known names | System labels (INBOX, SENT) |
| Archive a message | Move to archive folder | Remove the INBOX label |
For cross-provider tooling, the gap matters because code that assumes one container per message breaks on Gmail, and code that assumes many labels breaks on Outlook. The nylas email folders list command normalizes both into one shape across 3 providers (Outlook, Gmail, IMAP), so you write the folder logic once instead of branching on provider.
How do you move a message into a folder?
Moving a message into a Graph folder is a POST to /me/messages/{id}/move with a destinationId that names either a folder ID or one of the 17 well-known names. The Microsoft Learn message: move reference says the call creates a new copy of the message in the destination folder and removes the original message
, so the moved message gets a new id.
That new-ID behavior trips up scripts that cache message IDs. Because Graph returns 201 Created with a fresh message object, any code holding the old ID must read the new id from the response before acting again. The example moves a message to the well-known deleteditems folder and captures the new ID for follow-up calls.
# Move a message to Deleted Items, capture the new id
curl -s -X POST "https://graph.microsoft.com/v1.0/me/messages/$MESSAGE_ID/move" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"destinationId": "deleteditems"}' | jq '{newId: .id, parentFolderId}'The Nylas CLI relocates a message with one command, nylas email move, instead of a raw Graph call. Pass --folder with a folder ID (find IDs with nylas email folders list) to move the message, or --archive to archive it. The same command works across Outlook, Gmail, and IMAP: on folder-based Outlook accounts it relocates the message, while on Gmail label accounts --archive removes every label including INBOX.
# Move a message into a folder by ID
nylas email move <message-id> --folder <folder-id>
# Or archive it (provider-aware: folder move on Outlook, label removal on Gmail)
nylas email move <message-id> --archiveThe move emails between folders guide covers bulk workflows that pipe IDs from nylas email list --folder FOLDER_ID --json into a move loop across all three providers.
Next steps
- Microsoft Graph email quick start — app registration, OAuth scopes, and sending or reading messages
- Graph API error codes — fix the 401, 403, and 429 errors that fail folder calls
- Move emails between folders — route messages once you know the target folder
- Gmail labels API — how the same folder commands map to Gmail labels
- Microsoft Graph change notifications — subscribe to changes in a folder
- Full command reference — every flag and subcommand documented