{"id":13487431,"url":"https://github.com/eggjs/multipart","last_synced_at":"2025-10-28T15:58:12.161Z","repository":{"id":48534454,"uuid":"64051237","full_name":"eggjs/multipart","owner":"eggjs","description":"multipart plugin for egg","archived":false,"fork":false,"pushed_at":"2025-02-03T14:51:28.000Z","size":214,"stargazers_count":168,"open_issues_count":0,"forks_count":36,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-02-03T14:51:51.842Z","etag":null,"topics":["egg","egg-multipart","egg-plugin","multipart"],"latest_commit_sha":null,"homepage":"","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/eggjs.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":"2016-07-24T06:01:40.000Z","updated_at":"2025-02-03T14:49:19.000Z","dependencies_parsed_at":"2024-11-06T12:07:10.571Z","dependency_job_id":"f8d1ed74-8121-4e06-992c-745b1096deab","html_url":"https://github.com/eggjs/multipart","commit_stats":{"total_commits":91,"total_committers":26,"mean_commits":3.5,"dds":0.7032967032967032,"last_synced_commit":"b2a96624a473bfc26573fc721d30d9eeb78428bc"},"previous_names":["eggjs/multipart"],"tags_count":40,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eggjs%2Fmultipart","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eggjs%2Fmultipart/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eggjs%2Fmultipart/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eggjs%2Fmultipart/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eggjs","download_url":"https://codeload.github.com/eggjs/multipart/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245927454,"owners_count":20695238,"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":["egg","egg-multipart","egg-plugin","multipart"],"created_at":"2024-07-31T18:00:59.185Z","updated_at":"2025-10-28T15:58:07.108Z","avatar_url":"https://github.com/eggjs.png","language":"TypeScript","readme":"# @eggjs/multipart\n\n[![NPM version][npm-image]][npm-url]\n[![Node.js CI](https://github.com/eggjs/multipart/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/multipart/actions/workflows/nodejs.yml)\n[![Test coverage][codecov-image]][codecov-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/multipart)\n\n[npm-image]: https://img.shields.io/npm/v/@eggjs/multipart.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@eggjs/multipart\n[codecov-image]: https://codecov.io/github/eggjs/multipart/coverage.svg?branch=master\n[codecov-url]: https://codecov.io/github/eggjs/multipart?branch=master\n[snyk-image]: https://snyk.io/test/npm/@eggjs/multipart/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/@eggjs/multipart\n[download-image]: https://img.shields.io/npm/dm/@eggjs/multipart.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@eggjs/multipart\n\nUse [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and\nprocess it without save to disk(using the `stream` mode).\n\nJust use `ctx.multipart()` to got file stream, then pass to image processing module such as `gm` or upload to cloud storage such as `oss`.\n\n## Whitelist of file extensions\n\nFor security, if uploading file extension is not in white list, will response as `400 Bad request`.\n\nDefault Whitelist:\n\n```js\nconst whitelist = [\n  // images\n  '.jpg', '.jpeg', // image/jpeg\n  '.png', // image/png, image/x-png\n  '.gif', // image/gif\n  '.bmp', // image/bmp\n  '.wbmp', // image/vnd.wap.wbmp\n  '.webp',\n  '.tif',\n  '.psd',\n  // text\n  '.svg',\n  '.js', '.jsx',\n  '.json',\n  '.css', '.less',\n  '.html', '.htm',\n  '.xml',\n  // tar\n  '.zip',\n  '.gz', '.tgz', '.gzip',\n  // video\n  '.mp3',\n  '.mp4',\n  '.avi',\n];\n```\n\n### fileSize\n\nThe default fileSize that multipart can accept is `10mb`. if you upload a large file, you should specify this config.\n\n```js\n// config/config.default.js\nexports.multipart = {\n  fileSize: '50mb',\n};\n```\n\n### Custom Config\n\nDeveloper can custom additional file extensions:\n\n```js\n// config/config.default.js\nexports.multipart = {\n  // will append to whilelist\n  fileExtensions: [\n    '.foo',\n    '.apk',\n  ],\n};\n```\n\nCan also **override** built-in whitelist, such as only allow png:\n\n```js\n// config/config.default.js\nexports.multipart = {\n  whitelist: [\n    '.png',\n  ],\n};\n```\n\nOr by function：\n\n```js\nexports.multipart = {\n  whitelist: (filename) =\u003e [ '.png' ].includes(path.extname(filename) || '')\n};\n```\n\n**Note: if define `whitelist`, then `fileExtensions` will be ignored.**\n\n## Examples\n\nMore examples please follow:\n\n- [Handle multipart request in `stream` mode](https://github.com/eggjs/examples/tree/master/multipart)\n- [Handle multipart request in `file` mode](https://github.com/eggjs/examples/tree/master/multipart-file-mode)\n\n## `file` mode: the easy way\n\nIf you don't know the [Node.js Stream](https://nodejs.org/dist/latest-v18.x/docs/api/stream.html) work,\nmaybe you should use the `file` mode to get started.\n\nThe usage very similar to [bodyParser](https://eggjs.org/en/basics/controller.html#body).\n\n- `ctx.request.body`: Get all the multipart fields and values, except `file`.\n- `ctx.request.files`: Contains all `file` from the multipart request, it's an Array object.\n\n**WARNING: you should remove the temporary upload files after you use it**,\nthe `async ctx.cleanupRequestFiles()` method will be very helpful.\n\n### Enable `file` mode on config\n\nYou need to set `config.multipart.mode = 'file'` to enable `file` mode:\n\n```js\n// config/config.default.js\nexports.multipart = {\n  mode: 'file',\n};\n```\n\nAfter `file` mode enable, egg will remove the old temporary files(don't include today's files) on `04:30 AM` every day by default.\n\n```js\nconfig.multipart = {\n  mode: 'file',\n  tmpdir: path.join(os.tmpdir(), 'egg-multipart-tmp', appInfo.name),\n  cleanSchedule: {\n    // run tmpdir clean job on every day 04:30 am\n    // cron style see https://github.com/eggjs/egg-schedule#cron-style-scheduling\n    cron: '0 30 4 * * *',\n    disable: false,\n  },\n};\n```\n\nDefault will use the last field which has same name, if need the all fields value, please set `allowArrayField` in config.\n\n```js\n// config/config.default.js\nexports.multipart = {\n  mode: 'file',\n  allowArrayField: true,\n};\n```\n\n### Upload One File\n\n```html\n\u003cform method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\"\u003e\n  title: \u003cinput name=\"title\" /\u003e\n  file: \u003cinput name=\"file\" type=\"file\" /\u003e\n  \u003cbutton type=\"submit\"\u003eUpload\u003c/button\u003e\n\u003c/form\u003e\n```\n\nController which hanlder `POST /upload`:\n\n```js\n// app/controller/upload.js\nconst Controller = require('egg').Controller;\nconst fs = require('mz/fs');\n\nmodule.exports = class extends Controller {\n  async upload() {\n    const { ctx } = this;\n    const file = ctx.request.files[0];\n    const name = 'egg-multipart-test/' + path.basename(file.filename);\n    let result;\n    try {\n      // process file or upload to cloud storage\n      result = await ctx.oss.put(name, file.filepath);\n    } finally {\n      // remove tmp files and don't block the request's response\n      // cleanupRequestFiles won't throw error even remove file io error happen\n      ctx.cleanupRequestFiles();\n      // remove tmp files before send response\n      // await ctx.cleanupRequestFiles();\n    }\n\n    ctx.body = {\n      url: result.url,\n      // get all field values\n      requestBody: ctx.request.body,\n    };\n  }\n};\n```\n\n### Upload Multiple Files\n\n```html\n\u003cform method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\"\u003e\n  title: \u003cinput name=\"title\" /\u003e\n  file1: \u003cinput name=\"file1\" type=\"file\" /\u003e\n  file2: \u003cinput name=\"file2\" type=\"file\" /\u003e\n  \u003cbutton type=\"submit\"\u003eUpload\u003c/button\u003e\n\u003c/form\u003e\n```\n\nController which hanlder `POST /upload`:\n\n```js\n// app/controller/upload.js\nconst Controller = require('egg').Controller;\nconst fs = require('mz/fs');\n\nmodule.exports = class extends Controller {\n  async upload() {\n    const { ctx } = this;\n    console.log(ctx.request.body);\n    console.log('got %d files', ctx.request.files.length);\n    for (const file of ctx.request.files) {\n      console.log('field: ' + file.fieldname);\n      console.log('filename: ' + file.filename);\n      console.log('encoding: ' + file.encoding);\n      console.log('mime: ' + file.mime);\n      console.log('tmp filepath: ' + file.filepath);\n      let result;\n      try {\n        // process file or upload to cloud storage\n        result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath);\n      } finally {\n        // remove tmp files and don't block the request's response\n        // cleanupRequestFiles won't throw error even remove file io error happen\n        ctx.cleanupRequestFiles([ file ]);\n      }\n      console.log(result);\n    }\n  }\n};\n```\n\n## `stream` mode: the hard way\n\nIf you're well-known about know the Node.js Stream work, you should use the `stream` mode.\n\n### Use with `for await...of`\n\n```html\n\u003cform method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\"\u003e\n  title: \u003cinput name=\"title\" /\u003e\n  file1: \u003cinput name=\"file1\" type=\"file\" /\u003e\n  file2: \u003cinput name=\"file2\" type=\"file\" /\u003e\n  \u003cbutton type=\"submit\"\u003eUpload\u003c/button\u003e\n\u003c/form\u003e\n```\n\nController which hanlder `POST /upload`:\n\n```js\n// app/controller/upload.js\nconst { Controller } = require('egg');\nconst fs = require('fs');\nconst stream = require('stream');\nconst util = require('util');\nconst { randomUUID } = require('crypto');\nconst pipeline = util.promisify(stream.pipeline);\n\nmodule.exports = class UploadController extends Controller {\n  async upload() {\n    const parts = this.ctx.multipart();\n    const fields = {};\n    const files = {};\n\n    for await (const part of parts) {\n      if (Array.isArray(part)) {\n        // fields\n        console.log('field: ' + part[0]);\n        console.log('value: ' + part[1]);\n      } else {\n        // otherwise, it's a stream\n        const { filename, fieldname, encoding, mime } = part;\n\n        console.log('field: ' + fieldname);\n        console.log('filename: ' + filename);\n        console.log('encoding: ' + encoding);\n        console.log('mime: ' + mime);\n\n        // how to handler?\n        // 1. save to tmpdir with pipeline\n        // 2. or send to oss\n        // 3. or just consume it with another for await\n\n        // WARNING: You should almost never use the origin filename as it could contain malicious input.\n        const targetPath = path.join(os.tmpdir(), randomUUID() + path.extname(filename));\n        await pipeline(part, createWriteStream(targetPath)); // use `pipeline` not `pipe`\n      }\n    }\n\n    this.ctx.body = 'ok';\n  }\n};\n```\n\n### Upload One File (DEPRECATED)\n\nYou can got upload stream by `ctx.getFileStream*()`.\n\n```html\n\u003cform method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\"\u003e\n  title: \u003cinput name=\"title\" /\u003e\n  file: \u003cinput name=\"file\" type=\"file\" /\u003e\n  \u003cbutton type=\"submit\"\u003eUpload\u003c/button\u003e\n\u003c/form\u003e\n```\n\nController which handler `POST /upload`:\n\n```js\n// app/controller/upload.js\nconst path = require('node:path');\nconst { sendToWormhole } = require('stream-wormhole');\nconst { Controller } = require('egg');\n\nmodule.exports = class extends Controller {\n  async upload() {\n    const { ctx } = this;\n    // file not exists will response 400 error\n    const stream = await ctx.getFileStream();\n    const name = 'egg-multipart-test/' + path.basename(stream.filename);\n    // process file or upload to cloud storage\n    const result = await ctx.oss.put(name, stream);\n\n    ctx.body = {\n      url: result.url,\n      // process form fields by `stream.fields`\n      fields: stream.fields,\n    };\n  }\n\n  async uploadNotRequiredFile() {\n    const { ctx } = this;\n    // file not required\n    const stream = await ctx.getFileStream({ requireFile: false });\n    let result;\n    if (stream.filename) {\n      const name = 'egg-multipart-test/' + path.basename(stream.filename);\n      // process file or upload to cloud storage\n      const result = await ctx.oss.put(name, stream);\n    } else {\n      // must consume the empty stream\n      await sendToWormhole(stream);\n    }\n\n    ctx.body = {\n      url: result \u0026\u0026 result.url,\n      // process form fields by `stream.fields`\n      fields: stream.fields,\n    };\n  }\n};\n```\n\n### Upload Multiple Files (DEPRECATED)\n\n```html\n\u003cform method=\"POST\" action=\"/upload?_csrf={{ ctx.csrf | safe }}\" enctype=\"multipart/form-data\"\u003e\n  title: \u003cinput name=\"title\" /\u003e\n  file1: \u003cinput name=\"file1\" type=\"file\" /\u003e\n  file2: \u003cinput name=\"file2\" type=\"file\" /\u003e\n  \u003cbutton type=\"submit\"\u003eUpload\u003c/button\u003e\n\u003c/form\u003e\n```\n\nController which hanlder `POST /upload`:\n\n```js\n// app/controller/upload.js\nconst Controller = require('egg').Controller;\n\nmodule.exports = class extends Controller {\n  async upload() {\n    const { ctx } = this;\n    const parts = ctx.multipart();\n    let part;\n    while ((part = await parts()) != null) {\n      if (part.length) {\n        // arrays are busboy fields\n        console.log('field: ' + part[0]);\n        console.log('value: ' + part[1]);\n        console.log('valueTruncated: ' + part[2]);\n        console.log('fieldnameTruncated: ' + part[3]);\n      } else {\n        if (!part.filename) {\n          // user click `upload` before choose a file,\n          // `part` will be file stream, but `part.filename` is empty\n          // must handler this, such as log error.\n          continue;\n        }\n        // otherwise, it's a stream\n        console.log('field: ' + part.fieldname);\n        console.log('filename: ' + part.filename);\n        console.log('encoding: ' + part.encoding);\n        console.log('mime: ' + part.mime);\n        const result = await ctx.oss.put('egg-multipart-test/' + part.filename, part);\n        console.log(result);\n      }\n    }\n    console.log('and we are done parsing the form!');\n  }\n};\n```\n\n### Support `file` and `stream` mode in the same time\n\nIf the default `mode` is `stream`, use the `fileModeMatch` options to match the request urls switch to `file` mode.\n\n```js\nconfig.multipart = {\n  mode: 'stream',\n  // let POST /upload_file request use the file mode, other requests use the stream mode.\n  fileModeMatch: /^\\/upload_file$/,\n  // or glob\n  // fileModeMatch: '/upload_file',\n};\n```\n\nNOTICE: `fileModeMatch` options only work on `stream` mode.\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=eggjs/multipart)](https://github.com/eggjs/multipart/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n","funding_links":[],"categories":["仓库"],"sub_categories":["[内置插件](https://eggjs.org/zh-cn/basics/plugin.html#%E6%8F%92%E4%BB%B6%E5%88%97%E8%A1%A8)"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feggjs%2Fmultipart","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feggjs%2Fmultipart","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feggjs%2Fmultipart/lists"}