Use when editing, reviewing, or auditing DRF viewsets and serializers in PostHog. Triggers on files in posthog/api/, products/*/backend/api/, products/*/backend/presentation/, or any file importing rest_framework serializers or viewsets. Covers OpenAPI spec quality, field typing, schema annotations, and DRF best practices that flow through the type pipeline to generated TypeScript types and MCP tools.
Serializer fields are the source of truth for PostHog's entire type pipeline:
Django serializer → drf-spectacular → OpenAPI JSON → Orval → Zod schemas → MCP tools
Every help_text, every field type, every @extend_schema annotation flows downstream.
A missing help_text means an agent guessing at parameters.
A bare ListField() means z.unknown() in the generated Zod schema.
Getting the serializer right means every consumer — frontend types, MCP tools, API docs — gets correct types and descriptions automatically.
Serializer or ViewSetBefore diving into Python, look at the committed generated types to see what's broken. Find the generated files for the endpoint's product:
frontend/src/generated/core/products/<product>/frontend/generated/Each has two files:
api.schemas.ts — TypeScript interfaces derived from serializers. Search for the serializer name and look for unknown types (bare ListField/JSONField), missing JSDoc descriptions (missing help_text), or overly generic Record<string, unknown> shapes.api.ts — API client functions. Check if the endpoint's operation exists at all — if missing, the viewset method likely lacks @extend_schema.This tells you exactly which fields and endpoints to prioritize.
Work through this list for every serializer and viewset you touch.
help_text — describes purpose, format, constraints, valid valuesListField() or DictField() — always specify child= with a typed serializer or fieldJSONField() — create a custom field class with @extend_schema_field(TypedSchema)SerializerMethodField has @extend_schema_field on its get_* methodChoiceField has explicit choices= with all valid values listedSee serializer-fields.md for patterns and examples.
@action has @extend_schema or @validated_request — without it, drf-spectacular discovers zero parametersViewSet methods have schema annotations — ModelViewSet with serializer_class is auto-discovered; plain ViewSet is not@extend_schema is on the actual method (get, post, create, list), not on a helper or the class itselfOpenApiResponse(response=ErrorSerializer), not OpenApiTypes.OBJECTpagination_class=None on custom actions that don't paginate@validated_request over manual serializer.is_valid() + @extend_schema — it handles both in one decoratorproducts/ need @extend_schema(tags=["<product>"]) — ViewSets in products/<name>/backend/ are auto-tagged via module path, but ViewSets in posthog/api/ or ee/ are not. Without the tag, the MCP scaffold and frontend type generator can't route the endpoint to the right productStreaming endpoints: For SSE or streaming responses, use @extend_schema(request=InputSerializer, responses={(200, "text/event-stream"): OpenApiTypes.STR}) to document the request schema even though the response can't be fully typed.
See viewset-annotations.md for patterns and examples.
For products using the facade pattern (e.g., visual_review) with DataclassSerializer wrapping frozen dataclasses from contracts.py:
help_text (dataclass fields don't carry it; add it on the serializer field overrides)@validated_request is already the standard pattern — verify response serializers are declared@extend_schema tags and descriptions still need to be set on viewset methodsdigraph audit {
rankdir=TB
node [shape=diamond fontsize=10]
edge [fontsize=9]
start [label="Serializer or\nViewSet file?" shape=box]
is_model [label="ModelViewSet with\nserializer_class?"]
is_plain [label="Plain ViewSet or\ncustom @action?"]
is_facade [label="DataclassSerializer\n(facade product)?"]
check_fields [label="Check fields:\nhelp_text, ListField,\nJSONField, ChoiceField" shape=box]
add_schema [label="Add @validated_request\nor @extend_schema to\nevery method" shape=box]
check_help [label="Focus on help_text\nand response declarations" shape=box]
check_responses [label="Check response types,\npagination, error schemas" shape=box]
start -> is_model
is_model -> check_fields [label="yes"]
is_model -> is_plain [label="no"]
is_plain -> add_schema [label="yes"]
is_plain -> is_facade [label="no"]
is_facade -> check_help [label="yes"]
check_fields -> check_responses
add_schema -> check_fields
check_help -> check_responses
}
See quick-reference-table.md for a scannable "I see X, do Y" lookup.
See common-anti-patterns.md for before/after code pairs.
posthog/api/alert.pyproducts/tasks/backend/api.pyproducts/llm_analytics/backend/api/evaluation_summary.pyproducts/visual_review/backend/presentation/views.pyimplementing-mcp-tools skill to scaffold MCP toolsdocs/published/handbook/engineering/type-system.mdposthog/api/mixins.py (@validated_request source)posthog/settings/web.py (SPECTACULAR_SETTINGS)