Tag: validation

  • What happens when an optional TypeBox property is missing?

    What happens when an optional TypeBox property is missing?

    Nothing goes wrong. When an optional property is missing, validation simply passes, because TypeBox optional properties are allowed to be absent. The validator skips the missing field entirely, the object is accepted as it stands, and only a missing required property would produce an error.

    That simple answer hides a few details worth understanding, because optional is easily confused with neighbouring ideas. An absent optional field is not filled in with a default value, it does not turn into an empty value, and it is not the same thing as permitting a null. The TypeScript type generated from the schema also records that the property may not be there, so code reading the object is pushed to handle both possibilities.

    This article covers how a property comes to be marked optional in a schema built with TypeBox. It explains what validation does when the property is absent, how the absence appears in your types, how missing differs from empty or explicitly unset values, and how to mix optional and required properties sensibly.

    How a Property Is Marked Optional in a Schema

    Every declared property in an object schema starts out required. An object that lacks any one of them fails validation, because the schema is read as a contract about exactly what an object must contain.

    The Optional modifier changes the deal for one property at a time. Wrapping a property’s schema with it declares that the property may be omitted; in effect, the property leaves the schema’s required list while keeping every other rule attached to it. Its neighbours are untouched, so optionality is precise rather than contagious.

    Marking Every Property Optional at Once

    There is also a whole-object variation. A partial version of a schema marks every property optional at once, which is exactly what update-style payloads usually want. Per-property marking suits objects you are designing, while the partial transformation suits objects you are reusing for a different job.

    One distinction is worth fixing early. Optionality governs presence only: it says whether the property must appear. What values are acceptable when it does appear remains entirely the business of the property’s own schema.

    Optional Markers Double as Documentation

    Optionality recorded in the schema doubles as documentation. Because the marker is machine-readable, generated API documentation and client tools can show which fields may be skipped. Consumers of the API learn the contract from the schema itself instead of from guesswork or support tickets.

    What Validation Does When the Property Is Absent

    Validating an object involves two kinds of checks. The validator confirms that every required property is present and valid, and it checks any optional property that happens to be present against its rules. An absent optional property has nothing to check, so it is passed over without comment.

    Presence changes everything, though. If the property does appear, it is validated exactly as strictly as a required one. A wrong type or a broken constraint fails the whole check, because optional is permission to be absent, not permission to be anything at all.

    Absent Optional Versus Missing Required

    The contrast with a missing required property is stark. That absence fails validation immediately, with an error identifying the field that should have been there. One kind of absence is part of the contract; the other is a breach of it.

    Finally, validation inspects data but never modifies it. The object that passes is the object that arrived: no key is added carrying an empty or undefined value. Downstream code sees precisely what the sender sent, nothing more.

    The Same Rule Inside Nested Objects and Arrays

    The same behaviour repeats at every level of the data. An optional property inside a nested object is skipped when absent, exactly as at the top level. Objects inside arrays follow the identical rule, item by item, so there are no special cases to memorise.

    How an Absent Optional Property Shows Up in the TypeScript Type

    The static type generated from the schema marks the property as optional too. Reading it can therefore produce either the expected value or nothing at all, and TypeScript insists that consuming code cope with both outcomes before using the value.

    In practice, this means a presence check or a fallback wherever the field is used. Teams occasionally find that friction annoying, but it is the system working as intended. A field the API is allowed to omit can never be treated as guaranteed by accident.

    One Schema Keeps Types and Validation in Step

    The deeper benefit is that the two views cannot drift apart. One schema produces both the runtime check and the compile-time type, so changing a property from required to optional updates validation and types together. There is no separate type definition waiting to be forgotten.

    That coupling also guides schema evolution. When a property flips from required to optional, type errors appear at every place the code assumed its presence, marking the spots that need a second look. The compiler produces the migration checklist for free.

    The Difference Between Missing, Empty, and Explicitly Unset Values

    Three states are confused constantly, and they behave very differently. A property can be missing entirely, present with an empty value such as an empty string or a zero, or present while carrying an explicitly unset marker like undefined or null.

    Empty is a real value and is judged as one. An optional text field that arrives empty still faces its constraints, so a minimum-length rule will reject the empty string even though omitting the field altogether would have passed. Clients that send blank fields instead of leaving them out meet failures they did not expect.

    Undefined and Null Behave Differently Again

    Undefined occupies a grey area. JSON has no undefined value, so serialised payloads simply drop such keys, and present-but-undefined usually becomes plain missing by the time an API receives it. Data held in memory can carry undefined, and there it is generally treated like absence.

    Null is different again, because it is a genuine JSON value that gets checked like any other. It will fail a string schema unless the schema deliberately allows null among the permitted kinds of value. If your API uses null as a “clear this field” signal, that permission has to be granted explicitly; optionality does not grant it.

    Why Forms Make the Empty Case So Common

    Everyday clients make the empty case common. Web forms, for example, submit an empty string for every field the user left untouched, so APIs routinely receive present-but-empty values where the sender meant nothing to say. Deciding whether to trim such values into absence, or reject them, is a deliberate design choice worth making once and documenting.

    Mixing Optional and Required Properties in One Object

    Real objects almost always mix the two. An account cannot function without an email address and a password, so those are required. A display name, an avatar, and a preferred timezone are refinements, so they are natural candidates for optional.

    The working rule is to ask what the feature breaks without. If the answer is “everything”, the property is required; if the answer is “nothing important”, it is optional. Schemas that follow the rule state the real minimum a feature needs, and validation becomes meaningful again.

    The Trap of Marking Everything Optional

    The opposite habit is the trap. Marking everything optional makes validation guarantee almost nothing, and the cost reappears as absence checks scattered through every corner of the codebase. Generous optionality feels flexible and quietly taxes everything downstream.

    Creation and update payloads often want different mixtures. A creation schema is strict, while an update schema frequently wants the partial, all-optional variant with unchanged per-field rules. Deriving both from one source schema keeps them consistent instead of maintaining two definitions by hand.

    Defaults, Extra Fields, and Schema Evolution

    Optionality also interacts with how undeclared fields are treated, which is a separate decision covered in how extra properties are handled by a schema. Settling both questions together produces an object contract with no surprises. And if a missing optional field should behave as though it had a standard value, apply that default in application code after validation; the schema alone will not supply it.

    Evolution cuts in one safe direction. Relaxing a required property to optional never breaks existing senders, because everything they used to send still passes. Tightening an optional property to required does the opposite, silently invalidating clients that omit it, so it should be treated as a breaking change.

    Frequently Asked Questions

    Does a missing optional property ever cause a validation error?

    No. Absence is exactly what optional permits, so the field is skipped. Errors arise from missing required properties, or from properties that are present but break their rules.

    Is an optional property the same as one that allows null?

    No. Optional concerns whether the property appears at all. Null is a present value, and it must be allowed in its own right if you want it accepted.

    Will validation fill in a default when the field is missing?

    No. Validation only checks the data it receives and leaves that data unchanged. Applying defaults afterwards is a small, deliberate step in your own code.

    Can I make all properties optional for update requests?

    Yes. A partial variant of the schema does exactly that, keeping every field’s rules intact for whichever fields a client chooses to send.

    Conclusion

    A missing optional property is the quietest outcome in validation: the check passes, nothing is added or invented, and the type system reminds every reader that the field might not be there. Keep required for what features truly need, reserve optional for genuine extras, and stay precise about the difference between missing, empty, and null. With those distinctions clear, optional properties become a modelling tool instead of a source of surprises.

  • What happens when a TypeBox schema contains additional properties?

    What happens when a TypeBox schema contains additional properties?

    By default, nothing dramatic: the data still passes. TypeBox additional properties are the fields that arrive in an object even though the schema never mentioned them, and unless you deliberately restrict them, validation accepts the object and the extra fields simply ride along untouched. They are not checked, not converted and not removed.

    Only when a schema is configured to forbid additional properties does their presence turn into a validation failure. That default surprises many developers the first time they meet it, because a schema feels like it should be a complete description of what an object may contain. In JSON Schema, however, an object schema describes the fields it cares about and stays silent about the rest, and TypeBox follows that convention.

    Whether silence is the right choice for your project is a design decision. This article explains what counts as an additional property, what happens in each configuration, and how to choose strictness deliberately.

    What Counts as an Additional Property in an Object Schema

    An additional property is any key in the data that the object schema does not declare. If a schema describes a user as having a name and an email, then an incoming object carrying a nickname, a legacy identifier or a debugging flag has three declared-or-undeclared parts: the two known fields, and everything else, which falls into the additional category.

    The category is about names, not values. An undeclared field counts as additional no matter what it holds, whether that is a harmless string or an entire nested object.

    Keeping Two Failure Kinds Separate

    Conversely, a declared field with a bad value is not an additional property problem at all; it is an ordinary validation failure on a known field. Keeping those two failure kinds separate makes error reports much easier to reason about.

    Why Extra Fields Appear in Real Systems

    Additional properties appear for mundane reasons. Producers add fields before consumers update their schemas. Older clients keep sending retired fields long after a redesign.

    Intermediaries attach metadata of their own. None of this implies malice or even carelessness; it is the normal friction of systems that evolve at different speeds, and it is why TypeBox, like JSON Schema generally, treats tolerance as the sensible starting point.

    Additional Is Not the Same as Missing

    It is also worth separating additional properties from missing ones. A schema can be relaxed about extras while remaining firm about required fields, and the two settings pull in opposite directions: one governs what may arrive uninvited, the other governs what must always show up.

    The Default Behaviour When Extra Fields Appear in Data

    Under the default configuration, validation looks the other way. The validator checks every declared field against its rules and ignores the rest of the object entirely. An object with the right name and email plus four undeclared fields validates exactly as cleanly as the same object with nothing extra.

    The extra fields are also left physically untouched. They remain in the object after validation, available to any later code that happens to look for them, and they will be serialised along with everything else if the object is sent onward. This pass-through quality is why tolerant schemas are popular at system boundaries that aggregate data from several sources: nothing is silently destroyed on the way through.

    What Tolerance Means for Stored Data

    There is a subtle consequence for teams that store validated data. Whatever extra fields arrived are what gets stored, including fields nobody on the team recognises.

    Over time, stored objects accumulate historical extras like sediment. That is rarely an emergency, but it explains the mysterious legacy keys that surface whenever someone finally inspects the database with fresh eyes.

    Why the Default Optimises for Interoperability

    The default, in short, optimises for interoperability. Producers and consumers can evolve independently, minor version differences stop being breaking changes, and the schema enforces the contract it actually cares about rather than policing everything that travels with the data.

    How Validation Results Change When Additional Properties Are Restricted

    Restricting additional properties flips the validator’s posture from tolerance to gatekeeping. With the restriction switched on for an object schema, the presence of any undeclared key fails validation, even when every declared field is perfectly valid. The error identifies the offending property, so the sender can see exactly which field the schema refused to accept.

    This is a stronger contract, and it changes the failure profile of a system. Requests that previously sailed through now fail because a client sent a field the schema does not know. During development that strictness is valuable: typos in field names, which tolerant validation would let slip through as silent extras while the intended field stayed missing, are caught immediately and noisily.

    Selective Strictness and the Removal Alternative

    Strictness can also be applied selectively. A schema language that treats each object independently lets you forbid extras on sensitive objects while leaving aggregating or forwarding objects tolerant. Nested objects make their own choices too, so a strict outer record can contain a deliberately open inner one, such as a metadata bag whose whole purpose is to carry arbitrary extras.

    A related middle path is removal rather than rejection. Some teams prefer tooling that strips undeclared fields during or after validation, keeping the data that matters and discarding the rest. That approach suits stored data and forwarded messages, though it should be a deliberate pipeline step rather than a surprise, because silently discarding fields that another system expected can move the bug rather than fix it.

    How Extra Fields Affect the TypeScript Side of the Schema

    On the static side, the generated type describes the declared fields and says nothing about extras, which creates an asymmetry worth understanding. At runtime an object may carry additional properties; in the type system, those properties are invisible. Code reading validated data through its type sees exactly the declared shape and nothing more.

    TypeScript’s own behaviour adds a wrinkle. The language performs excess property checks on fresh object literals, so hand-written data assigned directly in code can be flagged for extra fields even while identical data arriving from outside passes validation without comment. Developers occasionally find this backwards, strict where they write data and relaxed where they receive it, but each behaviour guards a different moment: literals are checked at authoring time, external data at the boundary.

    The Practical Consequence for Trusting Your Data

    The practical consequence is about trust. Code that needs a field should not assume it can fish undeclared extras out of validated data in a type-safe way, because the type does not know they exist.

    If a field matters, declare it in the schema, possibly as optional. That is also the correct response when a supposedly extra field turns out to be load-bearing in production: promote it into the contract, where both the validator and the types can see it. Questions about declared-but-absent fields belong to the neighbouring topic of what happens when an optional property is missing, which pairs naturally with the strictness decision.

    Deciding Strictness for APIs That Receive Outside Data

    For public APIs and webhooks, tolerance is usually the safer default. Senders include partner systems, older mobile apps and third-party tools you do not control. Rejecting their requests over an extra field they consider harmless creates integration failures that are hard to debug from the other side, and the sender often cannot change quickly even when they want to.

    Strictness earns its keep in narrower settings. Internal messages between services owned by one team benefit from loud failures, because both sides can be fixed in the same afternoon. Security-sensitive payloads are another candidate: when an object drives permissions, pricing or identity, accepting undeclared fields creates room for confusion about which fields actually influenced a decision, and a strict schema removes that ambiguity.

    A Layered Policy Many Teams Adopt

    A workable policy many teams adopt is layered. The public boundary stays tolerant so integrations do not shatter, while the objects that drive internal decisions are strict. Whichever you choose, document it per schema rather than by habit, because the next developer will otherwise assume the opposite of what you intended.

    When to Revisit the Choice

    Revisit the choice when the data changes owners. A schema written for an internal tool that later becomes a partner-facing contract should usually loosen; a tolerant prototype that hardens into a payment flow should usually tighten.

    Strictness is not a personality trait of the codebase. It is a per-boundary decision about who sends the data and what a surprise field could cost.

    Frequently Asked Questions

    Does TypeBox reject extra fields by default?

    No. Following JSON Schema convention, extra fields are allowed by default and pass through validation untouched. Rejection happens only when the object schema is configured to restrict additional properties.

    Are additional properties removed during validation?

    Not by the validator itself. Extra fields stay in the object after a successful check. Removing them is a separate, deliberate step some teams add to their pipeline.

    Can I forbid extra fields on one object but allow them on another?

    Yes. The setting belongs to each object schema individually, including nested objects, so strict and tolerant shapes can be mixed within a single larger schema.

    Do extra fields appear in the generated TypeScript type?

    No. The generated type contains only the declared fields. Extras may exist at runtime, but code cannot access them in a type-safe way unless they are declared in the schema.

    Conclusion

    When a TypeBox schema contains additional properties in the data it receives, the default outcome is calm acceptance: declared fields are checked, extras are ignored and left in place. Restricting them turns the same situation into a clear validation failure, which protects sensitive contracts and catches naming mistakes early.

    The generated types only ever describe declared fields, so anything that matters should be written into the schema. Choose tolerance at open boundaries and strictness where surprises are expensive, and the behaviour stops being a surprise itself.

  • Can TypeBox handle binary data or buffer-based values?

    Can TypeBox handle binary data or buffer-based values?

    Yes, with a clear understanding of where the boundaries are. TypeBox binary data support exists because real applications deal with more than text and numbers: file contents, images, cryptographic material and encoded payloads all travel as bytes. TypeBox includes dedicated types for byte-based values, so a schema can describe a field as binary, generate a matching TypeScript type, and check the value properly when it is validated in memory.

    The boundary is JSON itself. Binary values do not survive a trip through plain JSON, so when data has to cross a JSON boundary it is normally converted to text first, most often base64, and the schema describes that text instead. Knowing which side of that boundary your data sits on is the whole trick, and this article explains how TypeBox represents binary values, what the types look like, and how validation treats them.

    Why Binary Data Does Not Fit the Usual JSON Shapes

    JSON was designed around a small set of shapes: text, numbers, true and false, lists, and objects made of named fields. A sequence of raw bytes is none of those.

    Bytes can hold any value from zero to 255, including many that have no printable character and no safe representation as ordinary text. Forcing them into a JSON string corrupts them or inflates them beyond recognition.

    Why an Array of Numbers Does Not Work

    The naive workaround is an array of numbers, one per byte. It is technically valid and practically miserable.

    A modest image becomes hundreds of thousands of array entries, the payload balloons in size, and every consumer has to rebuild the original bytes before doing anything useful. Nobody who has watched a network tab fill with numbers does this twice.

    The Two Honest Options

    So the ecosystem settled on two honest options. Inside a running program, bytes are bytes, held in a buffer-like container and handled as a single value.

    Across a JSON boundary, bytes are encoded as text, usually base64, which trades a size increase of roughly a third for safe transport through any JSON channel. A schema library has to serve both worlds, and that is exactly the split you will see in how TypeBox approaches the problem: real binary types for values in memory, and ordinary string schemas for encoded values on the wire.

    How TypeBox Represents Buffer-Based Values in a Schema

    TypeBox treats binary as a first-class category alongside its other built-in types. Rather than pretending a buffer is an array of numbers, it provides dedicated schema builders for byte array values and for the underlying buffer containers that hold them. A field declared with one of these builders is described as a binary value in its own right, with its own identity in the schema.

    This matters because the description stays truthful. A reader of the schema can see immediately that the field carries file contents or raw bytes, not a list of small integers.

    Size Expectations and Exported Schemas

    Tooling that consumes the schema sees the same thing. The schema can also express expectations about the value, such as size limits, in the same spirit as length limits on text or item limits on arrays, which is useful when a field is supposed to hold a small thumbnail rather than an unbounded upload.

    There is an important nuance when schemas are exported as pure JSON Schema documents. JSON Schema has no native binary shape, so exported descriptions lean on conventions: encoded content is usually described as text with a declared format, while the in-memory binary types rely on the validator understanding what a byte array instance is. Teams that share schemas with non-TypeScript tools should decide early which convention they are using, so the contract means the same thing on both sides.

    Binary Fields Inside Ordinary Records

    None of this changes the shape of the surrounding schema. A record can mix ordinary fields, a name here and a size there, with one or two binary fields where the actual content lives. Composition works as usual, and binary fields can sit inside optional properties, arrays of records and the rest of the normal toolkit.

    What the Generated TypeScript Type Looks Like for Binary Fields

    The static type generated for a binary field is the natural TypeScript counterpart: a byte array type for buffer-based values, and the buffer container type where the schema describes a whole buffer. In practice this means code that consumes validated data can treat the field as bytes immediately, passing it to file writers, hashing functions, image processors and network calls without any conversion ceremony.

    This is a noticeable quality-of-life difference from the encoded-text approach. When binary travels as base64 text, the static type is just a string, and nothing in the type tells the reader that the string represents bytes, which encoding it uses, or that it should be decoded before use.

    How the Two Styles Compose

    Every consumer has to know the convention by heart. With a true binary type, the type itself carries that information, and misuse, like accidentally displaying raw bytes as text, becomes easier to spot in review.

    The two styles also compose differently. A schema describing an upload record with a binary content field produces a record type whose content field is bytes and whose metadata fields are ordinary text and numbers. TypeScript developers reading that type understand the whole arrangement at a glance, which is the same readability benefit TypeBox aims for everywhere else.

    The Caution at the JSON Boundary

    One caution is worth repeating: the elegant in-memory type only exists while the data stays in memory. The moment the same record is serialised to JSON for storage or transport, the binary field needs an encoding step, and the type on the receiving side is whatever that side’s schema says it is. Keeping the two representations clearly named in a codebase prevents a whole category of confusion.

    How Validation Treats Binary Values at Runtime

    Runtime validation of a binary field is an identity check rather than a content inspection. The validator confirms that the value actually is a byte array or buffer of the expected kind, rather than, say, a string that merely looks like one.

    If size expectations were declared in the schema, those are checked as well. The contents of the bytes are not interpreted; a schema cannot tell a valid image from random noise, only that the value is genuinely binary and the right sort of size.

    What the Schema Cannot Judge

    That distinction shapes how you should design checks around uploads. The schema layer answers “is this really bytes, and is it within the allowed size”, while format-specific verification, such as confirming an image header or a file signature, belongs in ordinary code after validation. Expecting the schema to judge content is a category error that leads to disappointment.

    Performance and the Encoded-Text Case

    Performance is generally a non-issue for binary fields, because checking a value’s kind and length is cheap compared with walking a large object field by field. Teams wondering about the overall cost of checking large payloads on every request are really asking whether validation slows a busy API down, which depends far more on object size and schema complexity than on the presence of a binary field or two.

    Encoded values behave differently, of course. A base64 field validates as a string, so the schema can check its length and pattern but cannot confirm the decoded bytes are meaningful until code decodes them. If the distinction matters to your application, validate the text at the boundary, decode it, and then treat the result as binary data inside.

    Practical Places Binary Fields Appear, Such as File Uploads

    File uploads are the everyday case. A user submits a photo, a document or an attachment, and the receiving code holds the contents as bytes long before anything is stored. A schema with a binary field describes that payload honestly: the content is bytes, the filename is text, the size is a number, and each part validates according to its own nature.

    Generated and derived files form a second case. Systems that produce PDFs, export spreadsheets or render images end up holding the results as buffers before sending or saving them. Describing those results with binary fields keeps internal message shapes truthful as they move between services in the same process or across a direct binary channel.

    Security Material and the Hybrid Pattern

    Cryptographic and identity material is a third case. Hashes, signatures, keys and tokens at the byte level are all binary values, even when they are short. Schemas for security-sensitive messages benefit from saying so explicitly, because a hash described as bytes cannot be quietly confused with the human-readable text it might be printed as.

    Finally, there is the hybrid pattern most APIs actually use. Bytes cross the network encoded as text, then are decoded and handled as binary inside. A well-kept codebase marks the boundary clearly: the external schema describes the encoded text, the internal schema describes the binary value, and one deliberate conversion step connects them.

    Frequently Asked Questions

    Can a TypeBox schema describe a file upload directly?

    Yes, when the upload is handled in memory. The content field can be declared as binary alongside text fields for the filename and other metadata, and each part validates according to its kind.

    Does binary data survive being sent as JSON?

    No. JSON has no binary shape, so bytes are encoded as text, usually base64, before transport and described as a string in the schema on that side of the boundary.

    Can validation check that binary data is a valid image?

    Not at the schema level. Validation confirms the value is genuinely bytes and within any declared size expectations. Checking image headers or file signatures belongs in code after validation.

    What type does a binary field produce in TypeScript?

    A byte array or buffer type. It matches how the value is actually used in code, so consumers can pass it straight to file, hashing and network operations.

    Conclusion

    TypeBox handles binary data and buffer-based values as first-class citizens inside a running program: dedicated schema types describe them truthfully, the generated TypeScript types treat them as bytes, and validation checks their kind and size quickly. Across JSON boundaries the same data travels as encoded text under a string schema. Keep those two worlds clearly separated in your schemas, and binary fields stop being a special case and become just another well-described part of your data.

  • Can TypeBox represent complex conditional data requirements?

    Can TypeBox represent complex conditional data requirements?

    Yes. TypeBox conditional schemas exist for exactly this situation: data where the rules change depending on the values inside the data itself. A field that becomes required only when another field holds a certain value, a property that must appear when a status changes, or a record whose shape differs between account types can all be described in one schema.

    None of this is a TypeBox invention. The conditions come from JSON Schema, and TypeBox exposes them as ordinary building blocks that still generate a TypeScript type. The honest answer does come with a boundary.

    Simple and moderately complex conditions work well and stay readable for the people who maintain them. Very tangled business rules, where one requirement depends on three fields and a calculation, are usually better handled by a plain validation step that runs after the schema check. The rest of this article walks through what conditional requirements look like in practice, how TypeBox expresses them, and where the limits sit.

    What a Conditional Data Requirement Looks Like in Practice

    A conditional requirement is any rule you cannot state as a simple property of one field. Saying a name must be a string is unconditional.

    Saying a company name must be present whenever the account type is set to business is conditional, because whether the rule applies depends on another value in the same object. That distinction sounds small, but it shows up in almost every real system.

    Conditions in Checkout Forms

    Checkout forms are a classic home for conditions. A delivery address is required when the customer chooses delivery, and pointless when they choose collection.

    Payment details change shape depending on the payment method selected. A discount code field might be optional in general, yet need a matching reason field whenever it is used.

    Conditions in APIs and Larger Systems

    APIs produce the same pattern at a larger scale. A subscription object may carry renewal settings only for paid plans, while free plans must not send them at all.

    A report request might allow a custom date range only when the report type is set to custom, and fixed ranges otherwise. In each case, the validity of one field is decided by the content of another, which is precisely what TypeBox is built to model: one definition that describes the data and produces the type your code works with.

    The Negative Form of a Condition

    Conditions also appear in a negative form. Sometimes a field is forbidden unless something else is true, such as an approval note that may only exist once a request has been reviewed. Expressing both directions, required-when and forbidden-unless, is what turns a loose object description into a real data contract.

    The JSON Schema Patterns TypeBox Uses to Express Conditions

    TypeBox conditional schemas rest on a small set of JSON Schema patterns rather than on any special TypeBox-only machinery. The best known is the if, then and else arrangement.

    The schema first tests the data against a condition, much like a guard clause. When the data matches the condition, the rules in the then branch apply; when it does not, the else branch applies instead.

    Dependent Properties

    A second pattern covers dependent properties. Instead of testing values, it simply states that the presence of one property requires the presence of another.

    This suits the company name example well, because the rule is about fields travelling together rather than about any particular value. For richer cases, schemas can also demand that a whole sub-schema holds whenever a flag field equals a given value.

    Composition for Bigger Requirements

    Composition does the heavy lifting for anything bigger. Conditions are combined with the same tools used everywhere else in JSON Schema: a value must match one of several complete shapes, or it must satisfy several partial schemas at once.

    A common technique is to describe each variant of a record as its own complete shape and then say the data must match exactly one of them. The payment method example maps neatly onto this approach, with card, transfer and wallet each described in full.

    Why Standard Patterns Travel Well

    Because these are standard patterns, they travel well. A schema written for TypeScript validation can be shared with other tools that understand JSON Schema, and other developers reading it will recognise the conditional structure even if they have never used TypeBox. That portability is a practical advantage when schemas are shared between a frontend, a backend and partner systems.

    How Conditions Affect the TypeScript Type You Get Back

    The static type produced from a conditional schema is best thought of as an honest summary rather than a perfect mirror. When a record can take several shapes, the generated type becomes a union of those shapes. Your code then sees one type that could be the card variant or the transfer variant, and TypeScript pushes you to check which one you are holding before using variant-specific fields.

    This works especially well when the variants are separated by a tag field, such as a method or kind property with a fixed set of values. TypeScript can narrow a union by that tag, so inside a branch that checks the tag, the correct fields become available and the others are excluded. Well-designed conditional schemas lean on this behaviour deliberately, because it makes the runtime rules and the static types tell the same story.

    Where the Type Cannot Tell the Whole Story

    Value-dependent rules are harder for the type system. A rule like “the company name is required when the account type is business” cannot be fully captured as a static type without contortions, because it ties the presence of one field to the runtime value of another.

    The generated type will usually mark such fields as optional, and the schema carries the real enforcement at validation time. That split is normal and worth understanding: the type keeps your code convenient, while the schema remains the strict authority.

    How Unlisted Data Interacts With Conditions

    It also helps to know how unlisted data interacts with all of this. Strictness settings decide what extra properties do to a schema when incoming objects carry fields the schema never mentioned, and those settings apply whether or not the object also uses conditional rules.

    How Validation Behaves When a Condition Is Triggered

    At runtime, validation reads the actual data and follows the conditions wherever they lead. When the incoming object says the account type is business, the validator checks the branch that applies to businesses, including its extra required fields. When the type is personal, that branch is skipped entirely, and a missing company name is not treated as a problem.

    This selective checking is the real payoff of TypeBox conditional schemas. One schema can accept several legitimate shapes while still rejecting broken ones, such as a business account with no company name or a delivery order with no address. Without conditions, teams usually end up with one loose schema plus scattered manual checks, and the manual checks are the part that quietly drifts out of date.

    What Error Messages Look Like When a Branch Fails

    Error reporting follows the branch that failed. If a condition is triggered and its required field is missing, the error points at the missing field within that branch.

    In deeply nested conditions the messages can take some untangling, which is another reason to keep conditions as shallow as the data allows. A failing else branch can be particularly confusing to read, because the error describes a rule the reader may not have expected to apply.

    Where Conditional Logic Becomes Too Complex for a Schema

    Schemas are declarative, and that strength becomes a ceiling when the rule stops being about shape. A requirement that a total must equal the sum of its line items involves arithmetic across fields.

    A rule that a coupon is valid only while a campaign is running depends on outside data and the current time. A rule that changes with the user’s history depends on state the schema never sees.

    Nested Conditions as a Warning Sign

    Nested conditions are the other warning sign. One level of branching is easy to read; three levels of if and else, combined with several alternative shapes, produces a schema nobody wants to review. At that point the schema is functioning as a small program written in a language with no debugging tools, and bugs hide in it comfortably.

    The Two-Layer Approach

    The practical pattern is a two-layer approach. Let the schema enforce structure and the conditions it expresses cleanly, then run ordinary code checks for the genuinely computational rules. TypeBox does not object to this division of labour; the schema still guards every boundary, and the code layer handles the handful of rules that were never really about shape.

    Frequently Asked Questions

    Can TypeBox require a field only when another field has a specific value?

    Yes. That is the most common conditional requirement, and it is expressed with the standard if and then pattern: test the deciding field, and apply the extra requirement only when the test matches.

    Do conditional schemas still produce a TypeScript type?

    They do. Variant-based conditions produce union types that TypeScript can narrow by a tag field. Purely value-dependent requirements are enforced at validation time, with the affected fields usually appearing as optional in the static type.

    Can conditions be nested inside other conditions?

    They can, but deep nesting quickly becomes hard to read and hard to debug. If a rule needs several levels of branching, it is often a sign that part of the check belongs in ordinary code after validation.

    Are TypeBox conditional schemas portable to other tools?

    Mostly, yes. The conditions use standard JSON Schema patterns, so other validators that support those parts of the specification can read the same schema.

    Conclusion

    TypeBox conditional schemas comfortably represent the conditional requirements that appear in real forms, APIs and configuration data: fields that travel together, shapes that change with a tag value, and branches that apply only when a condition matches. The type you get back is a useful union or an approximation, while validation enforces the details. For rules involving arithmetic, outside data or deep branching, pair the schema with a small code check and you get the best of both layers.