pinojs/pino · error · Error
exported worker is not a function
Error message
exported worker is not a function
What it means
When a transport is loaded, Pino imports the transport module and unwraps its exported builder function, tolerating up to two nested .default exports from transpilation (ESM/TS interop). If after unwrapping the export is still not a function, the transport module's export shape is invalid.
Source
Thrown at lib/transport-stream.js:53
// When bundled with pkg, an undefined error is thrown when called with realImport
// When bundled with pkg and using node v20, an ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING error is thrown when called with realImport
// More info at: https://github.com/pinojs/thread-stream/issues/143
try {
fn = realRequire(decodeURIComponent(target))
} catch {
throw error
}
} else {
throw error
}
}
// Depending on how the default export is performed, and on how the code is
// transpiled, we may find cases of two nested "default" objects.
// See https://github.com/pinojs/pino/issues/1243#issuecomment-982774762
if (typeof fn === 'object') fn = fn.default
if (typeof fn === 'object') fn = fn.default
if (typeof fn !== 'function') throw Error('exported worker is not a function')
return fn
}
View on GitHub (pinned to 5aa62305c5)
Solutions
- Export a function from the transport module: module.exports = async function (opts) { ... } or export default async function (opts) { ... }.
- If the export is an object, add a callable default export.
- Check the resolved path in transport.target points at the transport file, not an index that re-exports an object.
- If transpiled output has >2 nested defaults, restructure the export so the function is at default or module.exports directly.
- Test with node -e "const m=require('./my-transport.js'); console.log(typeof (m.default||m))" to verify it resolves to 'function'.
Example fix
// before (my-transport.js)
export default { build: async (opts) => buildStream }
// after
export default async function (opts) {
return buildStream
} Defensive patterns
Strategy: validation
Validate before calling
function validateTransportModule(target) {
const mod = require(require.resolve(target))
let fn = mod
if (typeof fn === 'object') fn = fn.default
if (typeof fn === 'object') fn = fn.default
if (typeof fn !== 'function') {
throw new TypeError(`Transport ${target} must export a function, got ${typeof fn}`)
}
return true
} Type guard
const isTransportFn = (mod) => {
let fn = mod
if (typeof fn === 'object') fn = fn.default
if (typeof fn === 'object') fn = fn.default
return typeof fn === 'function'
} Try / catch
try {
const logger = pino({ transport: { target } })
} catch (err) {
if (err.message === 'exported worker is not a function') {
console.error(`Fix export in ${target}: default-export or module.exports a function`)
}
throw err
} Prevention
- Always export the transport builder as a default export (ESM/TS) or as module.exports (CJS)
- Smoke-test the module standalone: require it and assert typeof is 'function'
- Verify transport.target resolves to the actual transport file, not a re-exporting barrel
- Add a unit test that builds the transport before relying on it in production
When it happens
Trigger: Passing transport.target pointing at a module whose export is an object, class instance, or arrow-assigned object without a callable default; e.g. module.exports = { myTransport } without .default, or a CommonJS module exporting a non-function.
Common situations: Custom TypeScript transports compiled with esModuleInterop producing nested default objects deeper than Pino's double-unwrap; writing a transport as a plain object instead of a function; wrong file path resolving to a barrel file exporting an object; forgetting module.exports = fn in CJS.
Related errors
- only one of target or targets can be specified
- unable to determine transport target for "${origin}"
- PINO_TRANSPORT_MULTI_TARGETS_LEVEL_FORMATTER
- only one of option.transport or stream can be specified
- option.transport do not allow stream, please pass to option
AI-assisted analysis of pinojs/pino@5aa62305c5 (2026-09-02).
Data as JSON: /api/errors/163be7acd23f57f7.
Report an issue: GitHub.