{"record":{"id":"7f50dc7d2329f5fb","repo":"Automattic/mongoose","slug":"first-param-to-schema-plugin-must-be-a-functio","errorCode":null,"errorMessage":"First param to `schema.plugin()` must be a function, got \"${typeof fn}\"","messagePattern":"First param to `schema\\.plugin\\(\\)` must be a function, got \"(.+?)\"","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/schema.js","lineNumber":2256,"sourceCode":" *     s.plugin(schema => console.log(schema.path('name').path));\n *     mongoose.model('Test', s); // Prints 'name'\n *\n * Or with Options:\n *\n *     const s = new Schema({ name: String });\n *     s.plugin((schema, opts) => console.log(opts.text, schema.path('name').path), { text: \"Schema Path Name:\" });\n *     mongoose.model('Test', s); // Prints 'Schema Path Name: name'\n *\n * @param {Function} plugin The Plugin's callback\n * @param {object} [opts] Options to pass to the plugin\n * @param {boolean} [opts.deduplicate=false] If true, ignore duplicate plugins (same `fn` argument using `===`)\n * @see plugins https://mongoosejs.com/docs/plugins.html\n * @api public\n */\n\nSchema.prototype.plugin = function(fn, opts) {\n  if (typeof fn !== 'function') {\n    throw new MongooseError('First param to `schema.plugin()` must be a function, ' +\n      'got \"' + (typeof fn) + '\"');\n  }\n\n\n  if (opts?.deduplicate) {\n    for (const plugin of this.plugins) {\n      if (plugin.fn === fn) {\n        return this;\n      }\n    }\n  }\n  this.plugins.push({ fn: fn, opts: opts });\n\n  fn(this, opts);\n  return this;\n};\n\n/**","sourceCodeStart":2238,"sourceCodeEnd":2274,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/schema.js#L2238-L2274","documentation":"Schema#plugin() stores plugin functions so it can replay them on compiled models and discriminated schemas; the first argument must therefore be callable. Anything else - most commonly `undefined` from a broken import - is rejected on the spot, with the offending typeof shown in the message.","triggerScenarios":"`schema.plugin(undefined)` after `import plugin from 'pkg'` when the package only has a named export; passing the options object first (`schema.plugin({ deduplicate: true }, fn)`); importing a module namespace object instead of the function; destructuring the wrong name from the plugin package.","commonSituations":"A plugin package switches from default to named export across versions; TypeScript/ESM interop yields `{ default: fn }` instead of `fn`; copy-pasted plugin setup referencing a plugin never imported in that file.","solutions":["Log the value right before the call: `console.log(typeof myPlugin)` - 'undefined' or 'object' confirms an import problem.","Match the package export form: `const { plugin } = require('pkg')` vs `const plugin = require('pkg')`, or `import { plugin } from 'pkg'`.","Keep the argument order: schema.plugin(pluginFn, options)."],"exampleFix":"// before\nimport slugPlugin from 'mongoose-slug-plugin';\nschema.plugin(slugPlugin); // undefined: package exports a named `plugin`\n\n// after\nimport { plugin as slugPlugin } from 'mongoose-slug-plugin';\nschema.plugin(slugPlugin, { field: 'title' });","handlingStrategy":"type-guard","validationCode":"if (typeof myPlugin !== 'function') {\n  throw new Error(`plugin is ${typeof myPlugin} - check the import (default vs named export)`);\n}\nschema.plugin(myPlugin, opts);","typeGuard":"const isPluginFn = (fn) => typeof fn === 'function';","tryCatchPattern":null,"preventionTips":["Type plugin imports explicitly in TypeScript so default/named interop mismatches surface at compile time.","Register plugins in one place so a bad import fails once, loudly, at startup.","Keep argument order fixed: plugin function first, options second."],"tags":["plugins","imports","api-misuse"],"backgroundTag":"plugin-registration-error","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}