skrift-forms
Skrift form system — Pydantic-backed forms with CSRF protection, template rendering, and hook integration.
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.zipInstalls 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.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
| Key | Values | Purpose |
|---|---|---|
label | str | Custom label text |
widget | "textarea", "select", "checkbox" | Widget type |
input_type | "email", "password", etc. | HTML input type |
help_text | str | Help text below field |
choices | list[tuple[str, str]] | Options for select widget |
attrs | dict[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}.html → form.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
Form.__init__()— Createssecrets.token_urlsafe(32)in session if absent (key:_csrf_token)csrf_field()— Renders<input type="hidden" name="_csrf" value="{token}">validate()— Checks submitted_csrfagainst session token viahmac.compare_digest()- 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 explicitlyget_form_model(name)retrieves by name, raisesLookupErrorif 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
| File | Purpose |
|---|---|
skrift/forms/core.py | Form — CSRF, validation, rendering |
skrift/forms/model.py | FormModel base class, _form_registry, get_form_model() |
skrift/forms/fields.py | BoundField — field bound to form instance |
skrift/forms/decorators.py | @form() decorator |
skrift/templates/form.html | Default form template |