---
cover: API Design
title: Should we change the object schema?
description: >-
  Great news! Valibot v1 RC is just around the corner. There are only a few 
  issues holding us back.
published: 2025-01-18
authors:
  - fabian-hiller
  - andersk
  - EltonLobo07
---

import { Link } from '~/components';

Great news! Valibot v1 RC is just around the corner. There are only a few issues holding us back. One of them is [issue #983](https://github.com/open-circle/valibot/issues/983), which may lead to a breaking change. That's why I want to discuss this with you before making a final decision. This blog post will explain the current situation and the reasons for the proposed changes.

## The problem

Currently, Valibot does not distinguish between missing and undefined object entries. This leads to a mismatch between the input and output values of a schema and their types when TypeScript's [`exactOptionalPropertyTypes` configuration](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) is enabled.

```ts
import * as v from 'valibot';

// This throws no error and types `output` as `{ key?: string }`
const Schema = v.object({ key: v.optional(v.string()) });
const output = v.parse(Schema, { key: undefined });

// TypeScript thinks that `key` is a string if it is present
// but this is wrong because `key` is actually `undefined`
if ('key' in output) {
  const key = output.key;
}
```

## The solution

The solution to this mismatch is to finalize [PR #1013](https://github.com/open-circle/valibot/pull/1013) and change the implementation of <Link href="/api/object/">`object`</Link> and <Link href="/api/optional/">`optional`</Link> to distinguish between missing and undefined object entries. To allow missing entries, <Link href="/api/optional/">`optional`</Link> must be used as the outermost schema of an object entry. To explicitly allow `undefined` as a value, another schema function called <Link href="/api/undefinedable/">`undefinedable`</Link> must be used.

{/* prettier-ignore */}
```ts
const Schema = v.object({
  key1: v.string(),                               // key1: string
  key2: v.optional(v.string()),                   // key2?: string
  key3: v.undefinedable(v.string()),              // key3: string | undefined
  key4: v.optional(v.undefinedable(v.string())),  // key4?: string | undefined
});
```

## The impact

As seen in the previous code example, defining an object entry that can be both missing and undefined gets a little verbose. This gets especially ugly if you define a default value for both cases.

```ts
const Schema = v.object({
  key: v.optional(
    v.undefinedable(v.string(), 'undefinedable_default'),
    'optional_default'
  ),
});
```

To fix this, we could add a new function that covers both. The problem is that there is a third schema function called <Link href="/api/nullable/">`nullable`</Link> that also allows `null` values. So adding a new function to cover every possible combination of <Link href="/api/optional/">`optional`</Link>, <Link href="/api/undefinedable/">`undefinedable`</Link> and <Link href="/api/nullable/">`nullable`</Link> would result in 7 functions in total. I doubt this will make the API any better.

## Let's discuss

What do you think? Should we distinguish between missing and undefined object entries and fully support TypeScript's `exactOptionalPropertyTypes` configuration? If so, should we remove <Link href="/api/nullish/">`nullish`</Link> and only provide 3 strong primitives with <Link href="/api/optional/">`optional`</Link>, <Link href="/api/undefinedable/">`undefinedable`</Link> and <Link href="/api/nullable/">`nullable`</Link>? Or should we add 4 more functions to cover all possible combinations? If so, what names would you use?

Your opinion matters to me. I encourage everyone to share their thoughts. Even quick feedback like "I like this ... and I don't like that ..." is welcome and will help to shape Valibot's future API design. Please discuss with us on [GitHub](https://github.com/open-circle/valibot/discussions/1022) or share your thoughts on social media.
