(headless)= # Headless support Wagtail has good support for headless sites, but there are some limitations developers should take into account when using Wagtail as a headless CMS. This page covers most topics related to headless sites, and tries to identify where you might run into issues using ✅ (good support), ⚠️ (workarounds needed or incomplete support) and 🛑 (lacking support). Wagtail maintains a current list of issues tagged with #headless on [GitHub](https://github.com/wagtail/wagtail/issues?q=is%3Aopen+is%3Aissue+label%3AHeadless) (headless_api)= ## API There are generally two popular options for API when using Wagtail as a headless CMS, REST and GraphQL. ### ✅ REST REST (or REpresentational State Transfer) was introduced in 2000 as a simpler approach to machine-to-machine communication using the HTTP protocol. Since REST was introduced, RESTful APIs have proliferated across the web to the point where they're essentially the default standard for modern APIs. Many headless content management systems use either RESTful architecture or GraphQL for their APIs. Both options work with headless Wagtail, so let's explore the upsides and downsides of choosing REST. #### Upsides of a REST API - Requests can be sent using common software like cURL or through web browsers. - The REST standards are open source and relatively simple to learn. - REST uses standard HTTP actions like GET, POST, and PUT. - REST operations require less bandwidth then other comparable technologies (such as SOAP). - REST is stateless on the server-side, so each request is processed independently. - Caching is manageable with REST. - REST is more common currently and there are many more tools available to support REST. - The REST API is a native feature of Wagtail with some functionality already built in. #### Downsides of a REST API - Sometimes, multiple queries are required to return the necessary data. - REST isn't always efficient if a query requires access to multiple endpoints. - Requests to REST APIs can return extra data that's not needed. - REST depends on fixed data structures that can be somewhat difficult to update. ```{note} If you don't want to use Wagtail's built-in REST API, you can build your own using the [Django REST framework](https://www.django-rest-framework.org/). Remember, Wagtail is just Django. ``` ### ✅ GraphQL GraphQL is a newer API technology than REST. Unlike REST, GraphQL isn't an architecture; it's a data query language that helps simplify API requests. GraphQL was developed by Facebook (now Meta) and open sourced in 2015. It's a newer technology that was designed to provide more flexibility and efficiency than REST. Besides REST, GraphQL is currently the only other API technology that is recommended for headless Wagtail. Let's have a look at the current upsides and downsides of choosing GraphQL. #### Upsides of GraphQL - Changes can be made more rapidly on the client-side of a project without substantial backend updates. - Queries can be more precise and efficient without over- or under-fetching data. - You can use fewer queries to retrieve data that would require multiple endpoints in REST. - GraphQL APIs use fewer resources with fewer queries. - GraphQL provides options for analytics and performance monitoring. #### Downsides of GraphQL - GraphQL is not natively supported in Wagtail. - You will need to install a library package to use GraphQL. - There are currently fewer tools and resources available for supporting GraphQL. - Fewer developers are familiar with GraphQL. - GraphQL can introduce additional performance and security considerations due to its flexibility. #### GraphQL libraries compatible with Wagtail - [wagtail-grapple](https://github.com/torchbox/wagtail-grapple) by Torchbox - [strawberry-wagtail](https://github.com/patrick91/strawberry-wagtail) by Patrick Arminio ## Functionality ### ⚠️ Page preview Previews need a workaround currently. There currently isn’t a way to request a draft version of a page using the public API. We typically recommend [wagtail-headless-preview](https://github.com/torchbox/wagtail-headless-preview), a mature and widely used third-party package. When autosave is released in Wagtail, generating previews will likely be less of an obstacle since the API would be serving up the latest changes in all circumstances. This is can be achieved using a [workaround](https://github.com/cfpb/wagtail-sharing/pull/47). (headless_user_bar)= ### ✅ User bar In a cross-domain headless frontend, the [user bar](wagtailuserbar_tag) must be loaded in order to enable certain features in the page editor, such as live preview scroll restoration, the accessibility checker, and content metrics. The user bar can be added to the frontend by creating a Django view that renders the user bar template, which is then loaded by the frontend. For example, the Django view could be written like the following: ```py # views.py from django.views.generic import TemplateView from wagtail.admin.userbar import Userbar class UserbarView(TemplateView): template_name = Userbar.template_name http_method_names = ["get"] def dispatch(self, request, *args, **kwargs): response = super().dispatch(request, *args, **kwargs) response["Access-Control-Allow-Origin"] = "https://my.headless.site" return response def get_context_data(self, **kwargs): return Userbar(object=None, position="bottom-right").get_context_data( super().get_context_data(request=self.request, **kwargs) ) ``` ```py # urls.py from .views import UserbarView urlpatterns = [ # ... path("userbar/", UserbarView.as_view(), name="wagtail_userbar"), ] ``` Then, the frontend can load the user bar by making a request to the `/userbar/` endpoint and render the response in the appropriate place in the DOM. The following example is a React component in a Next.js app that loads the user bar: ```tsx 'use client'; import Script from 'next/script'; import { useEffect, useRef } from 'react'; export default function Userbar({ hidden = false }: { hidden?: boolean }) { const userbarRef = useRef(null); const apiHost = process.env.NEXT_PUBLIC_WAGTAIL_API_HOST as string; useEffect(() => { fetch(`${apiHost}/userbar/`) .then((res) => res.text()) .then((userbar) => { if ( !userbarRef.current || // useEffect runs twice in development mode, so we need to bail out if // the userbar is already present from the previous fetch() call. userbarRef.current.querySelector('wagtail-userbar') ) return; userbarRef.current.innerHTML = userbar; }); }, [apiHost]); return ( <>