{"id":13623708,"url":"https://github.com/jaydenseric/graphql-multipart-request-spec","last_synced_at":"2025-10-08T23:20:05.788Z","repository":{"id":38419467,"uuid":"106147733","full_name":"jaydenseric/graphql-multipart-request-spec","owner":"jaydenseric","description":"A spec for GraphQL multipart form requests (file uploads).","archived":false,"fork":false,"pushed_at":"2025-03-05T06:37:13.000Z","size":241,"stargazers_count":1024,"open_issues_count":7,"forks_count":56,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-04-01T11:01:48.326Z","etag":null,"topics":["graphql","maintained"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jaydenseric.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":".github/funding.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"jaydenseric"}},"created_at":"2017-10-08T03:36:53.000Z","updated_at":"2025-03-13T16:42:22.000Z","dependencies_parsed_at":"2024-01-10T22:10:28.559Z","dependency_job_id":"867a8666-f2bc-4669-a257-a70af1fce9cd","html_url":"https://github.com/jaydenseric/graphql-multipart-request-spec","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaydenseric%2Fgraphql-multipart-request-spec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaydenseric%2Fgraphql-multipart-request-spec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaydenseric%2Fgraphql-multipart-request-spec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaydenseric%2Fgraphql-multipart-request-spec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaydenseric","download_url":"https://codeload.github.com/jaydenseric/graphql-multipart-request-spec/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247838392,"owners_count":21004577,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql","maintained"],"created_at":"2024-08-01T21:01:34.744Z","updated_at":"2025-10-08T23:20:00.740Z","avatar_url":"https://github.com/jaydenseric.png","language":null,"readme":"# GraphQL multipart request specification\n\nAn interoperable [multipart form](https://tools.ietf.org/html/rfc7578) field structure for GraphQL requests, used by various file upload client/server implementations.\n\nIt’s possible to implement:\n\n- Nesting files anywhere within operations (usually in `variables`).\n- Operation batching.\n- File deduplication.\n- File upload streams in resolvers.\n- Aborting file uploads in resolvers.\n\n![Sync vs async GraphQL multipart request middleware](sync-vs-async-graphql-multipart-request-middleware.svg)\n\n## Multipart form field structure\n\nAn “operations object” is an [Apollo GraphQL POST request](https://www.apollographql.com/docs/apollo-server/workflow/requests/#post-requests) (or array of requests if batching). An “operations path” is an [`object-path`](https://npm.im/object-path) string to locate a file within an operations object.\n\nSo operations can be resolved while the files are still uploading, the fields are ordered:\n\n1. `operations`: A JSON encoded operations object with files replaced with `null`.\n2. `map`: A JSON encoded map of where files occurred in the operations. For each file, the key is the file multipart form field name and the value is an array of operations paths.\n3. File fields: Each file extracted from the operations object with a unique, arbitrary field name.\n\n## Examples\n\n### Single file\n\n#### Operations\n\n```js\n{\n  query: `\n    mutation($file: Upload!) {\n      singleUpload(file: $file) {\n        id\n      }\n    }\n  `,\n  variables: {\n    file: File // a.txt\n  }\n}\n```\n\n#### cURL request\n\n```shell\ncurl localhost:3001/graphql \\\n  -F operations='{ \"query\": \"mutation ($file: Upload!) { singleUpload(file: $file) { id } }\", \"variables\": { \"file\": null } }' \\\n  -F map='{ \"0\": [\"variables.file\"] }' \\\n  -F 0=@a.txt\n```\n\n#### Request payload\n\n```\n--------------------------cec8e8123c05ba25\nContent-Disposition: form-data; name=\"operations\"\n\n{ \"query\": \"mutation ($file: Upload!) { singleUpload(file: $file) { id } }\", \"variables\": { \"file\": null } }\n--------------------------cec8e8123c05ba25\nContent-Disposition: form-data; name=\"map\"\n\n{ \"0\": [\"variables.file\"] }\n--------------------------cec8e8123c05ba25\nContent-Disposition: form-data; name=\"0\"; filename=\"a.txt\"\nContent-Type: text/plain\n\nAlpha file content.\n\n--------------------------cec8e8123c05ba25--\n```\n\n### File list\n\n#### Operations\n\n```js\n{\n  query: `\n    mutation($files: [Upload!]!) {\n      multipleUpload(files: $files) {\n        id\n      }\n    }\n  `,\n  variables: {\n    files: [\n      File, // b.txt\n      File // c.txt\n    ]\n  }\n}\n```\n\n#### cURL request\n\n```shell\ncurl localhost:3001/graphql \\\n  -F operations='{ \"query\": \"mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }\", \"variables\": { \"files\": [null, null] } }' \\\n  -F map='{ \"0\": [\"variables.files.0\"], \"1\": [\"variables.files.1\"] }' \\\n  -F 0=@b.txt \\\n  -F 1=@c.txt\n```\n\n#### Request payload\n\n```\n--------------------------ec62457de6331cad\nContent-Disposition: form-data; name=\"operations\"\n\n{ \"query\": \"mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }\", \"variables\": { \"files\": [null, null] } }\n--------------------------ec62457de6331cad\nContent-Disposition: form-data; name=\"map\"\n\n{ \"0\": [\"variables.files.0\"], \"1\": [\"variables.files.1\"] }\n--------------------------ec62457de6331cad\nContent-Disposition: form-data; name=\"0\"; filename=\"b.txt\"\nContent-Type: text/plain\n\nBravo file content.\n\n--------------------------ec62457de6331cad\nContent-Disposition: form-data; name=\"1\"; filename=\"c.txt\"\nContent-Type: text/plain\n\nCharlie file content.\n\n--------------------------ec62457de6331cad--\n```\n\n### Batching\n\n#### Operations\n\n```js\n[\n  {\n    query: `\n      mutation($file: Upload!) {\n        singleUpload(file: $file) {\n          id\n        }\n      }\n    `,\n    variables: {\n      file: File, // a.txt\n    },\n  },\n  {\n    query: `\n      mutation($files: [Upload!]!) {\n        multipleUpload(files: $files) {\n          id\n        }\n      }\n    `,\n    variables: {\n      files: [\n        File, // b.txt\n        File, // c.txt\n      ],\n    },\n  },\n];\n```\n\n#### cURL request\n\n```shell\ncurl localhost:3001/graphql \\\n  -F operations='[{ \"query\": \"mutation ($file: Upload!) { singleUpload(file: $file) { id } }\", \"variables\": { \"file\": null } }, { \"query\": \"mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }\", \"variables\": { \"files\": [null, null] } }]' \\\n  -F map='{ \"0\": [\"0.variables.file\"], \"1\": [\"1.variables.files.0\"], \"2\": [\"1.variables.files.1\"] }' \\\n  -F 0=@a.txt \\\n  -F 1=@b.txt \\\n  -F 2=@c.txt\n```\n\n#### Request payload\n\n```\n--------------------------627436eaefdbc285\nContent-Disposition: form-data; name=\"operations\"\n\n[{ \"query\": \"mutation ($file: Upload!) { singleUpload(file: $file) { id } }\", \"variables\": { \"file\": null } }, { \"query\": \"mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }\", \"variables\": { \"files\": [null, null] } }]\n--------------------------627436eaefdbc285\nContent-Disposition: form-data; name=\"map\"\n\n{ \"0\": [\"0.variables.file\"], \"1\": [\"1.variables.files.0\"], \"2\": [\"1.variables.files.1\"] }\n--------------------------627436eaefdbc285\nContent-Disposition: form-data; name=\"0\"; filename=\"a.txt\"\nContent-Type: text/plain\n\nAlpha file content.\n\n--------------------------627436eaefdbc285\nContent-Disposition: form-data; name=\"1\"; filename=\"b.txt\"\nContent-Type: text/plain\n\nBravo file content.\n\n--------------------------627436eaefdbc285\nContent-Disposition: form-data; name=\"2\"; filename=\"c.txt\"\nContent-Type: text/plain\n\nCharlie file content.\n\n--------------------------627436eaefdbc285--\n```\n\n## Security\n\nGraphQL server authentication and security mechanisms are beyond the scope of this specification, which only covers a multipart form field structure for GraphQL requests.\n\nNote that a GraphQL multipart request has the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) `multipart/form-data`; if a browser making such a request determines it meets the criteria for a “[simple request](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests)” as defined in the [Fetch specification](https://fetch.spec.whatwg.org) for the [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (CORS) protocol, it won’t cause a [CORS preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request). GraphQL server authentication and security mechanisms must consider this to prevent [Cross-Site Request Forgery](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) (CSRF) attacks.\n\n## Implementations\n\nPull requests adding either experimental or mature implementations to these lists are welcome! ~~Strikethrough~~ means the project was renamed, deprecated, or no longer supports this spec out of the box (but might via an optional integration).\n\n### Client\n\n- [jaydenseric/graphql-react](https://github.com/jaydenseric/graphql-react) (JS: [npm](https://npm.im/graphql-react))\n- [jaydenseric/apollo-upload-client](https://github.com/jaydenseric/apollo-upload-client) (JS: [npm](https://npm.im/apollo-upload-client))\n- [jaydenseric/extract-files](https://github.com/jaydenseric/extract-files) (JS: [npm](https://npm.im/extract-files))\n- [nearform/graphql-hooks](https://github.com/nearform/graphql-hooks) (JS: [npm](https://npm.im/graphql-hooks))\n- [klis87/redux-saga-requests-graphql](https://github.com/klis87/redux-saga-requests/tree/master/packages/redux-saga-requests-graphql) (JS: [npm](https://npm.im/redux-saga-requests-graphql))\n- [imolorhe/altair](https://github.com/imolorhe/altair) (JS: [npm](https://npm.im/altair-static))\n- [haffdata/buoy](https://github.com/haffdata/buoy) (JS: [npm](https://npm.im/@buoy/client))\n- [FormidableLabs/urql](https://github.com/FormidableLabs/urql) (JS: [npm](https://npm.im/@urql/exchange-multipart-fetch))\n- [~~apollo-fetch-upload~~](https://github.com/apollographql/apollo-fetch/tree/master/packages/apollo-fetch-upload) (JS: [npm](https://npm.im/apollo-fetch-upload))\n- [GnRlLeclerc/go-graphql-client](https://github.com/GnRlLeclerc/go-graphql-client) (Go: [GitHub](https://github.com/GnRlLeclerc/go-graphql-client))\n- [apollographql/apollo-ios](https://github.com/apollographql/apollo-ios) (Swift: [CocoaPods](https://cocoapods.org/pods/Apollo))\n- [apollographql/apollo-android](https://github.com/apollographql/apollo-android) (Java: [Bintray](https://bintray.com/apollographql/android))\n- [zino-app/graphql-flutter](https://github.com/zino-app/graphql-flutter) (Dart: [Pub](https://pub.dev/packages/graphql))\n- [samirelanduk/kirjava](https://github.com/samirelanduk/kirjava) (Python: [PyPi](https://pypi.org/project/kirjava))\n- [DoctorJohn/aiogqlc](https://github.com/DoctorJohn/aiogqlc) (Python: [PyPi](https://pypi.org/project/aiogqlc))\n- [graphql-python/gql](https://github.com/graphql-python/gql) (Python: [PyPi](https://pypi.org/project/gql))\n\n### Server\n\n- [jaydenseric/graphql-upload](https://github.com/jaydenseric/graphql-upload) (JS: [npm](https://npm.im/graphql-upload))\n- [koresar/graphql-upload-minimal](https://github.com/koresar/graphql-upload-minimal) (JS: [npm](https://npm.im/graphql-upload-minimal))\n- [dotansimha/graphql-yoga](https://github.com/dotansimha/graphql-yoga) (JS: [npm](https://npm.im/@graphql-yoga/common))\n- [~~apollographql/apollo-server~~](https://github.com/apollographql/apollo-server) (JS: [npm](https://npm.im/apollo-server))\n- [~~jaydenseric/apollo-upload-server~~](https://github.com/jaydenseric/apollo-upload-server) (JS: [npm](https://npm.im/apollo-upload-server))\n- [99designs/gqlgen](https://github.com/99designs/gqlgen) (Go: [GitHub](https://github.com/99designs/gqlgen))\n- [jpascal/graphql-upload](https://github.com/jpascal/graphql-upload) (Go: [GitHub](https://github.com/jpascal/graphql-upload))\n- [jetruby/apollo_upload_server-ruby](https://github.com/jetruby/apollo_upload_server-ruby) (Ruby: [Gem](https://rubygems.org/gems/apollo_upload_server))\n- [Ecodev/graphql-upload](https://github.com/Ecodev/graphql-upload) (PHP: [Composer](https://packagist.org/packages/ecodev/graphql-upload))\n- [rebing/graphql-laravel](https://github.com/rebing/graphql-laravel) (PHP: [Composer](https://packagist.org/packages/rebing/graphql-laravel))\n- [nuwave/lighthouse](https://github.com/nuwave/lighthouse) (PHP: [Composer](https://packagist.org/packages/nuwave/lighthouse))\n- [overblog/graphql-bundle](https://github.com/overblog/GraphQLBundle) (PHP: [Composer](https://packagist.org/packages/overblog/graphql-bundle))\n- [infinityloop-dev/graphpinator](https://github.com/infinityloop-dev/graphpinator) (PHP: [Composer](https://packagist.org/packages/infinityloop-dev/graphpinator))\n- [lmcgartland/graphene-file-upload](https://github.com/lmcgartland/graphene-file-upload) (Python: [PyPi](https://pypi.org/project/graphene-file-upload))\n- [strawberry-graphql/strawberry](https://github.com/strawberry-graphql/strawberry) (Python: [PyPi](https://pypi.org/project/strawberry-graphql))\n- [graphql-java-kickstart/graphql-java-servlet](https://github.com/graphql-java-kickstart/graphql-java-servlet) (Java: [Maven](https://mvnrepository.com/artifact/com.graphql-java/graphql-java-servlet))\n- [ChilliCream/hotchocolate](https://github.com/ChilliCream/hotchocolate) (C#: [NuGet](https://www.nuget.org/packages/HotChocolate))\n- [async-graphql/async-graphql](https://github.com/async-graphql/async-graphql) (Rust: [Crates](https://crates.io/crates/async-graphql))\n","funding_links":["https://github.com/sponsors/jaydenseric"],"categories":["Others","Features"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaydenseric%2Fgraphql-multipart-request-spec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaydenseric%2Fgraphql-multipart-request-spec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaydenseric%2Fgraphql-multipart-request-spec/lists"}