A Score Upload Needs More Than an Accept Attribute Giang Pham, maker of Pianory, built a score importer that validates uploads by content rather than filename, detecting ZIP-based .mxl archives by byte signature and resolving the actual score through META-INF/container.xml instead of grabbing the first XML entry. The reader rejects entity declarations, timewise scores and malformed XML with typed error codes, caps entries at 40 MB uncompressed and encoded payloads at one million characters, and leaves server-side sync checks to a cheap prefix and base64 shape test rather than re-parsing. Pham notes the work is covered by unit tests only, not production testing. Disclosure: I’m Giang Pham, Pianory’s maker. AI helped draft this article from Pianory's source code. It describes the implementation and unit-test coverage, not production testing. A file picker can suggest which files a user should choose, but that suggestion is not an import contract. A browser may let someone select a misleading filename, a broken archive, or a file that has the right extension and the wrong bytes. A dependable importer needs to define what it accepts, inspect the content at each transformation, and make failure understandable before the app saves anything. Pianory’s score importer lists MusicXML, compressed MusicXML, MIDI, and PDF as selectable formats. For the MusicXML and MIDI path, the raw file must be no larger than 5 MB and its extension must be one of the supported score extensions. That is an early rejection step, not proof of file type. The reader then loads bytes, recognizes MIDI from its content and converts it to MusicXML, or sends other bytes through the MusicXML reader. ZIP detection uses the bytes’ signature, so a ZIP archive named .xml still follows the archive path. This distinction is useful in any upload flow: filenames help guide people, while the parser decides which content it can actually handle. Keep those jobs separate so a renamed file does not become an accidental format guarantee. An .mxl file is a ZIP container, and it may contain more than one XML-looking entry. Simply selecting the first .xml file can import a preview or decoy instead of the score. Pianory looks for META-INF/container.xml, reads its rootfile entries, and selects a referenced .musicxml or .xml file that exists in the archive. If that manifest is absent or does not identify a usable score, the code falls back to the first matching file outside META-INF. If no score entry can be found, it returns an archive error. The unzip helper only expands entries selected for the current step, and refuses an entry whose declared uncompressed size exceeds 40 MB. That is a per-entry ceiling, which matters when describing the limit precisely. Tests cover a manifest that points past a PDF entry and a decoy XML file, as well as fallback selection and malformed or empty archives. A parser should make its supported subset visible. This reader rejects empty input, entity declarations, timewise scores, and documents that do not contain a partwise score element. It allows a plain public DOCTYPE, but rejects entity declarations rather than trying to expand them. In a browser, it also asks DOMParser to parse the XML and checks for parser errors, so malformed XML can fail during import instead of later when the song is opened. Those checks describe a supported format boundary; they are not a claim of complete MusicXML schema validation. The implementation does not claim that every well-formed score is supported. Its tests exercise entity-expansion examples, timewise and non-MusicXML documents, empty input, and malformed archives. Typed error codes distinguish cases such as an unsafe declaration, an unsupported score shape, and an invalid XML document, which gives the interface a chance to explain a repair rather than show one generic failure. Once the input has become MusicXML, Pianory extracts basic metadata such as title, composer, part count, staves, measure count, and key. When the score lacks a title, the import path can fall back to the filename; MIDI conversion also receives that filename as its fallback title. The app then encodes MusicXML as a versioned mxl1: value containing base64-encoded gzip data. It checks the encoded string against a one-million-character limit, which keeps the stored representation aligned with the song payload limit. There is a second boundary on the server. The song schema caps the content length and, for MusicXML, checks the expected prefix and base64 shape. The API code deliberately keeps this inexpensive: it does not decompress and parse the score again during a sync push. Being explicit about that division avoids overstating what server validation proves. The practical lesson is to validate each representation where it changes: file selection, raw bytes, archive entry, parsed document, then persisted payload. Tests can target those handoffs with wrong extensions, decoy archive entries, hostile entity declarations, and oversized encoded content. That provides useful evidence about the importer’s contracts without pretending it replaces a production security review. I’m building Pianory https://pianory.app/en , a browser-based piano practice app that can work with imported scores. When you review an upload pipeline, which transition tends to hide the hardest edge cases: filename to bytes, archive to document, or document to stored data?