ValidPaths Demo
Filter field paths by value type - including nested objects and arrays

Schema Structure

const schema = z.object({
  // First level - primitives
  name: z.string(),
  email: z.string().email(),
  age: z.number(),
  isActive: z.boolean(),

  // Nested object
  profile: z.object({
    bio: z.string(),
    website: z.string().url().optional(),
    followers: z.number(),
    verified: z.boolean(),
  }),

  // Array of objects
  addresses: z.array(z.object({
    street: z.string(),
    city: z.string(),
    zipCode: z.string(),
    isPrimary: z.boolean(),
  })),

  // Deeply nested
  settings: z.object({
    notifications: z.object({
      email: z.boolean(),
      sms: z.boolean(),
      frequency: z.string(),
    }),
    preferences: z.object({
      theme: z.string(),
      language: z.string(),
    }),
  }),
});

Interactive Demo

These are actual components using ValidPaths. Hover over the name prop in your IDE to see IntelliSense!

StringInput:name
StringInput:email
StringInput:profile.bio
StringInput:settings.preferences.theme
NumberInput:age
NumberInput:profile.followers
BooleanInput:isActive
BooleanInput:profile.verified
BooleanInput:settings.notifications.email
ProfileSection:profile
AddressSection:addresses.0
NotificationsSection:settings.notifications

Tip: Try changing the name prop values above to invalid paths - TypeScript will show an error!

String Paths

All paths where the value type is string

type StringPaths = ValidPaths<typeof schema, never, never, string>
nameemailprofile.bioprofile.websiteaddresses.0.streetaddresses.0.cityaddresses.0.zipCodesettings.notifications.frequencysettings.preferences.themesettings.preferences.language

Number Paths

All paths where the value type is number

type NumberPaths = ValidPaths<typeof schema, never, never, number>
ageprofile.followers

Boolean Paths

All paths where the value type is boolean

type BooleanPaths = ValidPaths<typeof schema, never, never, boolean>
isActiveprofile.verifiedaddresses.0.isPrimarysettings.notifications.emailsettings.notifications.sms

Object Paths

Filter paths by object shape - useful for nested form sections

type Profile = z.infer<typeof schema>['profile']
type ProfilePaths = ValidPaths<typeof schema, never, never, Profile>
profile
type Address = z.infer<typeof schema>['addresses'][number]
type AddressPaths = ValidPaths<typeof schema, never, never, Address>
addresses.0
type Notifications = z.infer<typeof schema>['settings']['notifications']
type NotificationsPaths = ValidPaths<typeof schema, never, never, Notifications>
settings.notifications

Actual Usage Examples

1. Type-safe form field components: Create input components that only accept paths matching their expected value type.

// Only accepts paths that resolve to string values
// TPath is auto-inferred from the 'name' prop!
function StringInput<
  TSchema extends z.ZodType,
  TPath extends ValidPaths<TSchema, never, never, string>,
>({ schema, name }: { schema: TSchema; name: TPath }) {
  void schema; // used for type inference
  const { register } = useFormContext();
  return <input {...register(name)} />;
}

// Usage - no type params needed:
<StringInput schema={schema} name="profile.bio" />  // ✅ OK
<StringInput schema={schema} name="age" />          // ❌ Error: number

2. Number-only inputs with validation:

function NumberInput<
  TSchema extends z.ZodType,
  TPath extends ValidPaths<TSchema, never, never, number>,
>({ schema, name, min, max }: {
  schema: TSchema;
  name: TPath;
  min?: number;
  max?: number;
}) {
  void schema;
  const { register } = useFormContext();
  return <input type="number" min={min} max={max} {...register(name)} />;
}

// Usage:
<NumberInput schema={schema} name="age" min={0} max={120} />  // ✅ OK
<NumberInput schema={schema} name="profile.followers" />      // ✅ OK
<NumberInput schema={schema} name="name" />                   // ❌ Error

3. Object section components with useWatch:

type Profile = z.infer<typeof schema>['profile'];

// TPath is auto-inferred from the 'name' prop - no manual type params!
function ProfileSection<
  TSchema extends z.ZodType,
  TPath extends ValidPaths<TSchema, never, never, Profile>,
>({ schema, name }: { schema: TSchema; name: TPath }) {
  const { control } = useFormContext();
  const profileData = useWatch({ control, name });
  // profileData is typed as Profile: { bio, website, followers, verified }
  return (
    <div>
      <p>Bio: {profileData.bio}</p>
      <p>Followers: {profileData.followers}</p>
    </div>
  );
}

// Usage - TPath inferred automatically:
<ProfileSection schema={schema} name="profile" />  // ✅ OK
<ProfileSection schema={schema} name="settings" /> // ❌ Error: wrong type

4. Array item rendering with type safety:

type Address = z.infer<typeof schema>['addresses'][number];

function AddressCard<TPath extends ValidPaths<typeof schema, never, never, Address>>({
  name,
}: {
  name: TPath;
}) {
  const { control } = useFormContext();
  const address = useWatch({ control, name });
  // address is typed as Address: { street, city, zipCode, isPrimary }
  return (
    <div className={address.isPrimary ? 'border-primary' : ''}>
      <p>{address.street}, {address.city} {address.zipCode}</p>
    </div>
  );
}

// Usage with useFieldArray:
const { fields } = useFieldArray({ control, name: 'addresses' });
{fields.map((_, index) => (
  <AddressCard key={index} name={`addresses.${index}`} />  // ✅ Type-safe
))}

5. Bulk operations on paths of same type:

// Reset all boolean fields to false
type BoolPaths = ValidPaths<typeof schema, never, never, boolean>;
const booleanFields: BoolPaths[] = [
  'isActive',
  'profile.verified',
  'settings.notifications.email',
  'settings.notifications.sms',
];

function resetBooleans(form: UseFormReturn<z.infer<typeof schema>>) {
  booleanFields.forEach((path) => {
    form.setValue(path, false);  // ✅ Type-safe: path resolves to boolean
  });
}