Create a New Workbook in OneDrive with Microsoft Graph
Most Microsoft Graph examples for Excel start from a workbook that already exists in OneDrive or SharePoint. Often, though, the job is to create one: a weekly report that should appear in a team's document library, a fresh tracking workbook for each new project, a file colleagues will edit in Excel for the web. Graph has no "create workbook" call as such — you create the file by uploading an .xlsx, and then, if needed, use the workbook API on it. This page builds the workbook in memory with pandas, uploads it with a single request for ordinary file sizes and an upload session for larger ones, controls what happens when a file of the same name exists, turns its data into an Excel table so later jobs can append rows through Graph, and checks the requests the code sends. It belongs to Automating Excel Online with Microsoft Graph.
Prerequisites
pip install requests pandas openpyxl
You need an access token for Microsoft Graph with Files.ReadWrite (delegated) or Files.ReadWrite.All / Sites.ReadWrite.All (application), and the drive ID of the target OneDrive or SharePoint document library; getting a token is covered in Authenticate to Microsoft Graph with MSAL for Excel Automation. The outputs on this page were produced against a mocked Graph API (using the responses library) that returns the documented response shapes, so the requests can be shown and checked without a tenant; the code is unchanged against the real service.
Step 1 — Build the workbook in memory
There is no need to write a temporary file: build the workbook into a bytes buffer and upload those bytes:
from io import BytesIO
import pandas as pd
orders = pd.DataFrame({"Order": ["SO-1001", "SO-1002", "SO-1003"], "Customer": ["Oakridge Farms", "Café Señor", "Zenith Logistics"],
"Amount": [1200.0, 380.0, 5600.0]})
buf = BytesIO()
with pd.ExcelWriter(buf, engine="openpyxl") as xw:
orders.to_excel(xw, sheet_name="Orders", index=False)
payload = buf.getvalue()
print(len(payload), "bytes; starts with", payload[:2])
A few kilobytes of .xlsx — a zip file, hence the PK signature. Anything pandas, openpyxl or XlsxWriter can produce can be uploaded this way, including formatting and charts. Building in memory avoids temporary-file clean-up and works in serverless functions with read-only file systems; the pattern is covered in Build an Excel Workbook in Memory with BytesIO.
Step 2 — Upload with one PUT, choosing what happens on a name clash
For files up to 250 MB Graph accepts a single PUT to the item's content, addressed by path. A query parameter decides what happens if a file with that name already exists:
import os
import requests
from urllib.parse import quote
GRAPH = "https://graph.microsoft.com/v1.0"
DRIVE = "b!finance-drive-id"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['GRAPH_TOKEN']}"
def upload_small(path, data, conflict="replace"):
url = f"{GRAPH}/drives/{DRIVE}/root:/{quote(path)}:/content"
r = session.put(url, data=data, params={"@microsoft.graph.conflictBehavior": conflict},
headers={"Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, timeout=120)
if r.status_code >= 400:
raise RuntimeError(f"{r.status_code}: {r.json()['error']['message']}")
return r.json()
item = upload_small("Reports/2026/orders_2026-09.xlsx", payload)
print(item["id"], item["name"], item["webUrl"])
try:
upload_small("Reports/existing.xlsx", payload, conflict="fail")
except RuntimeError as e:
print("with conflictBehavior=fail:", e)
The path after root:/ is relative to the drive's root; folders in it must exist (create them with a POST to the parent's children with a folder facet). @microsoft.graph.conflictBehavior takes replace (overwrite — the default for this call), fail (refuse with HTTP 409, as shown) or rename (keep both, adding a number to the new file's name). For scheduled reports, replace with a dated file name is usually right; for one-off project workbooks, fail protects a file someone may already have edited. The response is a driveItem with the new file's id — needed for the workbook API — and webUrl, the link people open in the browser. Graph's documentation recommends the simple upload only for small files (a few megabytes); larger ones should use an upload session.
Step 3 — Larger files: an upload session in chunks
For larger workbooks, create an upload session and send the bytes in chunks. Each chunk's size must be a multiple of 320 KiB (327,680 bytes), except the last:
CHUNK = 320 * 1024 * 10 # 3.2 MiB, a multiple of 320 KiB
def upload_large(path, data, conflict="replace"):
url = f"{GRAPH}/drives/{DRIVE}/root:/{quote(path)}:/createUploadSession"
s = session.post(url, json={"item": {"@microsoft.graph.conflictBehavior": conflict}}, timeout=60).json()
upload_url, total = s["uploadUrl"], len(data)
for start in range(0, total, CHUNK):
end = min(start + CHUNK, total) - 1
r = requests.put(upload_url, data=data[start:end + 1], timeout=300,
headers={"Content-Range": f"bytes {start}-{end}/{total}", "Content-Length": str(end - start + 1)})
r.raise_for_status()
return r.json()
big = payload * 2000 # stand-in for a larger workbook's bytes
done = upload_large("Reports/2026/large.xlsx", big)
print(done["name"], done["size"], "bytes in", len(chunks), "chunks:", chunks[0], "…", chunks[-1])
createUploadSession returns a pre-authenticated uploadUrl; chunks are sent there without the Graph bearer token, which is why the code uses requests.put rather than the authorised session — sending the token to the upload URL can make the request fail. Each response before the last returns 202 with the next expected byte range; the final chunk returns the created driveItem. Chunks of a few megabytes are a good balance between the number of requests and the cost of retrying one after a network error; the session also lets an interrupted upload be resumed from the next expected range rather than restarted.
Step 4 — Turn the data into an Excel table
Later jobs that append rows through Graph need an Excel table. With the new item's ID, add one over the data range and give it a stable name:
BOOK = f"{GRAPH}/drives/{DRIVE}/items/{item['id']}/workbook"
table = session.post(f"{BOOK}/tables/add", json={"address": f"Orders!A1:C{len(orders) + 1}", "hasHeaders": True}, timeout=60).json()
renamed = session.patch(f"{BOOK}/tables/{table['id']}", json={"name": "Orders"}, timeout=60).json()
print("table created:", table["name"], "→ renamed to", renamed["name"])
tables/add turns a range — addressed as Sheet!A1:C4 — into a table with its first row as headers; Graph names it Table1, and a PATCH renames it to something a later job can rely on. Alternatively, create the table in the file before uploading, with openpyxl's Table or XlsxWriter's add_table — one request fewer, and the table's style is under your control. Appending rows to it later is covered in Append Rows to an Excel Online Table with Microsoft Graph.
Step 5 — Check what was sent
Because every call above went through requests, a test can inspect the exact requests — here against the mock, in your own tests with the same technique:
for call in mock.calls[:3]:
req = call.request
print(req.method, req.url.split("?")[0].replace(GRAPH, ""), "| conflict:", "fail" if "fail" in req.url else "replace" if "replace" in req.url else "-",
"| auth:", "Authorization" in req.headers)
print("upload-session chunks carried no token:", all("Authorization" not in c.request.headers for c in mock.calls if "upload.example.com" in c.request.url))
The simple uploads went to /drives/{id}/root:/{path}:/content with the chosen conflict behaviour and the bearer token; the chunk uploads went to the session URL without it. Checks like these — that paths are quoted, the conflict behaviour is what you intended and tokens go only where they should — catch the mistakes that otherwise surface as a confusing 401 or a silently overwritten file in production. Uploading to SharePoint document libraries by site and path is covered in Upload an Excel Report to SharePoint with Python.
Common pitfalls
- Uploading to a folder that does not exist. Create missing folders first.
- Overwriting by accident. Set
conflictBehaviordeliberately;replaceis the default. - Large files in one PUT. Use an upload session with chunks in multiples of 320 KiB.
- Sending the bearer token to the upload URL. The session URL is pre-authenticated.
- Relying on the default table name. Rename it so later jobs can find it.
Conclusion
To create a workbook in OneDrive or SharePoint with Microsoft Graph, build it in memory and upload the bytes: a single PUT to root:/{path}:/content for small files, an upload session with chunks in multiples of 320 KiB for larger ones, with @microsoft.graph.conflictBehavior set to replace, fail or rename as the situation needs. Use the returned item ID for the workbook API — for example to add and name a table — and test the requests your code sends before running it against a real tenant.
Frequently asked questions
How do I create an Excel file in OneDrive with Python? Build the xlsx in memory with pandas or openpyxl and upload the bytes with a PUT request to the drive item's content endpoint, addressed by path under root, using a Microsoft Graph access token.
How do I stop Graph overwriting an existing file? Set the conflictBehavior query parameter to fail, which makes the upload fail with HTTP 409 if the name exists, or use rename to keep both files.
How do I upload a large Excel file with Microsoft Graph? Create an upload session with createUploadSession and send the file in chunks whose sizes are multiples of 320 KiB, each with a Content-Range header, to the returned upload URL.
How do I add a table to a workbook through Graph? POST to /workbook/tables/add on the item with the range address and hasHeaders, then PATCH the table to give it a meaningful name.
Related
- Up one level: Automating Excel Online with Microsoft Graph — Excel in the cloud from Python.
- Copy a Template Workbook in SharePoint with Microsoft Graph — starting from a template instead.
- Share an Excel File in OneDrive with a Link Using Graph — sharing the new workbook.
- Read and Write Excel Online Ranges with Microsoft Graph — editing it afterwards.
More on excel online & microsoft graph
- How-toSend an Excel Report with the Microsoft Graph API
- How-toAdd a Chart to an Excel Online Workbook with Microsoft Graph
- How-toAdd, Rename and Delete Worksheets with Microsoft Graph
- How-toAppend Rows to an Excel Online Table with Microsoft Graph
- How-toAuthenticate to Microsoft Graph with MSAL for Excel Automation
- How-toCall Excel Worksheet Functions Through Microsoft Graph