Fix ValidationError: marshmallow.exceptions.ValidationError: {'email': ['Not a valid email address.'], 'age': ['Not a valid integer.']} in Flask
This error occurs when incoming request data fails Marshmallow schema validation. The validation error dictionary maps field names to lists of error messages. Fix it by catching ValidationError in your route handler, returning a 422 response with the error details, and ensuring your frontend sends data in the format your schema expects.
Reading the Stack Trace
Here's what each line means:
- File "/app/app/routes.py", line 14, in create_user: The route handler calls schema.load() which validates and deserializes the incoming JSON data.
- File "/app/venv/lib/python3.12/site-packages/marshmallow/schema.py", line 909, in _do_load: Marshmallow's load pipeline detected validation errors and raises them as a single exception with all field errors.
- marshmallow.exceptions.ValidationError: {'email': ['Not a valid email address.'], 'age': ['Not a valid integer.']}: Two fields failed: 'email' is not a valid email format and 'age' cannot be parsed as an integer.
Common Causes
1. Unhandled ValidationError
The route calls schema.load() without catching ValidationError, so the exception propagates and returns a 500 error.
@app.route('/users', methods=['POST'])
def create_user():
data = user_schema.load(request.get_json()) # no try/except
user = User(**data)
db.session.add(user)
db.session.commit()
return jsonify(user_schema.dump(user)), 201
2. Wrong data types from frontend
The client sends age as a string and email without an @ symbol, violating the schema's field types.
// Frontend sends:
fetch('/users', {
method: 'POST',
body: JSON.stringify({ email: 'notanemail', age: 'twenty-five' })
})
3. Missing required fields
The schema marks fields as required but the request payload omits them entirely.
class UserSchema(Schema):
name = fields.String(required=True)
email = fields.Email(required=True)
# Request body: {'name': 'Alice'} — missing email
The Fix
Catch Marshmallow's ValidationError and return a 422 Unprocessable Entity response with the field-level error messages. This gives the client actionable feedback about which fields failed and why, instead of a generic 500 error.
@app.route('/users', methods=['POST'])
def create_user():
data = user_schema.load(request.get_json())
user = User(**data)
db.session.add(user)
db.session.commit()
return jsonify(user_schema.dump(user)), 201
from marshmallow import ValidationError
@app.errorhandler(422)
def handle_validation_error(err):
return jsonify({'errors': err.description}), 422
@app.route('/users', methods=['POST'])
def create_user():
try:
data = user_schema.load(request.get_json())
except ValidationError as err:
return jsonify({'errors': err.messages}), 422
user = User(**data)
db.session.add(user)
db.session.commit()
return jsonify(user_schema.dump(user)), 201
Testing the Fix
import pytest
from app import create_app
@pytest.fixture
def client():
app = create_app()
app.config['TESTING'] = True
return app.test_client()
def test_valid_user_creation(client):
response = client.post('/users', json={'name': 'Alice', 'email': 'alice@example.com', 'age': 30})
assert response.status_code == 201
def test_invalid_email_returns_422(client):
response = client.post('/users', json={'name': 'Alice', 'email': 'notanemail', 'age': 30})
assert response.status_code == 422
errors = response.get_json()['errors']
assert 'email' in errors
def test_invalid_age_returns_422(client):
response = client.post('/users', json={'name': 'Alice', 'email': 'alice@example.com', 'age': 'old'})
assert response.status_code == 422
errors = response.get_json()['errors']
assert 'age' in errors
def test_missing_required_field_returns_422(client):
response = client.post('/users', json={'name': 'Alice'})
assert response.status_code == 422
Run your tests:
pytest tests/ -v
Pushing Through CI/CD
git checkout -b fix/flask-marshmallow-validation-handler,git add app/routes.py tests/test_users.py,git commit -m "fix: catch Marshmallow ValidationError and return 422",git push origin fix/flask-marshmallow-validation-handler
Your CI config should look something like this:
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- run: pip install -r requirements.txt
- run: pytest tests/ -v --tb=short
- run: flake8 app/
The Full Manual Process: 18 Steps
Here's every step you just went through to fix this one bug:
- Notice the error alert or see it in your monitoring tool
- Open the error dashboard and read the stack trace
- Identify the file and line number from the stack trace
- Open your IDE and navigate to the file
- Read the surrounding code to understand context
- Reproduce the error locally
- Identify the root cause
- Write the fix
- Run the test suite locally
- Fix any failing tests
- Write new tests covering the edge case
- Run the full test suite again
- Create a new git branch
- Commit and push your changes
- Open a pull request
- Wait for code review
- Merge and deploy to production
- Monitor production to confirm the error is resolved
Total time: 30-60 minutes. For one bug.
Or Let bugstack Fix It in Under 2 minutes
Every step above? bugstack does it automatically.
Step 1: Install the SDK
pip install bugstack
Step 2: Initialize
import bugstack
bugstack.init(api_key=os.environ["BUGSTACK_API_KEY"])
Step 3: There is no step 3.
bugstack handles everything from here:
- Captures the stack trace and request context
- Pulls the relevant source files from your GitHub repo
- Analyzes the error and understands the code context
- Generates a minimal, verified fix
- Runs your existing test suite
- Pushes through your CI/CD pipeline
- Deploys to production (or opens a PR for review)
Time from error to fix deployed: Under 2 minutes.
Human involvement: zero.
Try bugstack Free →No credit card. 5-minute setup. Cancel anytime.
Deploying the Fix (Manual Path)
- Run pytest locally to verify valid data succeeds and invalid data returns 422 with error details.
- Open a pull request with the error handler and tests.
- Wait for CI checks to pass on the PR.
- Have a teammate review and approve the PR.
- Merge to main and verify the API returns proper validation errors in staging.
Frequently Asked Questions
BugStack tests with valid data, invalid data for each field, and missing required fields, then runs your full suite to confirm no regressions before marking it safe.
BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review it before merging.
422 Unprocessable Entity is semantically correct for validation failures. 400 Bad Request is for malformed syntax. Most REST APIs use 422 for field-level validation errors.
Yes. Pass error_messages={'invalid': 'Custom message'} to the field constructor or define custom validators with tailored messages.