python 85 lines · 3 tabs

Field-Level Signup Validation in Flask with Marshmallow Schemas

Shared by codesnips Aug 2026
3 tabs
from marshmallow import (
    Schema, fields, validates, validates_schema,
    ValidationError, RAISE,
)
from marshmallow.validate import Length, Email, Equal


class SignupSchema(Schema):
    class Meta:
        unknown = RAISE

    email = fields.Email(required=True, validate=Email())
    password = fields.Str(
        required=True,
        load_only=True,
        validate=Length(min=8, max=128),
    )
    confirm_password = fields.Str(required=True, load_only=True)
    display_name = fields.Str(required=True, validate=Length(min=2, max=40))
    terms_accepted = fields.Bool(
        required=True,
        validate=Equal(True, error="You must accept the terms."),
    )

    @validates("password")
    def validate_password(self, value, **kwargs):
        if value.isalnum():
            raise ValidationError(
                "Password must include a symbol or punctuation character."
            )
        if value.lower() == value or value.upper() == value:
            raise ValidationError("Password must mix upper and lower case.")

    @validates_schema
    def check_passwords_match(self, data, **kwargs):
        pw = data.get("password")
        confirm = data.get("confirm_password")
        if pw and confirm and pw != confirm:
            raise ValidationError(
                "Passwords do not match.", field_name="confirm_password"
            )
3 files · python Explain with highlit

This snippet shows how a Flask signup endpoint turns raw JSON into a clean, validated payload using Marshmallow, and how it surfaces per-field errors back to the client in a predictable shape. The core idea is to separate validation from route logic: the schema owns the rules, the route owns the flow, and a single error handler owns the response format.

In schemas.py, SignupSchema declares each field with its constraints — email uses the built-in Email validator, password combines a Length check with a custom validate_password method decorated with @validates, and terms_accepted must equal True via Equal. Marshmallow accumulates all failures at once rather than stopping at the first, so a client sees every problem in one round trip. The @validates_schema method check_passwords_match demonstrates cross-field validation, attaching its error to confirm_password so the message lands on the right input. Meta.unknown = RAISE rejects unexpected keys, which keeps mass-assignment surprises out of the handler.

The ValidationError raised by Marshmallow carries a messages dict keyed by field name, which maps naturally to form fields on the frontend. In errors.py, the register_error_handlers function installs an app-level handler for ValidationError that returns HTTP 422 with a consistent {"errors": ...} envelope, so no individual route needs a try/except around load.

In auth.py, the signup view calls schema.load(request.get_json()); because the error handler is registered globally, a validation failure short-circuits into the 422 response automatically. Only valid, coerced data reaches the business logic, so the duplicate-email check and User creation operate on trusted values. Note the deliberate choice to raise a ValidationError for the taken email too, keeping all field errors — including database-level ones — in the same envelope the frontend already understands.

The trade-off is that schema logic lives apart from the model, which means keeping the two in sync; the payoff is testable, reusable validation and a uniform error contract. This pattern scales well: the same schema can be reused for updates with partial=True, and the error shape stays stable as the API grows.


Related snips

ruby
class SignupForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :account_name, :string
  attribute :email, :string

Shallow Controller, Deep Params: Form Object Pattern

rails activemodel form-object
by codesnips 3 tabs
javascript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  setTimeout(() => {
    if (success) {

Promises and async/await patterns for asynchronous JavaScript

javascript promises async-await
by Alex Chang 1 tab
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Form Validation Example</title>
  <style>

HTML forms with validation and accessibility

html forms validation
by Alex Chang 1 tab
ruby
class PostSerializer < ActiveModel::Serializer
  attributes :id, :title, :excerpt, :body, :published_at, :views, :likes_count, :comments_count

  attribute :can_edit, if: :current_user_can_edit?

  belongs_to :author, serializer: UserSummarySerializer

Serializers with ActiveModel::Serializers

rails api serialization
by Alex Kumar 2 tabs
ruby
class CreateDeadJobs < ActiveRecord::Migration[7.1]
  def change
    create_table :dead_jobs do |t|
      t.string  :jid, null: false
      t.string  :queue, null: false
      t.string  :klass, null: false

Background Job Dead Letter Queue (DLQ) Table

rails reliability background-jobs
by codesnips 4 tabs
ruby
class PostsController < ApplicationController
  def index
    posts = Post.for_feed.page(params[:page]).per(25)

    render json: {
      data: posts.map { |post| PostSerializer.new(post).as_json },

N+1 Proof Serialization with preloaded associations

rails activerecord performance
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Field-Level Signup Validation in Flask with Marshmallow Schemas — share card
Link copied