SK

A secure form management system for Python apps using Pydantic models with built-in CSRF protection and validation hooks.

Install

mkdir -p .claude/skills/skrift-forms && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16447" && unzip -o skill.zip -d .claude/skills/skrift-forms && rm skill.zip

Installs to .claude/skills/skrift-forms

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Skrift form system — Pydantic-backed forms with CSRF protection, template rendering, and hook integration.
106 charsno explicit “when” trigger
Intermediate

Key capabilities

  • Define web forms using Pydantic models
  • Automatically register forms for use
  • Render forms automatically or manually in templates
  • Validate form submissions with Pydantic
  • Implement CSRF protection for forms
  • Integrate hooks for post-validation processing

How it works

The skill combines Pydantic models with a Form class to handle form definition, rendering, CSRF protection, and validation, allowing for structured data processing and hook integration.

Inputs & outputs

You give it
Pydantic BaseModel or FormModel definition, and an HTTP request object
You get back
A rendered HTML form, validated data, or validation errors

When to use skrift-forms

  • Implement secure contact form validation
  • Automate CSRF protection for user input forms
  • Link Pydantic data models directly to frontend templates

About this skill

Skrift Form System

Skrift forms combine Pydantic validation with CSRF protection, template rendering, and hook integration.

Architecture

FormModel/BaseModel → Form(model, request) → validate() → hooks → data
      │                      │                    │           │
      │                      │                    │           ├─ form_{name}_validated
      │                      │                    │           └─ form_validated
      │                      │                    │
      │                      │                    ├─ CSRF verify (hmac.compare_digest)
      │                      │                    ├─ Token rotation (single-use)
      │                      │                    ├─ Checkbox injection (bool fields)
      │                      │                    └─ Pydantic validation
      │                      │
      │                      ├─ render() → Template("form", name).try_render()
      │                      ├─ csrf_field() → hidden input
      │                      ├─ fields → dict[str, BoundField]
      │                      └─ errors → dict[str, str]
      │
      └─ Registered in _form_registry (by form_name)

Defining Forms

FormModel (auto-registers)

from skrift.forms import FormModel
from pydantic import Field, EmailStr

class ContactForm(FormModel, form_name="contact"):
    name: str
    email: EmailStr
    message: str = Field(json_schema_extra={"widget": "textarea"})

@form() Decorator (registers a plain BaseModel)

from pydantic import BaseModel
from skrift.forms import form

@form("newsletter", action="/subscribe", method="post")
class NewsletterForm(BaseModel):
    email: str

Field Customization via json_schema_extra

KeyValuesPurpose
labelstrCustom label text
widget"textarea", "select", "checkbox"Widget type
input_type"email", "password", etc.HTML input type
help_textstrHelp text below field
choiceslist[tuple[str, str]]Options for select widget
attrsdict[str, str]Extra HTML attributes

Full example:

class ContactForm(FormModel, form_name="contact"):
    name: str = Field(json_schema_extra={
        "label": "Your Name",
        "attrs": {"placeholder": "Jane Doe"},
    })
    email: EmailStr = Field(json_schema_extra={
        "label": "Email Address",
        "input_type": "email",
        "help_text": "We'll never share your email.",
    })
    subject: str = Field(json_schema_extra={
        "widget": "select",
        "choices": [
            ("general", "General Inquiry"),
            ("support", "Technical Support"),
        ],
    })
    message: str = Field(json_schema_extra={
        "widget": "textarea",
        "attrs": {"rows": "6"},
    })
    subscribe: bool = Field(default=False, json_schema_extra={
        "label": "Subscribe to newsletter",
    })

Controller GET/POST Pattern

from litestar import Controller, get, post, Request
from litestar.response import Template as TemplateResponse, Redirect
from skrift.forms import Form

class ContactController(Controller):
    path = "/contact"

    @get("/")
    async def show(self, request: Request) -> TemplateResponse:
        form = Form(ContactForm, request)
        return TemplateResponse("contact.html", context={"form": form})

    @post("/")
    async def submit(self, request: Request) -> TemplateResponse | Redirect:
        form = Form(ContactForm, request)
        if await form.validate():
            # form.data is a validated ContactForm instance
            await process_contact(form.data)
            return Redirect("/contact?thanks=1")
        return TemplateResponse("contact.html", context={"form": form})

Template Rendering

Automatic

{{ form.render() }}
{{ form.render(submit_label="Send") }}

Manual

