---
title: "Pydantic Empty String → None Coercion"
description: "field_validator with mode='before' to treat empty string as absent optional field, preventing 422/400 on CC-only or AD-only payloads"
tags: [pydantic, fastapi, python, validation]
created: 2026-05-01
updated: 2026-05-01
projects: [video-accessibility]
---

# Pydantic Empty String → None Coercion

## Problem

Frontend forms often send `""` for optional fields that were not filled in. Pydantic `str | None` fields accept `""` as a valid non-None string, so downstream guards like `if request.audio_description_vtt:` silently skip validation — but then attempt VTT format parsing on an empty string, producing confusing 400 errors.

**Concrete bug (video-accessibility):** CC-only jobs sent `audio_description_vtt: ""`. The PATCH `/vtt` handler tried to validate the empty string as a VTT file and rejected it with 400 instead of ignoring it.

## Solution

```python
from typing import Any
from pydantic import BaseModel, field_validator

class VttUpdateRequest(BaseModel):
    captions_vtt: str | None = None
    audio_description_vtt: str | None = None

    @field_validator('captions_vtt', 'audio_description_vtt', mode='before')
    @classmethod
    def empty_str_to_none(cls, v: Any) -> str | None:
        return None if v == '' else v
```

`mode='before'` runs before Pydantic's own type coercion, so the empty string is converted to `None` before field assignment. After this, `if request.audio_description_vtt:` correctly evaluates to `False`.

## Notes

- Works for any `str | None` field where `""` should mean "not provided"
- Python 3.10+ syntax (`str | None`); use `Optional[str]` + `from __future__ import annotations` for older versions
- Ruff UP007 flags `Optional[str]` — prefer `str | None`
- Apply to all optional VTT / text content fields in upload/edit schemas
