Adding Markdown Support End-to-End (Part 7) A developer added Markdown file support to the Sift RAG stack, touching validation, extraction, MIME types, and a database schema migration. The change required updates across six layers including API validation, content-type mapping, extraction dispatch, frontend file picker, and a CHECK constraint. A subtle browser bug emerged from mismatched Content-Type headers in presigned S3 URLs. What it actually takes to wire a new file type through a layered RAG stack — validation, extraction, MIME quirks, and a schema migration. One of the things I deliberately built into Sift is a clear separation of concerns across the stack. The API validates what's allowed, the pipeline handles extraction, the frontend controls what you can drop, and the database enforces the constraint at rest. That structure pays off when you add features — but it also means "add Markdown support" touches more layers than you might expect. This post walks through what that change actually looked like: every file it touched, the subtle bug that emerged in the browser, and the schema migration that closed the loop. Before making any change, I mapped out every place the stack has an opinion about file types: DocumentsFunction validates the extension before issuing a presigned upload URL DocumentService maps extensions to S3 content types for signing extract handler.py dispatches on extension to the right extractor UploadDropzone controls what the file picker and drag-and-drop accept useDocuments.ts sets the Content-Type header on the S3 PUT CHECK constraint on documents.file type enforces the allowed set at restEach of these is independent. A gap in any one of them causes a different failure mode: the API rejects the upload at step 1, the pipeline silently fails at step 3, the S3 PUT returns a 403 at step 5, or the database insert throws at step 6. The entry point is DocumentsFunction.cs . When a client calls POST /documents/upload-url , the function checks the extension against an allowed set before doing anything else: js // Before var allowedExtensions = new HashSet