Wagtail API¶
This project exposes a custom Wagtail API from:
/api/v2/
The API is built on Wagtail's v2 API router with custom endpoint viewsets in app/api/urls.
Authentication¶
API authentication is controlled by WAGTAILAPI_AUTHENTICATION.
When enabled, endpoints require authentication via one of:
- API token (recommended for services)
- Authenticated Wagtail admin session user
Token and session fallback is implemented by TokenOrUserAuthentication in app/api/auth.py.
For API token management commands, see API tokens.
Endpoint overview¶
All API endpoints are registered in app/api/urls/__init__.py.
How to add a new endpoint¶
Use this workflow when creating new API routes.
1. Choose the endpoint type¶
Pick the base class based on response shape:
- Extend
CustomPagesAPIViewSetwhen returning Wagtail page content with page filters, pagination, and default page serialization behavior. - Extend
GenericViewSetwhen returning non-page or aggregate payloads. - Extend a specific Wagtail endpoint class (for example
ImagesAPIViewSetorMediaAPIViewSet) when you need to customize existing Wagtail endpoints.
2. Create a new viewset module¶
Create a file in app/api/urls/, for example app/api/urls/my_feature.py.
Page-based example:
from wagtail.api.v2.views import path
from app.api.urls.pages import CustomPagesAPIViewSet
from app.my_feature.models import MyFeaturePage
class MyFeatureAPIViewSet(CustomPagesAPIViewSet):
model = MyFeaturePage
known_query_parameters = CustomPagesAPIViewSet.known_query_parameters.union(
["my_filter"]
)
@classmethod
def get_urlpatterns(cls):
return [
path("", cls.as_view({"get": "listing_view"}), name="listing"),
]
Aggregate/custom-response example:
from django.conf import settings
from django.urls import path
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from app.api.permissions import IsAPITokenAuthenticated
class MySummaryAPIViewSet(GenericViewSet):
if settings.WAGTAILAPI_AUTHENTICATION:
permission_classes = (IsAPITokenAuthenticated,)
def summary_view(self, request):
return Response({"status": "ok"})
@classmethod
def get_urlpatterns(cls):
return [
path("summary/", cls.as_view({"get": "summary_view"}), name="summary"),
]
3. Register the endpoint in the API router¶
Edit app/api/urls/__init__.py:
from app.api.urls.my_feature import MyFeatureAPIViewSet
api_router.register_endpoint("my_feature", MyFeatureAPIViewSet)
This mounts the endpoint at /api/v2/my_feature/.
4. Add filters or query parameters (if needed)¶
- Put reusable filters in
app/api/filters.py. - Add accepted params to
known_query_parametersto avoid "unknown parameter" errors. - Keep validation errors explicit by raising
BadRequestErrorwith a clear message.
5. Apply auth rules consistently¶
If the endpoint should follow normal API auth behavior, add:
if settings.WAGTAILAPI_AUTHENTICATION:
permission_classes = (IsAPITokenAuthenticated,)
Only skip this when an endpoint is intentionally public.
6. Add tests¶
Create or update tests in app/api/tests/:
- success response shape
- auth required vs disabled behavior
- filter/query param validation
- edge cases (empty results, invalid inputs)
Run:
docker compose exec app poetry run pytest app/api/tests
Project extensions to the default Wagtail API¶
1. Extended page responses¶
CustomPagesAPIViewSet extends PagesAPIViewSet with:
meta.breadcrumbsin page detail responses- additional
metafields:privacy,last_published_at,url,depth - support for
html_pathlookup, including redirect resolution - support for
descendant_of_pathfiltering - support for
authorfiltering and alias handling (include_aliases)
2. Privacy-aware page detail behavior¶
For restricted pages:
- list responses exclude restricted subtrees
- detail responses return a locked payload with privacy metadata
- password-protected pages can be fetched by passing
passwordin query params
3. Site-aware querying¶
Several endpoints use site-aware filtering:
sitequery parameter support for site-specific content- fallback to the default Wagtail site where appropriate
- redirects endpoint includes both site-specific and global redirects
4. Custom serializers and payloads¶
DefaultPageSerializerbuilds response data from each page model'sdefault_api_fieldsandapi_fields.- Images and media endpoints use UUID-based lookup and include custom payload fields.
- Global and catalogue endpoints provide aggregate, frontend-oriented payloads.
Endpoint-specific behavior¶
Pages: /api/v2/pages/¶
Custom query parameters include:
passwordauthorinclude_aliasesdescendant_of_path- standard Wagtail API query parameters
Useful patterns:
- Resolve by route path:
?html_path=/some/path/ - Filter to a tree branch:
?descendant_of_path=/education/ - Include aliases:
?include_aliases=true
Blog posts: /api/v2/blog_posts/¶
Adds filters:
yearmonth(requiresyear)day(requiresyearandmonth)author
Adds custom endpoints:
/api/v2/blog_posts/count/for grouped post totals by year/month/api/v2/blog_posts/authors/for author/post counts
Education resources and sessions¶
/api/v2/education/resources/supports taxonomy filters:key_stagetime_periodtheme/api/v2/education/sessions/supports taxonomy and location filters:key_stage,time_period,themelocation,region
Session listings also apply a current-or-future filter.
Events: /api/v2/events/¶
Supports:
- location filters:
online,at_tna - inclusive date range filters:
from,to(ISO date)
Redirects: /api/v2/redirects/¶
Extensions include:
is_permanentin payloadssitefilter support
Page preview: /api/v2/page_preview/¶
Preview lookups require:
content_typeinapp_label.modelformat- preview
token
This endpoint resolves content via wagtail_headless_preview.
Article tags: /api/v2/article_tags/¶
Supports:
tagsas comma-separated slugs (required)- optional
limit(defaults to3)
Images and media¶
/api/v2/images/uses UUIDs and includes generated rendition metadata./api/v2/media/uses UUIDs and includes chapters/subtitles metadata.
Globals and catalogue¶
/api/v2/globals/notifications/returns global alert and mourning notice data./api/v2/globals/navigation/returns primary/secondary/footer navigation blocks./api/v2/catalogue/landing/returns homepage notification data plus "explore the collection" sections.