<form method="{{ form.method }}">
    {{ form.csrf_field() }}
    {% for field in form %}
        {{ field.label_tag() }}
        {{ field.widget() }}
    {% endfor %}
    <button type="submit">Send</button>
</form>

Custom Form Template

Template hierarchy: form-{name}.htmlform.html → programmatic fallback.

{# templates/form-contact.html #}
<form method="{{ form.method }}" class="contact-form">
    {{ form.csrf_field() }}

    {% if form.form_error %}
        <div class="alert">{{ form.form_error }}</div>
    {% endif %}

    <div class="row">
        <div class="col">
            {{ form['name'].label_tag() }}
            {{ form['name'].widget(class_="form-input") }}
        </div>
        <div class="col">
            {{ form['email'].label_tag() }}
            {{ form['email'].widget(class_="form-input") }}
        </div>
    </div>

    {{ form['message'].label_tag() }}
    {{ form['message'].widget(class_="form-input", rows="8") }}

    <button type="submit">{{ submit_label }}</button>
</form>

CSRF Flow

  1. Form.__init__() — Creates secrets.token_urlsafe(32) in session if absent (key: _csrf_token)
  2. csrf_field() — Renders <input type="hidden" name="_csrf" value="{token}">
  3. validate() — Checks submitted _csrf against session token via hmac.compare_digest()
  4. Token rotation — New token generated after successful check (single-use)

Registration Mechanism

Forms are registered in a global _form_registry dict (in model.py):

  • FormModel.__init_subclass__() auto-registers on class creation
  • @form() decorator registers explicitly
  • get_form_model(name) retrieves by name, raises LookupError if missing

Hook Integration

Two filter hooks fire after successful validation:

# Form-specific: e.g. "form_contact_validated"
self.data = await hooks.apply_filters(f"form_{self.name}_validated", self.data)

# Global: "form_validated"
self.data = await hooks.apply_filters("form_validated", self.data, self.name)

Example hook:

from skrift.hooks import filter

@filter("form_contact_validated")
async def sanitize_contact(data):
    data.message = data.message.strip()
    return data

@filter("form_validated")
async def log_all_forms(data, name):
    print(f"Form '{name}' submitted")
    return data

Jinja2 Global

The Form class is registered as a Jinja2 global, making it accessible in templates without passing it through context.

Key Files

FilePurpose
skrift/forms/core.pyForm — CSRF, validation, rendering
skrift/forms/model.pyFormModel base class, _form_registry, get_form_model()
skrift/forms/fields.pyBoundField — field bound to form instance
skrift/forms/decorators.py@form() decorator
skrift/templates/form.htmlDefault form template

When not to use it

  • When Pydantic validation is not desired for web forms
  • When CSRF protection is not needed for form submissions

Limitations

  • Requires Pydantic for form definition
  • Relies on a specific template rendering hierarchy
  • Hooks are limited to post-validation processing

How it compares

This skill integrates data validation, security, and rendering into a single Pydantic-backed system, offering a more cohesive and secure approach to web forms than manually handling each aspect.

Compared to similar skills

skrift-forms side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
skrift-forms (this skill)01moNo flagsIntermediate
fastapi-templates5202moNo flagsIntermediate
fastapi-pro794moNo flagsAdvanced
python-pro234moNo flagsAdvanced

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

fastapi-templates

wshobson

Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.

5201,086

fastapi-pro

sickn33

Build high-performance async APIs with FastAPI, SQLAlchemy 2.0, and Pydantic V2. Master microservices, WebSockets, and modern Python async patterns. Use PROACTIVELY for FastAPI development, async optimization, or API architecture.

79181

python-pro

sickn33

Master Python 3.12+ with modern features, async programming, performance optimization, and production-ready practices. Expert in the latest Python ecosystem including uv, ruff, pydantic, and FastAPI. Use PROACTIVELY for Python development, optimization, or advanced Python patterns.

2358

python-configuration

wshobson

Python configuration management via environment variables and typed settings. Use when externalizing config, setting up pydantic-settings, managing secrets, or implementing environment-specific behavior.

542

fastapi-router-py

microsoft

Create FastAPI routers with CRUD operations, authentication dependencies, and proper response models. Use when building REST API endpoints, creating new routes, implementing CRUD operations, or adding authenticated endpoints in FastAPI applications.

525

pydantic-models-py

microsoft

Create Pydantic models following the multi-model pattern with Base, Create, Update, Response, and InDB variants. Use when defining API request/response schemas, database models, or data validation in Python applications using Pydantic v2.

325

Search skills

Search the agent skills registry