{"id":13455189,"url":"https://github.com/octet-stream/form-data-encoder","last_synced_at":"2025-05-16T14:04:41.680Z","repository":{"id":41609980,"uuid":"366168398","full_name":"octet-stream/form-data-encoder","owner":"octet-stream","description":"Encode FormData content into the multipart/form-data format","archived":false,"fork":false,"pushed_at":"2024-11-18T10:12:36.000Z","size":837,"stargazers_count":26,"open_issues_count":2,"forks_count":12,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-03T01:03:41.131Z","etag":null,"topics":["encoder","file-upload","form-data","formdata","spec-compliant","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/form-data-encoder","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/octet-stream.png","metadata":{"files":{"readme":"readme.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"license","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}},"created_at":"2021-05-10T20:31:42.000Z","updated_at":"2025-01-14T05:43:45.000Z","dependencies_parsed_at":"2024-11-05T23:36:18.344Z","dependency_job_id":"4fda9754-0ea4-4137-8de6-4397966679fc","html_url":"https://github.com/octet-stream/form-data-encoder","commit_stats":{"total_commits":328,"total_committers":6,"mean_commits":"54.666666666666664","dds":0.03658536585365857,"last_synced_commit":"5aa6f9f5285f992a9b0539e3eb4b59d305b86e07"},"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/octet-stream%2Fform-data-encoder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/octet-stream%2Fform-data-encoder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/octet-stream%2Fform-data-encoder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/octet-stream%2Fform-data-encoder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/octet-stream","download_url":"https://codeload.github.com/octet-stream/form-data-encoder/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254040246,"owners_count":22004485,"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":["encoder","file-upload","form-data","formdata","spec-compliant","typescript"],"created_at":"2024-07-31T08:01:02.223Z","updated_at":"2025-05-16T14:04:41.663Z","avatar_url":"https://github.com/octet-stream.png","language":"TypeScript","readme":"# form-data-encoder\n\nEncode `FormData` content into the `multipart/form-data` format\n\n[![Code Coverage](https://codecov.io/github/octet-stream/form-data-encoder/coverage.svg?branch=main)](https://codecov.io/github/octet-stream/form-data-encoder?branch=main)\n[![CI](https://github.com/octet-stream/form-data-encoder/workflows/CI/badge.svg)](https://github.com/octet-stream/form-data-encoder/actions/workflows/ci.yml)\n[![ESLint](https://github.com/octet-stream/form-data-encoder/workflows/ESLint/badge.svg)](https://github.com/octet-stream/form-data-encoder/actions/workflows/eslint.yml)\n[![TypeScript Types](https://github.com/octet-stream/form-data-encoder/actions/workflows/typescript.yml/badge.svg)](https://github.com/octet-stream/form-data-encoder/actions/workflows/typescript.yml)\n\n## Requirements\n\n- Node.js v18.0.0 or higher;\n- Runtime should support `TextEncoder`, `TextDecoder`, `WeakMap`, `WeakSet` and async generator functions;\n- For TypeScript users: tsc v4.3 or higher.\n\n## Installation\n\nYou can install this package using npm:\n\n```sh\nnpm install form-data-encoder\n```\n\nOr yarn:\n\n```sh\nyarn add form-data-encoder\n```\n\nOr pnpm:\n\n```sh\npnpm add form-data-encoder\n```\n\n## Usage\n\n1. To start the encoding process, you need to create a new Encoder instance with the FormData you want to encode:\n\n```js\nimport {Readable} from \"stream\"\n\nimport {FormData, File} from \"formdata-node\"\nimport {FormDataEncoder} from \"form-data-encoder\"\n\nimport fetch from \"node-fetch\"\n\nconst form = new FormData()\n\nform.set(\"greeting\", \"Hello, World!\")\nform.set(\"file\", new File([\"On Soviet Moon landscape see binoculars through YOU\"], \"file.txt\"))\n\nconst encoder = new FormDataEncoder(form)\n\nconst options = {\n  method: \"post\",\n\n  // Set request headers provided by the Encoder.\n  // The `headers` property has `Content-Type` and `Content-Length` headers.\n  headers: encoder.headers,\n\n  // Create a Readable stream from the Encoder.\n  // You can omit usage of `Readable.from` for HTTP clients whose support async iterables in request body.\n  // The Encoder will yield FormData content portions encoded into the multipart/form-data format as node-fetch consumes the stream.\n  body: Readable.from(encoder.encode()) // or just Readable.from(encoder)\n}\n\nconst response = await fetch(\"https://httpbin.org/post\", options)\n\nconsole.log(await response.json())\n```\n\n2. Encoder support different spec-compatible FormData implementations. Let's try it with [`formdata-polyfill`](https://github.com/jimmywarting/FormData):\n\n```js\nimport {Readable} from \"stream\"\n\nimport {FormDataEncoder} from \"form-data-encoder\"\nimport {FormData} from \"formdata-polyfill/esm-min.js\"\nimport {File} from \"fetch-blob\" // v3\n\nconst form = new FormData()\n\nform.set(\"field\", \"Some value\")\nform.set(\"file\", new File([\"File content goes here\"], \"file.txt\"))\n\nconst encoder = new FormDataEncoder(form)\n\nconst options = {\n  method: \"post\",\n  headers: encoder.headers,\n  body: Readable.from(encoder)\n}\n\nawait fetch(\"https://httpbin.org/post\", options)\n```\n\n3. Because the Encoder is iterable (it has both Symbol.asyncIterator and Symbol.iterator methods), you can use it with different targets. Let's say you want to convert FormData content into `Blob`, for that you can write a function like this:\n\n```js\nimport {Readable} from \"stream\"\n\nimport {FormDataEncoder} from \"form-data-encoder\"\nimport {FormData, File, Blob} from \"formdata-node\"\nimport {fileFromPath} from \"formdata-node/file-from-path\"\n\nimport fetch from \"node-fetch\"\n\nconst form = new FormData()\n\nform.set(\"field\", \"Just a random string\")\nform.set(\"file\", new File([\"Using files is class amazing\"], \"file.txt\"))\nform.set(\"fileFromPath\", await fileFromPath(\"path/to/a/file.txt\"))\n\n// Note 1: When using with native Blob or fetch-blob@2 you might also need to generate boundary string for your FormDataEncoder instance\n// because Blob will lowercase value of the `type` option and default boundary generator produces a string with both lower and upper cased alphabetical characters. Math.random() should be enough to fix this:\n// const encoder = new FormDataEncoder(form, String(Math.random()))\nconst encoder = new FormDataEncoder(form)\n\nconst options = {\n  method: \"post\",\n\n  // Note 2: To use this approach with fetch-blob@2 you probably gonna need to convert the encoder parts output to an array first:\n  // new Blob([...encoder], {type: encoder.contentType})\n  body: new Blob(encoder, {type: encoder.contentType})\n}\n\nconst response = await fetch(\"https://httpbin.org/post\", options)\n\nconsole.log(await response.json())\n```\n\n4. Here's FormData to Blob conversion with async-iterator approach:\n\n```js\nimport {FormData} from \"formdata-polyfill/esm-min.js\"\nimport {FormDataEncoder} from \"form-data-encoder\"\nimport {blobFrom} from \"fetch-blob/from.js\"\n\nimport Blob from \"fetch-blob\"\nimport fetch from \"node-fetch\"\n\n// This approach may require much more RAM compared to the previous one, but it works too.\nasync function toBlob(form) {\n  const encoder = new Encoder(form)\n  const chunks = []\n\n  for await (const chunk of encoder) {\n    chunks.push(chunk)\n  }\n\n  return new Blob(chunks, {type: encoder.contentType})\n}\n\nconst form = new FormData()\n\nform.set(\"name\", \"John Doe\")\nform.set(\"avatar\", await blobFrom(\"path/to/an/avatar.png\"), \"avatar.png\")\n\nconst options = {\n  method: \"post\",\n  body: await toBlob(form)\n}\n\nawait fetch(\"https://httpbin.org/post\", options)\n```\n\n5. Another way to convert FormData parts to blob using `form-data-encoder` is making a Blob-ish class:\n\n```js\nimport {Readable} from \"stream\"\n\nimport {FormDataEncoder} from \"form-data-encoder\"\nimport {FormData} from \"formdata-polyfill/esm-min.js\"\nimport {blobFrom} from \"fetch-blob/from.js\"\n\nimport Blob from \"fetch-blob\"\nimport fetch from \"node-fetch\"\n\nclass BlobDataItem {\n  constructor(encoder) {\n    this.#encoder = encoder\n    this.#size = encoder.headers[\"Content-Length\"]\n    this.#type = encoder.headers[\"Content-Type\"]\n  }\n\n  get type() {\n    return this.#type\n  }\n\n  get size() {\n    return this.#size\n  }\n\n  stream() {\n    return Readable.from(this.#encoder)\n  }\n\n  get [Symbol.toStringTag]() {\n    return \"Blob\"\n  }\n}\n\nconst form = new FormData()\n\nform.set(\"name\", \"John Doe\")\nform.set(\"avatar\", await blobFrom(\"path/to/an/avatar.png\"), \"avatar.png\")\n\nconst encoder = new FormDataEncoder(form)\n\n// Note that node-fetch@2 performs more strictness tests for Blob objects, so you may need to do extra steps before you set up request body (like, maybe you'll need to instaniate a Blob with BlobDataItem as one of its blobPart)\nconst blob = new BlobDataItem(encoder) // or new Blob([new BlobDataItem(encoder)], {type: encoder.contentType})\n\nconst options = {\n  method: \"post\",\n  body: blob\n}\n\nawait fetch(\"https://httpbin.org/post\", options)\n```\n\n6. In this example we will pull FormData content into the ReadableStream:\n\n```js\n // This module is only necessary when you targeting Node.js or need web streams that implement Symbol.asyncIterator\nimport {ReadableStream} from \"web-streams-polyfill/ponyfill/es2018\"\n\nimport {FormDataEncoder} from \"form-data-encoder\"\nimport {FormData} from \"formdata-node\"\n\nimport fetch from \"node-fetch\"\n\nfunction toReadableStream(encoder) {\n  const iterator = encoder.encode()\n\n  return new ReadableStream({\n    async pull(controller) {\n      const {value, done} = await iterator.next()\n\n      if (done) {\n        return controller.close()\n      }\n\n      controller.enqueue(value)\n    }\n  })\n}\n\nconst form = new FormData()\n\nform.set(\"field\", \"My hovercraft is full of eels\")\n\nconst encoder = new FormDataEncoder(form)\n\nconst options = {\n  method: \"post\",\n  headers: encoder.headers,\n  body: toReadableStream(encoder)\n}\n\n// Note that this example requires `fetch` to support Symbol.asyncIterator, which node-fetch lacks of (but will support eventually)\nawait fetch(\"https://httpbin.org/post\", options)\n```\n\n7. Speaking of async iterables - if HTTP client supports them, you can use encoder like this:\n\n```js\nimport {FormDataEncoder} from \"form-data-encoder\"\nimport {FormData} from \"formdata-node\"\n\nimport fetch from \"node-fetch\"\n\nconst form = new FormData()\n\nform.set(\"field\", \"My hovercraft is full of eels\")\n\nconst encoder = new FormDataEncoder(form)\n\nconst options = {\n  method: \"post\",\n  headers: encoder.headers,\n  body: encoder\n}\n\nawait fetch(\"https://httpbin.org/post\", options)\n```\n\n8. ...And for those client whose supporting form-data-encoder out of the box, the usage will be much, much more simpler:\n\n```js\nimport {FormData} from \"formdata-node\" // Or any other spec-compatible implementation\n\nimport fetch from \"node-fetch\"\n\nconst form = new FormData()\n\nform.set(\"field\", \"My hovercraft is full of eels\")\n\nconst options = {\n  method: \"post\",\n  body: form\n}\n\n// Note that node-fetch does NOT support form-data-encoder\nawait fetch(\"https://httpbin.org/post\", options)\n```\n\n## API\n\n### `class FormDataEncoder`\n\n##### `constructor(form[, boundary, options]) -\u003e {FormDataEncoder}`\n\n  - **{FormDataLike}** form - FormData object to encode. This object must be a spec-compatible FormData implementation.\n  - **{string}** [boundary] - An optional boundary string that will be used by the encoder. If there's no boundary string is present, FormDataEncoder will generate it automatically.\n  - **{object}** [options] - FormDataEncoder options.\n  - **{boolean}** [options.enableAdditionalHeaders = false] - When enabled, the encoder will emit additional per part headers, such as `Content-Length`. Please note that the web clients do not include these, so when enabled this option might cause an error if `multipart/form-data` does not consider additional headers.\n\nCreates a `multipart/form-data` encoder.\n\n#### Instance properties\n\n##### `boundary -\u003e {string}`\n\nReturns boundary string.\n\n##### `contentType -\u003e {string}`\n\nReturns Content-Type header.\n\n##### `contentLength -\u003e {string}`\n\nReturn Content-Length header.\n\n##### `headers -\u003e {object}`\n\nReturns headers object with Content-Type and Content-Length header.\n\n#### Instance methods\n\n##### `values() -\u003e {Generator\u003cUint8Array | FileLike, void, undefined\u003e}`\n\nCreates an iterator allowing to go through form-data parts (with metadata).\nThis method **will not** read the files and **will not** split values big into smaller chunks.\n\n##### `encode() -\u003e {AsyncGenerator\u003cUint8Array, void, undefined\u003e}`\n\nCreates an async iterator allowing to perform the encoding by portions.\nThis method reads through files and splits big values into smaller pieces (65536 bytes per each).\n\n##### `[Symbol.iterator]() -\u003e {Generator\u003cUint8Array | FileLike, void, undefined\u003e}`\n\nAn alias for `Encoder#values()` method.\n\n##### `[Symbol.asyncIterator]() -\u003e {AsyncGenerator\u003cUint8Array, void, undefined\u003e}`\n\nAn alias for `Encoder#encode()` method.\n\n### `isFile(value) -\u003e {boolean}`\n\nCheck if a value is File-ish object.\n\n  - **{unknown}** value - a value to test\n\n### `isFormData(value) -\u003e {boolean}`\n\nCheck if a value is FormData-ish object.\n\n  - **{unknown}** value - a value to test\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foctet-stream%2Fform-data-encoder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foctet-stream%2Fform-data-encoder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foctet-stream%2Fform-data-encoder/lists"}