ErrLookup › Background articles › TypeError: Wrong Value Type at the API Boundary
TypeError: Wrong Value Type at the API Boundary
TypeError is the built-in exception both Python and JavaScript use when a value is not the type an operation expects, and in these libraries it surfaces less as a language quirk than as a deliberate fail-fast guard. A factory refuses a non-object config, a hook loop rejects a bad return value, a validator catches a constraint placed on the wrong field type. Developers meet it the moment they hand an API a value that breaks its documented shape: at construction, during request handling, or while a schema is being built.
Distilled from 1,068 documented records across 54 repositories.
Background
TypeError originates in the language runtime (ECMAScript's TypeError, Python's built-in TypeError), but the records show libraries raising it deliberately as an API-contract signal. Rather than letting a wrong-typed argument flow into code that would later crash with a cryptic AttributeError or corrupt state silently, the library performs an explicit typeof or isinstance check at the boundary and throws TypeError naming the expected shape. Multer's options guard rejects anything that is not a non-null object so its constructor can safely dereference options.storage and options.limits; aiohttp's AppRunner asserts isinstance(app, Application) before every downstream step assumes a real Application; Django's SpatialReference dispatches on whether the input is a str, int, or SRS pointer; n8n's assertPlainObject rejects null, arrays, and primitives before a builder spreads an argument's keys. The shared intent is fail-fast with a descriptive message rather than a downstream surprise.
From the caller's side the throw is usually synchronous and local. It fires at the call site (multer(...), AppRunner(app), model_dump(exclude=...)) or during an early lifecycle step such as schema build, config finalization, or migration authoring. A subset fire during request handling: Koa's app.onerror re-throws when middleware throws or rejects with a non-Error value, got's hook loops check each hook's return value against a strict contract, and node-fetch's formData() refuses a response whose content-type is not multipart. In nearly every case the message identifies the offending value's type or the constraint that was violated, so the fix is almost always at the call site rather than inside library internals.
The family splits into several shapes that behave differently. Pure type guards (multer, aiohttp, mysql2's ssl check, Django's SRS dispatch, n8n's builder) reject a value of the wrong runtime type. Contract enforcers (the got hook family, koa's onerror) police the return value or thrown value of an extension point. Security allow-lists use TypeError to block injection vectors: knex's operator formatter permits only a fixed list of SQL operators, ESLint rejects rules whose declared languages do not include the file's language, and got refuses non-native FormData. Schema-build mismatches catch annotations that are statically incompatible: pydantic rejects string constraints on non-string fields and numeric constraints on string fields, and Sequelize throws when a validate key does not resolve to a function. A few records are misleading or unreachable: node-fetch reports 'Failed to fetch' for what is really a content-type mismatch, axios carries a duplicate guard that current control flow never reaches, and pydantic's exclude message renders the raw class object rather than its clean name.
Whether a given misuse is reported as TypeError versus a library-specific subclass is library-specific, and the records disagree on strictness. got forbids any body on GET and HEAD by default for RFC alignment while other HTTP clients are more permissive; multer explicitly excludes null even though typeof null === 'object'; got accepts boolean true for ssl but mysql2 throws on truthy non-object ssl values. Some guards are essentially defensive and never fire under normal control flow (axios), while others are load-bearing security controls (knex). Treat the message as a strong hint, but confirm the exact guard against the library's source when the behavior is surprising.
Common causes
- Non-object passed where an object is required.Factories and constructors such as multer(), AppRunner(), and n8n's builder helpers check that a config argument is a non-null object and throw if it is a string, number, null, array, or primitive. The guard exists so downstream code can safely dereference the object's fields without a later cryptic failure.
- Hook or handler returns the wrong type.Lifecycle hooks in got (afterResponse, beforeError, beforeCache, beforeRetry) and thrown values in Koa middleware are checked against a strict contract. Forgetting to return the response, returning a string instead of an Error, declaring a sync hook as async, or throwing a primitive all trip a TypeError.
- Constraint applied to an incompatible field type.Pydantic rejects string constraints like pattern or to_lower on non-string fields and numeric constraints on string fields; Sequelize throws when a validate key does not resolve to a validator.js function. The mismatch is between the annotation and what the schema type can actually enforce.
- Value outside a security or validation allow-list.Knex permits only an allow-list of SQL operators to prevent operator injection, ESLint rejects rules whose declared meta.languages omit the file's language, and got refuses non-native FormData polyfills. These TypeErrors guard correctness or safety rather than just type shape.
- List or tuple used where a set or mapping is required.Pydantic's include/exclude serialization API accepts only an AbstractSet or a Mapping; passing a list or tuple triggers _coerce_items during model_dump. The fix is to wrap the value with set() or restructure nested excludes as dicts whose leaves are sets or Ellipsis.
- Runtime or environment too old for the feature.Required or NotRequired on typing.TypedDict needs Python 3.11 or later, native FormData needs Node 18 or later, and deferred import cycles surface only under lazyCompilation. The TypeError reflects a capability the current runtime cannot provide, not a malformed argument.
- Unresolvable forward reference or self-referential value.Pydantic raises TypeError when a string annotation cannot be evaluated against the module namespace, and jQuery rejects a Deferred resolved with its own promise to enforce the Promises/A+ no-self-resolution rule. Both are correctness rules the library refuses to silently relax.
- Misleading message hides the real cause.node-fetch's 'Failed to fetch' from formData() is actually a content-type mismatch with the response, and axios carries an unreachable duplicate guard whose message never fires under current control flow. The literal text can point away from the actual problem.
What usually fixes it
- Match the documented shape at the call site. Pass object literals to factories, set literals to include/exclude, and the documented class instance to isinstance-guarded constructors. Avoid forwarding untyped variables; build the value in the same scope where the call happens.
- Add compile-time types and lint. Annotate hook return types and builder arguments in TypeScript so a missing return or wrong shape is a compile error, run tsc or mypy in CI, and use no-throw-literal lint rules to ban throwing primitives that would later crash Koa's onerror.
- Normalize external input before passing it in. JSON.parse config strings before handing them to a builder, coerce float EPSG codes to int for Django's SpatialReference, run set() over array leaves loaded from JSON for pydantic excludes, and sanitize env-derived ssl values through an explicit helper.
- Honor hook contracts: return the expected value, mutate in place. Return the response from afterResponse, an Error from beforeError, undefined or false from beforeCache, and regenerate a fresh stream inside beforeRetry. Use ctx.throw() or new Error() so Koa always receives a real Error instance.
- Cross-check constraints and allow-lists against the source. Keep a constraint-to-type compatibility table for schema annotations, verify validator names against the validator.js version bundled with your Sequelize release, use only whitelisted SQL operators in knex, and scope language-specific ESLint rules with a files glob.
- Inspect deeper when the message feels off. Check the content-type header before calling response.formData(), confirm which guard actually fires in axios (the reachable one is 'target must be an object'), and read the library source for the exact check when the message does not match the symptom.
Go deeper
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Documented occurrences
- Unexpected type of exclude value {class_name}(pydantic/pydantic)
- Expected object for argument options(expressjs/multer)
- Unable to apply constraint '{constraint}' to supplied value {value} for schema of type '{schema_type}'(pydantic/pydantic)
- The operator "${value}" is not permitted(knex/knex)
- SSL profile must be an object, instead it's a ${typeof this.ssl}(sidorares/node-mysql2)
- non-error thrown: %j(koajs/koa)
- The `afterResponse` hook returned an invalid value(sindresorhus/got)
- AddConstraintNotValid.constraint must be a check constraint.(django/django)
- `@validator(..., each_item=True)` cannot be applied to fields with a schema of {schema["type"]}(pydantic/pydantic)
- Key must be either a string or bytes(redis/redis-py)
- Non-native FormData is not supported. Use globalThis.FormData instead.(sindresorhus/got)
- data must be an object(axios/axios)
- BelongsToMany: Option "through.unique" can only be used if the through model's foreign keys are not also the primary keys. Add your own primary key to the through model, on different attributes than the foreign keys, to be able to use this option.(sequelize/sequelize)
- The defaults must be passed as the third argument(sindresorhus/got)
- You should use `typing_extensions.TypedDict` instead of `typing.TypedDict` with Python < 3.11. Without it, there is no way to reflect Required/NotRequired keys.(pydantic/pydantic)
- Cannot access a deferred module namespace while the module is being evaluated(webpack/webpack)
- The reassigned stream body must be readable. Ensure you provide a fresh, readable stream in the beforeRetry hook.(sindresorhus/got)
- beforeCache hooks must be synchronous. The hook returned a Promise, but this hook must return synchronously. If you need async logic, use beforeRequest hook instead.(sindresorhus/got)
- The `${options.method}` method cannot be used with a body(sindresorhus/got)
- ${fnName}() requires ${hint}, but received ${received}.(n8n-io/n8n)
…and 1,048 more across the corpus — use search.
Honest provenance: generated on 2026-08-12 from AI-assisted analysis of the linked records. See how records are made.