Skip to content

Latest commit

 

History

History
193 lines (138 loc) · 7.25 KB

File metadata and controls

193 lines (138 loc) · 7.25 KB

Plain

The Python web framework for building apps.

Originally a fork of Django, reshaped over years of real use. Ready for the era of agents.

Get started

Start with an agent (Claude, Codex, Amp, OpenCode, or your agent of choice):

mkdir my-app && cd my-app && claude "$(curl -sSf https://plainframework.com/start.md)"

Or start with uv directly:

uvx plain-start my-app

Full walkthrough: https://plainframework.com/start/

What Plain code looks like

Explicit, typed, and predictable. What's good for humans is good for AI.

Models are Postgres-only:

# app/users/models.py
from plain import postgres
from plain.postgres import types
from plain.passwords.models import PasswordField

@postgres.register_model
class User(postgres.Model):
    email: str = types.EmailField()
    password: str = PasswordField()
    display_name: str = types.TextField(max_length=100)
    is_admin: bool = types.BooleanField(default=False)
    created_at: datetime = types.DateTimeField(create_now=True)

    query: postgres.QuerySet[User] = postgres.QuerySet()

    model_options = postgres.Options(
        constraints=[
            postgres.UniqueConstraint(fields=["email"], name="unique_email"),
        ],
    )

URLs use a Router class:

# app/users/urls.py
from plain.urls import Router, path
from . import views

class UsersRouter(Router):
    namespace = "users"
    urls = [
        path("<int:pk>/", views.UserDetail),
    ]

Views are class-based:

# app/users/views.py
from plain.views import DetailView
from .models import User

class UserDetail(DetailView):
    template_name = "users/detail.html"

    def get_object(self):
        return User.query.get(pk=self.url_kwargs["pk"])

Templates are Jinja:

{# app/users/templates/users/detail.html #}
{% extends "base.html" %}

{% block content %}
<h1>{{ user.display_name }}</h1>
<p>Joined {{ user.created_at.strftime("%B %Y") }}</p>
{% endblock %}

An opinionated stack

Python where you want it, JS where you need it.

  • Python: 3.13+
  • Database: Postgres
  • Templates: Jinja2
  • Frontend: htmx, Tailwind CSS
  • Python tooling: uv (packages), ruff (lint/format), ty (type checking)
  • JavaScript tooling: oxc (lint/format), esbuild (bundling)
  • Testing: pytest

Models declare fields as annotated attributes, and that typing carries through views, forms, and URLs. plain check runs ty on every pass — what your IDE shows, CI enforces, and agents read from the same signatures.

Observability at the core

OpenTelemetry traces, a built-in request observer, and slow-query detection ship in the box. The first time an N+1 matters, you already have the tools to see it.

Agents at the forefront

Predictable APIs, typed signatures, and on-demand docs happen to be what both people and coding agents need. Plain projects also ship tooling that agents use automatically.

Rules — Always-on guardrails stored in project rules files (e.g. .claude/rules/ for Claude Code). Short files (~50 lines) that prevent the most common mistakes.

Docs — Full framework documentation, accessible on demand from the command line:

plain docs models                      # full docs
plain docs models --section querying   # one section
plain docs models --api                # typed signatures only
plain docs --search "queryset"         # search across all packages

Skills — End-to-end workflows triggered by slash commands:

  • /plain-install — add a new package and walk through setup
  • /plain-upgrade — bump versions, read changelogs, apply breaking changes, run checks
  • /plain-optimize — capture performance traces, identify slow queries and N+1 problems, apply fixes
  • /plain-bug — collect context and submit a bug report as a GitHub issue

First-party ecosystem

30 packages, one framework. All with built-in docs. Decisions that usually take a sprint are already made.

Foundation:

Backend:

Frontend:

Development:

Production:

Users:

About

Plain is a fork of Django, started in the stone age of 2023 and driven by real use at PullApprove.