You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE]:Support drag-and-drop for Microsoft Office files (.docx, .xlsx) #27689
I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Description: Currently, the opencode chat interface does not support dragging and dropping Microsoft Office files (such as .docx and .xlsx). Users are forced to manually provide file paths or convert files to text/PDF before uploading.
Proposed Solution:
Expand the allowed file types in the frontend's drag-and-drop handler to include .docx and .xlsx.
Integrate a backend parsing mechanism (e.g., using python-docx and openpyxl) to convert these documents into a text-based format (like Markdown) that the LLM can process.
Use Case: Many software engineering and administrative tasks involve reading requirements or data stored in Office documents. Direct support would significantly improve the user experience and workflow efficiency.
Please review those issues and consider whether your request is already covered. If it is, adding a comment there with your specific use case (.docx/.xlsx + backend parsing) would help consolidate the discussion.
Hi! I would love to work on this feature. Could you assign it to me?
Here is my proposed implementation plan:
Frontend: Update \ACCEPTED_FILE_TYPES\ in \packages/app/src/constants/file-picker.ts\ to include .docx, .xlsx, and their MIME types.
Backend: Add \mammoth\ (for docx) and \xlsx\ (for Excel) to the dependencies in \packages/opencode/package.json.
Modify \packages/opencode/src/tool/read.ts\ to intercept .docx\ and .xlsx\ before they hit the binary file check. We will parse their contents into text/CSV and return it within <content>\ tags for the LLM to ingest natively.
Let me know if this approach sounds good or if you have any feedback before I submit a PR!
Adding our case for consolidation (as the dedupe bot suggested) — we hit this in a corporate rollout of opencode Desktop on a central config.
The angle that's slightly different from drag-and-drop: the “+” button / Cmd+U file picker filters selectable files by the model's modalities.input (text, image, audio, video, pdf) — so .docx/.xlsx and other office formats are simply greyed out and can't be attached at all.
Meanwhile the capability already exists elsewhere: the Read tool parses those same files fine once they're inside the project folder, and plugins (e.g. opencode-markitdown) convert them to Markdown. Only the picker blocks the path.
Two options from our users' perspective:
add a document modality for office formats, or
when attachments are handled tool-side, don't filter the picker — accept any file and route it through Read/conversion.
Observed on Desktop v1.17.18, macOS. Essentially the same request as #18143.
Adding our case: we run opencode against an internal gateway (LiteLLM proxy) that already handles Excel. The backend is fine with .xlsx — only the client drops it.
One correction to @bitfinop's note above — the picker doesn't filter on the model's modalities.input; I couldn't find that in the attachment path at all. The actual blockers are three hardcoded lists:
ACCEPTED_FILE_TYPES in packages/app/src/constants/file-picker.ts — the picker allowlist
attachmentMime() in prompt-input/files.ts — returns undefined for .xlsx (it's a ZIP, so it fails the text sniff), so the file is dropped after selection even if the picker lets it through. This logic is duplicated in session-ui/src/v2/components/prompt-input/attachments.ts.
local-attachment.ts in the TUI — if (!mime.startsWith("image/") && mime !== "application/pdf") return
The server already passes arbitrary mime through as a base64 file part (session/prompt.ts), so this looks client-side only.
Would you take a config option extending the allowed types, reusing the existing attachment namespace?
{ "attachment": { "types": [".xlsx"] } }
Same token syntax that array already mixes (MIME, text/*, or .ext), appended to the defaults. No parsing and no new dependencies — deliberately not what #32104 attempted.
One design question before I start: when an accepted file's type is beyond what the model claims to support, should it be sent anyway and let the provider decide, or gated on capability? The former is what our proxy needs, and matches what the server does today.
Also affected on the desktop app (Windows). The attach button / file picker filters out .xlsx (and the attachment MIME sniffing rejects both .xls and .xlsx as binary), so spreadsheet files cannot be attached at all. Being able to drag-and-drop .xlsx and have it converted to Markdown tables (like the office-converter approach in the pending PRs) would be very useful. Looking forward to seeing this merged.
We hit this on OpenCode Desktop 1.18.25 (Windows) and spent some time instrumenting the shipped
bundle before building a working local patch. Sharing what we found, because there is a detail in
this thread that will cost the next person a day.
The change proposed above is not sufficient on its own
Adding .docx / .xlsx to ACCEPTED_FILE_TYPES in packages/app/src/constants/file-picker.ts
does not make Office files attachable. We tried exactly that first, and nothing changed.
There are two independent attachment pipelines in the renderer, each with its own accept list
and its own MIME resolver:
The prompt input in the desktop app is data-component=prompt-input-v2, so only the second row is
live. Patching the first list has no observable effect.
Three places need to change
attachments.ts:5 — add the extensions to accepted (this drives both the <input accept>
attribute and the native picker's extensions).
attachments.ts:101 — attachmentMime() samples the first 4096 bytes and rejects anything with
NUL bytes. A .xlsx is a zip, so it is rejected as binary even once the picker lets it through.
The file has to be converted to text before this call.
file-picker.ts:3 — for consistency with the legacy path.
A subtle bug worth flagging
If you convert the file by replacing the File object, attachments.ts:107
silently returns undefined. getPathForFile looks the file up in a WeakMap populated by the
picker, and the converted File is a different object. The original reference has to be kept for
the sourcePath lookup, otherwise picker-attached documents lose their path.
What we prototyped
Conversion in the desktop main process behind an IPC handler (convert-office), exposed on the
preload bridge, called from add() before attachmentMime. .xlsx/.xlsm become TSV per sheet, .docx becomes plain text.
Deliberately no new dependencies: the zip central directory is read directly and inflated with node:zlib, then xl/worksheets/sheet*.xml + xl/sharedStrings.xml (or word/document.xml) are
parsed with regexes. No xlsx, no mammoth, no native modules, nothing added to the bundle size
beyond ~180 lines. It has been running against real spreadsheets and Word documents on Windows.
We are happy to open a PR with this cleaned up and tested.
Question for the maintainers
Per CONTRIBUTING this touches UI/product behaviour, so before writing anything we would like a
steer on scope:
Desktop-only, as we prototyped it (main-process IPC)? Simple, but the CLI and web clients
keep rejecting Office files.
Server-side, so every client benefits? Larger change, and it moves parsing into the server.
Is a dependency-free parser acceptable, or would you rather pull in a real library and accept the
bundle cost?
Happy to go whichever way you prefer — or to just contribute the findings above if this is already
on someone's roadmap.
We needed .docx/.xlsx drag-and-drop on a self-hosted opencode web (1.18.25 → 1.18.29, Linux arm64) and ended up instrumenting the same code paths as @revaza88. A few findings not in this thread yet, in case they save someone time:
attachmentMime() has two rejections, not one. After the NUL-byte check there is a control-character ratio check (> 0.3 of the first 4096 bytes). OOXML zips are ~0.13 so they only hit the NUL check, but legacy OLE2 files (.doc/.xls/.ppt, ~0.49) are rejected by the ratio check as well. Extending the accept list alone won't let those through.
Once a binary passes the client gate it is labeled text/plain and sent as a data: URL. On the server, resolvePart turns text/plain data parts into synthetic Called the Read tool … + raw-content text parts beforechat.message plugin hooks run. So a client-only fix leaks the raw bytes into the prompt as text — we saw the zip bytes land in context even though our hook had already replaced the file part.
What worked for us end-to-end (web drop, paste, and run --file): disable the two client checks (we patched the embedded bundle; upstream this could be the attachment.types-style allowlist suggested above) plus a server-side chat.message plugin that ignores the declared mime, sniffs magic bytes (PK\x03\x04 with word/ | xl/ | ppt/ entries, the OLE2 header, %PDF-), converts documents to text (pandoc / LibreOffice / pandas in our setup), and replaces non-document binaries with a visible notice so they never reach the model as text. Verified with a headless-Chromium drop test against the real bundle for docx/xlsx/pptx/doc, mixed drops, and a random-binary guard. The remaining wart is the duplicated synthetic part from (2), which a plugin cannot prevent.
If maintainers prefer the server-side direction, the smallest upstream change that makes this cleanly pluggable is running the text/plain synthetic-part injection after plugin hooks (or letting hooks see the raw file part first), plus the allowlist. Happy to share the hook if useful.
Thanks for the request! You'd like Microsoft Office files (.docx, .xlsx) dropped into the chat to be converted to text, so the model can read them without manual conversion first.
On OpenCode v2, dropping a .docx/.xlsx into the composer already attaches it, as a file path. The missing part is the content: the built-in read tool doesn't extract text from binary Office formats. Without that, the model only gets the contents if it converts the file itself, e.g. with a shell command.
Maintainers will consider this. This issue is where the request is being tracked; #49534 was closed as a duplicate of it. Related:
Activity
github-actions commentedon May 15, 2026
This issue might be a duplicate of existing issues. Please check:
Please review those issues and consider whether your request is already covered. If it is, adding a comment there with your specific use case (.docx/.xlsx + backend parsing) would help consolidate the discussion.
GP-commits commentedon May 17, 2026
Hi! I would love to work on this feature. Could you assign it to me?
Here is my proposed implementation plan:
Let me know if this approach sounds good or if you have any feedback before I submit a PR!
buildgreatthings commentedon Jun 9, 2026
We use Opencode as a general harness for all our agent runs. I think an even simpler first change would start with:
A separate follow on PR would be great for support opensource models.
bitfinop commentedon Jul 16, 2026
Adding our case for consolidation (as the dedupe bot suggested) — we hit this in a corporate rollout of opencode Desktop on a central config.
The angle that's slightly different from drag-and-drop: the “+” button / Cmd+U file picker filters selectable files by the model's
modalities.input(text, image, audio, video, pdf) — so .docx/.xlsx and other office formats are simply greyed out and can't be attached at all.Meanwhile the capability already exists elsewhere: the Read tool parses those same files fine once they're inside the project folder, and plugins (e.g. opencode-markitdown) convert them to Markdown. Only the picker blocks the path.
Two options from our users' perspective:
documentmodality for office formats, orObserved on Desktop v1.17.18, macOS. Essentially the same request as #18143.
jdecker76 commentedon Jul 30, 2026
Adding our case: we run opencode against an internal gateway (LiteLLM proxy) that already handles Excel. The backend is fine with
.xlsx— only the client drops it.One correction to @bitfinop's note above — the picker doesn't filter on the model's
modalities.input; I couldn't find that in the attachment path at all. The actual blockers are three hardcoded lists:ACCEPTED_FILE_TYPESinpackages/app/src/constants/file-picker.ts— the picker allowlistattachmentMime()inprompt-input/files.ts— returnsundefinedfor.xlsx(it's a ZIP, so it fails the text sniff), so the file is dropped after selection even if the picker lets it through. This logic is duplicated insession-ui/src/v2/components/prompt-input/attachments.ts.local-attachment.tsin the TUI —if (!mime.startsWith("image/") && mime !== "application/pdf") returnThe server already passes arbitrary mime through as a base64 file part (
session/prompt.ts), so this looks client-side only.Would you take a config option extending the allowed types, reusing the existing
attachmentnamespace?{ "attachment": { "types": [".xlsx"] } }Same token syntax that array already mixes (MIME,
text/*, or.ext), appended to the defaults. No parsing and no new dependencies — deliberately not what #32104 attempted.One design question before I start: when an accepted file's type is beyond what the model claims to support, should it be sent anyway and let the provider decide, or gated on capability? The former is what our proxy needs, and matches what the server does today.
zukktest-cell commentedon Aug 8, 2026
Also affected on the desktop app (Windows). The attach button / file picker filters out .xlsx (and the attachment MIME sniffing rejects both .xls and .xlsx as binary), so spreadsheet files cannot be attached at all. Being able to drag-and-drop .xlsx and have it converted to Markdown tables (like the office-converter approach in the pending PRs) would be very useful. Looking forward to seeing this merged.
revaza88 commentedon Sep 2, 2026
We hit this on OpenCode Desktop 1.18.25 (Windows) and spent some time instrumenting the shipped
bundle before building a working local patch. Sharing what we found, because there is a detail in
this thread that will cost the next person a day.
The change proposed above is not sufficient on its own
Adding
.docx/.xlsxtoACCEPTED_FILE_TYPESinpackages/app/src/constants/file-picker.tsdoes not make Office files attachable. We tried exactly that first, and nothing changed.
There are two independent attachment pipelines in the renderer, each with its own accept list
and its own MIME resolver:
ACCEPTED_FILE_TYPES(packages/app/src/constants/file-picker.ts:3)attachmentMimecreatePromptAttachmentsCoreaccepted(packages/session-ui/src/v2/components/prompt-input/attachments.ts:5)attachmentMimeaddatattachments.ts:99The prompt input in the desktop app is
data-component=prompt-input-v2, so only the second row islive. Patching the first list has no observable effect.
Three places need to change
attachments.ts:5— add the extensions toaccepted(this drives both the<input accept>attribute and the native picker's
extensions).attachments.ts:101—attachmentMime()samples the first 4096 bytes and rejects anything withNUL bytes. A
.xlsxis a zip, so it is rejected as binary even once the picker lets it through.The file has to be converted to text before this call.
file-picker.ts:3— for consistency with the legacy path.A subtle bug worth flagging
If you convert the file by replacing the
Fileobject,attachments.ts:107silently returns
undefined.getPathForFilelooks the file up in aWeakMappopulated by thepicker, and the converted
Fileis a different object. The original reference has to be kept forthe
sourcePathlookup, otherwise picker-attached documents lose their path.What we prototyped
Conversion in the desktop main process behind an IPC handler (
convert-office), exposed on thepreload bridge, called from
add()beforeattachmentMime..xlsx/.xlsmbecome TSV per sheet,.docxbecomes plain text.Deliberately no new dependencies: the zip central directory is read directly and inflated with
node:zlib, thenxl/worksheets/sheet*.xml+xl/sharedStrings.xml(orword/document.xml) areparsed with regexes. No
xlsx, nomammoth, no native modules, nothing added to the bundle sizebeyond ~180 lines. It has been running against real spreadsheets and Word documents on Windows.
We are happy to open a PR with this cleaned up and tested.
Question for the maintainers
Per CONTRIBUTING this touches UI/product behaviour, so before writing anything we would like a
steer on scope:
keep rejecting Office files.
bundle cost?
Happy to go whichever way you prefer — or to just contribute the findings above if this is already
on someone's roadmap.
supremest commentedon Sep 8, 2026
We needed .docx/.xlsx drag-and-drop on a self-hosted
opencode web(1.18.25 → 1.18.29, Linux arm64) and ended up instrumenting the same code paths as @revaza88. A few findings not in this thread yet, in case they save someone time:attachmentMime()has two rejections, not one. After the NUL-byte check there is a control-character ratio check (> 0.3of the first 4096 bytes). OOXML zips are ~0.13 so they only hit the NUL check, but legacy OLE2 files (.doc/.xls/.ppt, ~0.49) are rejected by the ratio check as well. Extending the accept list alone won't let those through.Once a binary passes the client gate it is labeled
text/plainand sent as adata:URL. On the server,resolvePartturnstext/plaindata parts into syntheticCalled the Read tool …+ raw-content text parts beforechat.messageplugin hooks run. So a client-only fix leaks the raw bytes into the prompt as text — we saw the zip bytes land in context even though our hook had already replaced the file part.What worked for us end-to-end (web drop, paste, and
run --file): disable the two client checks (we patched the embedded bundle; upstream this could be theattachment.types-style allowlist suggested above) plus a server-sidechat.messageplugin that ignores the declared mime, sniffs magic bytes (PK\x03\x04withword/|xl/|ppt/entries, the OLE2 header,%PDF-), converts documents to text (pandoc / LibreOffice / pandas in our setup), and replaces non-document binaries with a visible notice so they never reach the model as text. Verified with a headless-Chromium drop test against the real bundle for docx/xlsx/pptx/doc, mixed drops, and a random-binary guard. The remaining wart is the duplicated synthetic part from (2), which a plugin cannot prevent.Trap: over a remote plain-HTTP origin nothing attaches at all, regardless of this fix — that is Web UI: pasting images fails with
Cannot read properties of undefined (reading 'digest')when accessing via HTTP on non-localhost #41706 / web: file attachments silently dropped on HTTP remote host — crypto.subtle undefined #47645 (crypto.subtleunavailable in insecure contexts). Test over HTTPS orlocalhost.If maintainers prefer the server-side direction, the smallest upstream change that makes this cleanly pluggable is running the
text/plainsynthetic-part injection after plugin hooks (or letting hooks see the raw file part first), plus the allowlist. Happy to share the hook if useful.opencodejr commentedon Oct 29, 2026
Thanks for the request! You'd like Microsoft Office files (
.docx,.xlsx) dropped into the chat to be converted to text, so the model can read them without manual conversion first.On OpenCode v2, dropping a
.docx/.xlsxinto the composer already attaches it, as a file path. The missing part is the content: the built-inreadtool doesn't extract text from binary Office formats. Without that, the model only gets the contents if it converts the file itself, e.g. with a shell command.Maintainers will consider this. This issue is where the request is being tracked; #49534 was closed as a duplicate of it. Related:
In the meantime, a plugin could register a tool that extracts text from Office files, using the plugin API's
ctx.tool.transform.