docs/advanced_topics/content_personalization.md
(content_personalization)=
Wagtail’s built-in features can go a long way to meet content personalization requirements. We can configure StreamField blocks to only display for specific segments based on the request context. In the editor, preview modes give users a way to review each segment before publishing. Editorial teams can then tailor content for different audience segments within a page and preview how the content appears for each segment.
This supports the following use cases:
For more advanced segmentation or analytics-driven targeting, consider the external wagtail-personalisation package. Check out our Content personalization explainer for a birds’ eye view of how core features and the package differ in scope.
This example defines a block that stores rich text alongside a segment choice. We use a custom form_layout and BlockGroup to keep the segment configuration in a collapsed "Settings" section. The get_context method of the block class reads the request to determine the visitor segment.
Make sure to use {% include_block %} when rendering StreamField content so the block receives the parent context, including the request object used by get_context.
class SegmentedContentBlock(StructBlock):
content = RichTextBlock()
segment = ChoiceBlock(
choices=[
("all", "All visitors"),
("logged_in", "Logged-in users"),
("anonymous", "Anonymous visitors"),
],
default="all",
)
def get_context(self, value, parent_context=None):
context = super().get_context(value, parent_context)
request = parent_context.get("request") if parent_context else None
preview = getattr(request, "personalization_preview_segment", None)
context["is_authenticated"] = (
preview == "logged_in"
if preview
else (request.user.is_authenticated if request else False)
)
return context
class Meta:
icon = "group"
template = "blocks/segmented_content_block.html"
preview_value = {
"content": "<p>Welcome back! Exclusive content for members.</p>",
"segment": "logged_in",
}
description = "Content targeted to specific audience segments"
form_layout = BlockGroup(
children=["content"],
settings=["segment"],
)
This type of simple segmentation is simple to add to any StructBlock. See for details on grouping and ordering StructBlock fields, for scenarios where blocks are already more complex.
The template conditionally renders content based on the chosen segment. Here, we also style the block differently for each segment.
{% load wagtailcore_tags %}
{% if self.segment == "all" %}
<div class="segmented-content">{{ self.content|richtext }}</div>
{% elif self.segment == "logged_in" and is_authenticated %}
<div class="segmented-content segmented-content--logged-in">{{ self.content|richtext }}</div>
{% elif self.segment == "anonymous" and not is_authenticated %}
<div class="segmented-content segmented-content--anonymous">{{ self.content|richtext }}</div>
{% endif %}
You can use preview modes on the page to let editors switch between anonymous and logged-in variants. This mixin stores the selected mode on the request so the block’s get_context can apply it during preview.
class PersonalizationPreviewMixin:
default_preview_mode = "anonymous"
preview_modes = [
("anonymous", _("Anonymous visitor")),
("logged_in", _("Logged-in user")),
]
def serve_preview(self, request, mode_name):
request.personalization_preview_segment = mode_name
return super().serve_preview(request, mode_name)
Wagtail also has built-in support for page-level segmentation, with two of its features.
The routable pages feature can be set up to serve variations of the page at different routes. This is particularly useful to create campaign-specific links, without having to duplicate pages and have the site content expand uncontrollably over time.
Wagtail has built-in support for private pages, so you can create pages or whole sections of a site that are only accessible for logged-in users, or users in specific groups.