2025, Dec 01 23:00

Reusable Marshmallow Field for Flask-Smorest APIs: Eliminate Duplicate Schema Definitions and Keep Regex Validation Consistent

Learn how to avoid duplicated Marshmallow schema fields in Flask-Smorest by creating a reusable custom field with a built-in RegExp validator and metadata.

When you build a Flask API with flask-smorest and Marshmallow, it’s common to repeat the same input constraints across multiple endpoints. Copying the same field definition with identical metadata and a RegExp validator again and again is noisy and easy to get wrong during future edits.

Repetitive schema fields: what it looks like

The pattern below is representative of what happens when every schema redefines the same string with the same validation rules and the same descriptive metadata.

from marshmallow import Schema, fields, validate
class EndpointA:
    class InPayload(Schema):
        req_code = fields.Str(
            metadata={'description': 'Request Number', 'example': 'REQUEST12345'},
            validate=validate.Regexp(
                regex=r"^REQUEST\d{3,9}$",
                error="Input string didn't match required format - REQUEST12345"
            )
        )
class EndpointB:
    class InPayload(Schema):
        req_code = fields.Str(
            metadata={'description': 'Request Number', 'example': 'REQUEST12345'},
            validate=validate.Regexp(
                regex=r"^REQUEST\d{3,9}$",
                error="Input string didn't match required format - REQUEST12345"
            )
        )

What’s actually the problem

Every endpoint repeats the same field setup: type, metadata, and a built-in validate.Regexp with identical settings. Even if you try to centralize the validator instance in a helper object and reference it from each schema, you’re still rewriting the field configuration and metadata each time. The end result is duplication, and duplication invites drift.

The clean solution: a reusable field

Marshmallow lets you package type, metadata, and validators into a custom Field subclass. You declare the shared behavior once and then use that field wherever you need it. This keeps the built-in validator, preserves the same regex and error text, and removes boilerplate from your schemas.

from marshmallow import Schema, fields, validate
class ReqNumField(fields.String):
    def __init__(self, *args, **kwargs):
        super().__init__(
            *args,
            metadata={'description': 'Request Number', 'example': 'REQUEST12345'},
            validate=validate.Regexp(
                regex=r"^REQUEST\d{3,9}$",
                error="Input string didn't match required format - REQUEST12345"
            ),
            **kwargs
        )
class ServiceOne:
    class InputSchema(Schema):
        req_code = ReqNumField()
class ServiceTwo:
    class InputSchema(Schema):
        req_code = ReqNumField()

This approach keeps all semantics intact: the field is still a string, the pattern is the same ^REQUEST\d{3,9}$, the error message stays unchanged, and the descriptive metadata remains attached to the field. Anywhere you use ReqNumField, you automatically get consistent validation and documentation.

Why this matters

Centralizing the validator and metadata eliminates the copy/paste cycle across schemas. If the format ever changes, you update it in a single place. Because the field carries its own metadata, generated API docs stay aligned without extra work. And since it relies on Marshmallow’s built-in validate.Regexp, you keep the standard behavior you were aiming for in the first place.

Takeaways

When multiple endpoints require the same validated input, package the rules into a custom Marshmallow field and reuse it. Keep the logic and regex exactly where they belong, avoid duplication in schemas, and let flask-smorest surface unified documentation from a single definition.