{"id":"4ee3e82b86bba1ab","repo":"fastify/fastify","slug":"fst-err-sch-duplicate","errorCode":"FST_ERR_SCH_DUPLICATE","errorMessage":"Schema with '%s' already present!","messagePattern":"Schema with '(.+?)' already present!","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/schemas.js","lineNumber":67,"sourceCode":" * Checks whether a schema is a non-plain object.\n *\n * @param {*} schema the schema to check\n * @returns {boolean} true if schema has a custom prototype\n */\nfunction isCustomSchemaPrototype (schema) {\n  return typeof schema === 'object' && Object.getPrototypeOf(schema) !== Object.prototype\n}\n\nfunction normalizeSchema (routeSchemas, serverOptions) {\n  if (routeSchemas[kSchemaVisited]) {\n    return routeSchemas\n  }\n\n  // alias query to querystring schema\n  if (routeSchemas.query) {\n    // check if our schema has both querystring and query\n    if (routeSchemas.querystring) {\n      throw new FST_ERR_SCH_DUPLICATE('querystring')\n    }\n    routeSchemas.querystring = routeSchemas.query\n  }\n\n  generateFluentSchema(routeSchemas)\n\n  for (const key of SCHEMAS_SOURCE) {\n    const schema = routeSchemas[key]\n    if (schema && !isCustomSchemaPrototype(schema)) {\n      if (key === 'body' && schema.content) {\n        const contentProperty = schema.content\n        const keys = Object.keys(contentProperty)\n        for (let i = 0; i < keys.length; i++) {\n          const contentType = keys[i]\n          const contentSchema = contentProperty[contentType].schema\n          if (!contentSchema) {\n            throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(contentType)\n          }","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/fastify/fastify/blob/7299a57d3fdce7da49f2c6d19ba08dc673e53f22/lib/schemas.js#L49-L85","documentation":"Thrown by normalizeSchema (lib/schemas.js:67) when a route schema defines both query and querystring keys. Fastify treats query as an alias for querystring and copies it over; defining both is ambiguous, so it is rejected to prevent silent shadowing.","triggerScenarios":"fastify.get('/x', { schema: { query: { ... }, querystring: { ... } } }, handler). Copy-pasting between Fastify versions (older docs used querystring, examples now show query) and ending up with both.","commonSituations":"Migrating from older Fastify samples. Combining schemas from different team members where one used query and the other querystring. Generic schema factories that merge objects and accidentally produce both keys.","solutions":["Keep only one of query or querystring in each route schema (query is the preferred short alias).","If merging schema fragments programmatically, delete the duplicate key before passing to Fastify.","Standardise your codebase on a single key name."],"exampleFix":"// before\nfastify.get('/x', { schema: { query: { type: 'object' }, querystring: { type: 'object' } } }, handler)\n\n// after\nfastify.get('/x', { schema: { querystring: { type: 'object' } } }, handler)","handlingStrategy":"validation","validationCode":"function normalizeQueryAlias (schema) {\n  if (schema && schema.query && schema.querystring) {\n    throw new Error('Define only one of schema.query or schema.querystring')\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Standardise the codebase on a single key (querystring or query).","When merging schema fragments, delete the duplicate alias before registering.","Lint route schemas in a factory to catch the dual-key case."],"tags":["schema","querystring","duplicate","route-registration"],"analyzedSha":"7299a57d3fdce7da49f2c6d19ba08dc673e53f22","analyzedAt":"2026-08-03T17:43:01.300Z","schemaVersion":2}