fastify/fastify · critical · TypeError

FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN

FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN

Error message

schemaErrorFormatter option should be a non async function. Instead got '%s'.

What it means

Thrown by validateSchemaErrorFormatter (fastify.js:1004-1005), called both at construction (fastify.js:356) and from setSchemaErrorFormatter (fastify.js:735). It fires when `schemaErrorFormatter` is defined but `typeof schemaErrorFormatter !== 'function'`. The formatter must be a regular (non-async) function Fastify can call synchronously to turn validation errors into a single Error.

Source

Thrown at fastify.js:1005

  }

  // Most devs do not know what to do with this error.
  // In the vast majority of cases, it's a network error and/or some
  // config issue on the load balancer side.
  this.log.trace({ err }, `client ${errorLabel}`)
  // Copying standard node behavior
  // https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666

  // If the socket is not writable, there is no reason to try to send data.
  if (socket.writable) {
    socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
  }
  socket.destroy(err)
}

function validateSchemaErrorFormatter (schemaErrorFormatter) {
  if (typeof schemaErrorFormatter !== 'function') {
    throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN(typeof schemaErrorFormatter)
  } else if (schemaErrorFormatter.constructor.name === 'AsyncFunction') {
    throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN('AsyncFunction')
  }
}

/**
 * These export configurations enable JS and TS developers
 * to consume fastify in whatever way best suits their needs.
 * Some examples of supported import syntax includes:
 * - `const fastify = require('fastify')`
 * - `const { fastify } = require('fastify')`
 * - `import * as Fastify from 'fastify'`
 * - `import { fastify, TSC_definition } from 'fastify'`
 * - `import fastify from 'fastify'`
 * - `import fastify, { TSC_definition } from 'fastify'`
 */
module.exports = fastify
module.exports.errorCodes = errorCodes

View on GitHub (pinned to 7299a57d3f)

Solutions

  1. Pass a synchronous function: `fastify({ schemaErrorFormatter: (errors, dataVar) => new Error(...) })`.
  2. Omit the option to use the default formatter.
  3. If importing the formatter, verify the export name and that it resolves to a function.

Example fix

// before
const app = fastify({ schemaErrorFormatter: { format: myFormat } })

// after
const app = fastify({ schemaErrorFormatter: (errors) => new Error(errors.map(e => e.message).join(',')) })
Defensive patterns

Strategy: type-guard

Validate before calling

function validateFormatter(fn) {
  if (typeof fn !== 'function') {
    throw new TypeError('schemaErrorFormatter must be a function, got ' + typeof fn)
  }
  return fn
}
const app = fastify(opts.schemaErrorFormatter
  ? { schemaErrorFormatter: validateFormatter(opts.schemaErrorFormatter) }
  : {})

Type guard

const isFormatter = (fn) => typeof fn === 'function' && fn.constructor.name !== 'AsyncFunction'

Try / catch

try {
  app.setSchemaErrorFormatter(formatter)
} catch (err) {
  if (err.code === 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') {
    throw new TypeError('schemaErrorFormatter must be a non-async function, got ' + typeof formatter)
  }
  throw err
}

Prevention

When it happens

Trigger: Calling `fastify({ schemaErrorFormatter: {} })`, `{ schemaErrorFormatter: 'fmt' }`, or `fastify.setSchemaErrorFormatter(undefined)`. Also when the formatter is imported as undefined.

Common situations: Passing a configuration object describing a formatter rather than the function; misspelled import; copy-pasting a config snippet that referenced an unbound symbol.

Related errors


AI-assisted analysis of fastify/fastify@7299a57d3f (2026-08-03). Data as JSON: /data/errors/f87fd1847dbf9f31.json. Report an issue: GitHub.