> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dottxt.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Optional vs Null

> Model omission and explicit null as different, intentional states.

Optional and nullable look similar but mean different things:

* **Optional** (not in `required`): the key may be absent entirely. This means "we didn't ask" or "not applicable."
* **Nullable** (`type: ["string", "null"]`): the key is always present, but the value may be `null`. This means "we asked, but the answer is unknown."

The distinction matters for storage, analytics, and downstream logic. If you treat both as the same thing, you can't tell whether a field was never captured or was captured and found to be empty, and that ambiguity cascades into every system that touches the data.

## Use case

CRM contact records where `nickname` is a nice-to-have that the model may or may not extract (optional), but `middle_name` should always be present in the output and set to `null` when the source text doesn't mention one.

## Schema pattern

<CodeGroup>
  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "type": "object",
    "properties": {
      "first_name": { "type": "string", "minLength": 1 },
      "middle_name": { "type": ["string", "null"], "maxLength": 80 },
      "last_name": { "type": "string", "minLength": 1 },
      "nickname": { "type": "string", "maxLength": 80 }
    },
    "required": ["first_name", "middle_name", "last_name"],
    "additionalProperties": false
  }
  ```

  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from pydantic import BaseModel, ConfigDict, Field

  class Contact(BaseModel):
      model_config = ConfigDict(extra="forbid")
      first_name: str = Field(..., min_length=1)
      middle_name: str | None = Field(..., max_length=80)
      last_name: str = Field(..., min_length=1)
      nickname: str | None = Field(None, max_length=80)
  ```

  Pydantic note: `middle_name: str | None = Field(...)` maps cleanly to a required nullable field. For `nickname`, the idiomatic `str | None = Field(None, ...)` form makes the field optional and nullable in emitted schema. If you need omission without `null`, author the JSON Schema directly or use a schema representation that distinguishes those cases more directly.

  ```typescript Zod theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  import { z } from "zod";

  const contactSchema = z.object({
    first_name: z.string().min(1),
    middle_name: z.string().max(80).nullable(),
    last_name: z.string().min(1),
    nickname: z.string().max(80).optional(),
  }).strict();
  ```
</CodeGroup>

## Example outputs

Known middle name, no nickname:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "first_name": "Alice",
  "middle_name": "Marie",
  "last_name": "Johnson"
}
```

Unknown middle name, nickname present:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "first_name": "Alice",
  "middle_name": null,
  "last_name": "Johnson",
  "nickname": "AJ"
}
```

## Why this works

In the first example, `nickname` is absent. The model didn't extract one, and your application can skip rendering it entirely. In the second example, `middle_name` is explicitly `null`. The model looked for a middle name and didn't find one, so your UI can show "Unknown" instead of leaving a blank gap.

This distinction is especially important for analytics and data pipelines. A `COUNT` of non-null `middle_name` values tells you how many contacts have known middle names. A `COUNT` of records where `nickname` exists tells you how many contacts provided one. Without the distinction, both queries return the same misleading number.

## Related docs

* [Object reference](/json-schema/reference/object)
* [Improve Your Schema](/json-schema/improve-your-schema)
