Title: Client form bodies ignore `encoding` style/explode, so arrays are always sent as `name[0]=…` · Issue #2604 · oapi-codegen/oapi-codegen · GitHub
Open Graph Title: Client form bodies ignore `encoding` style/explode, so arrays are always sent as `name[0]=…` · Issue #2604 · oapi-codegen/oapi-codegen
X Title: Client form bodies ignore `encoding` style/explode, so arrays are always sent as `name[0]=…` · Issue #2604 · oapi-codegen/oapi-codegen
Description: Summary For application/x-www-form-urlencoded request bodies, the generated client encodes array properties as indexed keys (topic[0]=news&topic[1]=releases), even when the spec declares style: form, explode: true for the property. Per t...
Open Graph Description: Summary For application/x-www-form-urlencoded request bodies, the generated client encodes array properties as indexed keys (topic[0]=news&topic[1]=releases), even when the spec declares style: for...
X Description: Summary For application/x-www-form-urlencoded request bodies, the generated client encodes array properties as indexed keys (topic[0]=news&topic[1]=releases), even when the spec declares style:...
Opengraph URL: https://github.com/oapi-codegen/oapi-codegen/issues/2604
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Client form bodies ignore `encoding` style/explode, so arrays are always sent as `name[0]=…`","articleBody":"### Summary\n\nFor `application/x-www-form-urlencoded` request bodies, the generated client encodes array properties as indexed keys (`topic[0]=news&topic[1]=releases`), even when the spec declares `style: form, explode: true` for the property. Per the OpenAPI 3.0 [Encoding Object](https://spec.openapis.org/oas/v3.0.3#encoding-object), that serializes as repeated keys: `topic=news&topic=releases`. A server that reads repeated keys sees no `topic` at all.\n\nThis looks like the same problem as #1575, which was closed as a JSON body question; the body there was form-encoded too.\n\n### Reproduction\n\n`openapi.yaml`:\n\n```yaml\nopenapi: 3.0.3\ninfo:\n title: repro\n version: 1.0.0\npaths:\n /subscriptions:\n post:\n operationId: subscribe\n requestBody:\n required: true\n content:\n application/x-www-form-urlencoded:\n schema:\n type: object\n required: [email, topic]\n properties:\n email:\n type: string\n topic:\n type: array\n items:\n type: string\n encoding:\n topic:\n style: form\n explode: true\n responses:\n '204':\n description: Subscribed\n```\n\n`cfg.yaml`:\n\n```yaml\npackage: api\ngenerate:\n models: true\n client: true\noutput: api/api.gen.go\n```\n\n`main.go`:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\n\t\"repro/api\"\n)\n\nfunc main() {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tbody, _ := io.ReadAll(r.Body)\n\t\tfmt.Printf(\"body: %s\\n\", body)\n\t\tw.WriteHeader(http.StatusNoContent)\n\t}))\n\tdefer server.Close()\n\n\tclient, _ := api.NewClient(server.URL)\n\t_, _ = client.SubscribeWithFormdataBody(context.Background(), api.SubscribeFormdataRequestBody{\n\t\tEmail: \"jane@example.com\",\n\t\tTopic: []string{\"news\", \"releases\"},\n\t})\n}\n```\n\n```\n$ go mod init repro\n$ go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.8.0 -config cfg.yaml openapi.yaml\n$ go mod tidy\n$ go run .\nbody: email=jane%40example.com&topic%5B0%5D=news&topic%5B1%5D=releases\n```\n\nExpected `body: email=jane%40example.com&topic=news&topic=releases`.\n\n### Cause\n\n- The client template always calls `runtime.MarshalForm(body, nil)` (`pkg/codegen/templates/client.tmpl`), so the spec's `encoding` never reaches the runtime.\n- `runtime.MarshalForm` only reads `RequestBodyEncoding.ContentType`. `Style` and `Explode` are unused, and `marshalFormImpl` always writes slice elements as `name[i]`.\n\n`runtime.BindForm` accepts repeated keys, `name[]` and `name[i]`, so a generated client works against a generated server either way. The mismatch only shows up against other servers.\n\n### Proposal\n\nHonour `style`/`explode` when the spec declares them explicitly: the generator passes the operation's `encoding` to `MarshalForm` instead of `nil`, and `MarshalForm` writes repeated keys for arrays with `style: form, explode: true`. Bodies without an explicit `encoding` keep today's output, so the change isn't breaking.\n\nI can open PRs for both oapi-codegen and runtime if this direction sounds right.\n\noapi-codegen v2.8.0, runtime v1.7.0, Go 1.26.8\n","author":{"url":"https://github.com/kahlstrm","@type":"Person","name":"kahlstrm"},"datePublished":"2026-10-15T12:54:17Z","url":"https://github.com/oapi-codegen/oapi-codegen/issues/2604"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:d9bae1ce-97a4-fe53-d2df-9a73f683127d |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | E868:D5CEE:4012893:54C71DD:6AF9BAC1 |
| html-safe-nonce | 94ed09cc46be3d8c69d1177adf55c40ba943f46d49c346974cd1695f8716e7db |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFODY4OkQ1Q0VFOjQwMTI4OTM6NTRDNzFERDo2QUY5QkFDMSIsInZpc2l0b3JfaWQiOiI2MTU3MjY5NzM0NDMwMTk0MzY5IiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 451cecc352a551787597076675d084c4147162a99f85aade5e988b8926b37611 |
| hovercard-subject-tag | issue:5866503926 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/oapi-codegen/oapi-codegen/2604/issue_layout |
| twitter:image | https://opengraph.githubassets.com/76305919c0e1876e1bcc93bab9b8b3816caac9743919de4ba3d59874407e0d02/oapi-codegen/oapi-codegen/issues/2604 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/76305919c0e1876e1bcc93bab9b8b3816caac9743919de4ba3d59874407e0d02/oapi-codegen/oapi-codegen/issues/2604 |
| og:image:alt | Summary For application/x-www-form-urlencoded request bodies, the generated client encodes array properties as indexed keys (topic[0]=news&topic[1]=releases), even when the spec declares style: for... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | kahlstrm |
| hostname | github.com |
| expected-hostname | github.com |
| None | 612d3e6ffd693e7ea8d9fcf452370995872baef31d825b1fedef704cd3d24ac7 |
| turbo-cache-control | no-preview |
| go-import | github.com/oapi-codegen/oapi-codegen git https://github.com/oapi-codegen/oapi-codegen.git |
| octolytics-dimension-user_id | 142752710 |
| octolytics-dimension-user_login | oapi-codegen |
| octolytics-dimension-repository_id | 173009358 |
| octolytics-dimension-repository_nwo | oapi-codegen/oapi-codegen |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 173009358 |
| octolytics-dimension-repository_network_root_nwo | oapi-codegen/oapi-codegen |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 072e34035c13c31543fba9f025cbbbda4210dae4 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width