How It Works Features Pricing Blog Error Guides
Log In Start Free Trial
Flask · Python

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

Traceback (most recent call last): File "/app/venv/lib/python3.12/site-packages/flask/app.py", line 869, in full_dispatch_request rv = self.dispatch_request() File "/app/app/routes.py", line 14, in create_user data = user_schema.load(request.get_json()) File "/app/venv/lib/python3.12/site-packages/marshmallow/schema.py", line 723, in load return self._do_load(data, many=many, partial=partial, unknown=unknown, postprocess=True) File "/app/venv/lib/python3.12/site-packages/marshmallow/schema.py", line 909, in _do_load raise exc marshmallow.exceptions.ValidationError: {'email': ['Not a valid email address.'], 'age': ['Not a valid integer.']}

Here's what each line means:

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.

Before (broken)
@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
After (fixed)
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:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. 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:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. 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)

  1. Run pytest locally to verify valid data succeeds and invalid data returns 422 with error details.
  2. Open a pull request with the error handler and tests.
  3. Wait for CI checks to pass on the PR.
  4. Have a teammate review and approve the PR.
  5. 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.