@fastify/multipart

repository·main·Indexed 19 days ago

https://github.com/fastify/fastify-multipart

A Fastify plugin for parsing `multipart/form-data` content types. It provides high-performance streaming support for files and fields via `req.file()`, `req.files()`, and `req.parts()`, with options for memory accumulation, disk storage using `req.saveRequestFiles()`, and automatic body attachment. Supports JSON Schema validation, Zod integration, and @fastify/swagger documentation.

Tokens
5.9K
Snippets
23
Records
26
Agent score
19%

What's inside @fastify/multipart

  1. Understand non-file field structure when attachFieldsToBody is true

    main

    When attachFieldsToBody: true is enabled, non-file fields are transformed into a complex object before validation. A field named hello with value world will appear in req.body as:

    {
      hello: {
        fieldname: "hello",
        value: "world",
        fieldnameTruncated: false,
        valueTruncated: false,
        fields: body
      }
    }

    Important: Because this conversion happens before validation, your JSON Schema must account for this structure. For example, to validate the value property, your schema should look like:

    hello: {
      properties: {
        value: {
          type: 'string'
        }
      }
    }

    If a field has a Content-Type header starting with application/json, it will be parsed using JSON.parse.

  2. Handle file size limits and errors

    main

    If a fileSize limit is reached, the plugin can throw a fastify.multipartErrors.RequestFileTooLargeError.

    To detect if a limit was reached without catching an error, you can:

    1. Listen to data.file.on('limit').
    2. Check data.file.truncated after the stream is consumed.
    3. Call data.toBuffer() and catch the error.

    To disable the automatic throwing behavior (useful for custom error handling), set throwFileSizeLimit: false in the plugin registration or per-request options.

    // Check truncated property
    const data = await req.file()
    await pipeline(data.file, fs.createWriteStream(data.filename))
    if (data.file.truncated) {
      // Handle limit reached
    }
    
    // OR catch error
    try {
      const buffer = await data.toBuffer()
    } catch (err) {
      // error is RequestFileTooLargeError
    }
  3. Attach all fields to the request body

    main

    You can configure the plugin to automatically parse all fields and attach them to req.body.

    • attachFieldsToBody: true: Fields are attached as objects. Files are accumulated in memory as buffers. You access non-file fields via req.body.fieldName.value and files via req.body.fieldName.toBuffer().
    • attachFieldsToBody: 'keyValues': Fields are attached directly as their values. Files are attached as Buffer objects.

    Warning: If you use attachFieldsToBody without an onFile handler, files are accumulated in memory. You cannot stream them after this; you can only use .toBuffer().

    To avoid memory issues when using attachFieldsToBody, define an onFile handler to process files (e.g., stream them to disk) as they arrive.

    // Using attachFieldsToBody: true
    fastify.register(require('@fastify/multipart'), { attachFieldsToBody: true })
    
    fastify.post('/upload', async function (req, reply) {
      const uploadValue = await req.body.upload.toBuffer()
      const fooValue = req.body.foo.value
      reply.send()
    })
    
    // Using attachFieldsToBody: 'keyValues' with an onFile handler to decode data
    async function onFile(part) {
      const buff = await part.toBuffer()
      const decoded = Buffer.from(buff.toString(), 'base64').toString()
      part.value = decoded 
    }
    fastify.register(require('@fastify/multipart'), { attachFieldsToBody: 'keyValues', onFile })
  4. Validate multipart bodies with JSON Schema (keyValues mode)

    main

    When attachFieldsToBody is set to 'keyValues', the multipart body is parsed similarly to application/json or application/x-www-form-urlencoded. In this mode, uploaded files are attached to the body as Buffer objects. This allows you to use standard JSON Schema validation on the req.body.

    fastify.register(require('@fastify/multipart'), { attachFieldsToBody: 'keyValues' })
    
    fastify.post('/upload/files', {
      schema: {
        consumes: ['multipart/form-data'],
        body: {
          type: 'object',
          required: ['myFile'],
          properties: {
            // file that gets decoded to string
            myFile: {
              type: 'object',
            },
            hello: {
              type: 'string',
              enum: ['world']
            }
          }
        }
      }
    }, function (req, reply) {
      console.log({ body: req.body })
      reply.send('done')
    })
  5. Validate multipart fields using a shared JSON Schema

    main

    By setting attachFieldsToBody: true and providing a sharedSchemaId (a string ID), @fastify/multipart adds a shared JSON Schema to your Fastify instance. This schema can be used via $ref to validate the metadata of uploaded files (encoding, filename, limit, and mimetype).

    const opts = {
      attachFieldsToBody: true,
      sharedSchemaId: '#mySharedSchema'
    }
    fastify.register(require('@fastify/multipart'), opts)
    
    fastify.post('/upload/files', {
      schema: {
        consumes: ['multipart/form-data'],
        body: {
          type: 'object',
          required: ['myField'],
          properties: {
            // field that uses the shared schema
            myField: { $ref: '#mySharedSchema'},
            // or another field that uses the shared schema
            myFiles: { type: 'array', items: fastify.getSchema('mySharedSchema') },
            // or a field that doesn't use the shared schema
            hello: {
              properties: {
                value: {
                  type: 'string',
                  enum: ['male']
                }
              }
            }
          }
        }
      }
    }, function (req, reply) {
      console.log({ body: req.body })
      reply.send('done')
    })
  6. Validate multipart bodies with Zod

    main

    To use Zod for multipart validation:

    1. Install and configure fastify-type-provider-zod.
    2. Set attachFieldsToBody: true when registering @fastify/multipart.
    3. (Optional) Use attachFieldsToBody: 'keyValues' to avoid field preprocessing, but note that files will be received as Buffer objects if they are not text/plain.
  7. Integrate @fastify/multipart with @fastify/swagger

    main

    To enable Swagger/OpenAPI documentation for multipart files, you must:

    1. Add require('@fastify/multipart').ajvFilePlugin to your Fastify instance's ajv.plugins configuration.
    2. Use the isFile: true property in your JSON Schema for file fields.
    const fastify = require('fastify')({
     // ...
      ajv: {
        // Adds the file plugin to help @fastify/swagger schema generation
        plugins: [require('@fastify/multipart').ajvFilePlugin]
      }
    })
    
    fastify.register(require("@fastify/multipart"), {
      attachFieldsToBody: true,
    });
    
    fastify.post(
      "/upload/files",
      {
        schema: {
          consumes: ["multipart/form-data"],
          body: {
            type: "object",
            required: ["myField"],
            properties: {
              myField: { isFile: true },
            },
          },
        },
      },
      function (req, reply) {
        console.log({ body: req.body });
        reply.send("done");
      }
    );
  8. Configure multipart limits

    main

    When registering the plugin, you can pass a limits object to configure constraints on the uploaded content. These options are passed directly to @fastify/busboy.

    fastify.register(require('@fastify/multipart'), {
      limits: {
        fieldNameSize: 100, // Max field name size in bytes
        fieldSize: 100,     // Max field value size in bytes
        fields: 10,         // Max number of non-file fields
        fileSize: 1000000,  // For multipart forms, the max file size in bytes
        files: 1,           // Max number of file fields
        headerPairs: 2000,  // Max number of header key=>value pairs
        parts: 1000         // For multipart forms, the max number of parts (fields + files)
      }
    });
  9. Register @fastify/multipart as a plugin

    main

    To enable multipart parsing in your Fastify application, register the @fastify/multipart plugin. You can pass an options object to configure limits such as parts, fileSize, files, and fields.

    const fastify = require('fastify')()
    const multipart = require('@fastify/multipart')
    
    fastify.register(multipart, {
      limits: {
        fileSize: 1000000 // 1MB
      }
    })
  10. Upload files to disk with req.saveRequestFiles()

    main

    Use req.saveRequestFiles() to automatically store all files in the operating system's default temporary directory. The files are removed as soon as the response ends. This method returns an object containing files (an array of file info) and values (parsed non-file fields).

    fastify.post('/upload/files', async function (req, reply) {
      const { files, values } = await req.saveRequestFiles()
      // files[0].filepath contains the temp path
      // values.someField.value contains non-file fields
      reply.send()
    })
  11. Basic usage with req.file()

    main

    Use req.file() to process a single file from a multipart request. The returned object contains the file stream, field information, and other parsed parts.

    Important: You must consume the file stream (e.g., via pipeline or toBuffer()), otherwise the promise will never fulfill.

    Field Order Warning: Because multipart is processed serially, non-file fields should be placed before file fields in the form data to ensure they are available in data.fields before the file stream is consumed.

    const fastify = require('fastify')()
    const fs = require('node:fs')
    const { pipeline } = require('node:stream/promises')
    
    fastify.register(require('@fastify/multipart'))
    
    fastify.post('/', async function (req, reply) {
      const data = await req.file()
    
      data.file // stream
      data.fields // other parsed parts
      data.fieldname
      data.filename
      data.encoding
      data.mimetype
    
      // Option 1: Stream to disk
      await pipeline(data.file, fs.createWriteStream(data.filename))
    
      // Option 2: Accumulate in memory
      // const buffer = await data.toBuffer()
    
      reply.send()
    })