Day 2 Welcome Remarks
Published November 19, 2025
This video is from Wagtail Space 2025 in Online.
Campaigns often require multiple landing pages—one for every platform, channel, or audience. For Truth Initiative, this quickly became unmanageable, forcing editors to maintain dozens of nearly identical pages.
We built a dynamic Wagtail landing page that tracks users based on the last URL segment (e.g., /youtube, /facebook). Ability for A/B testing was also included to help test different features against a percentage of the audience. Editors now manage a single page design for tailoring text, CTAs, and creative assets.
This session covers:
Attendees will walk away with a model for tackling personalization and scale—without drowning in duplicate content.
💻 Wagtail is the easiest open-source Python CMS to use:
Install the demo and start building your first site in 10 minutes: https://wagtail.org/get-started/
📹 Related Videos To Watch Next:
â–¶ Quick Video Tour of Wagtail CMS 7.0 https://youtu.be/r5RbV7TveFU
â–¶ The Latest on Wagtail AI https://www.youtube.com/watch?v=4zfs1u4Vy5Y
▶ What’s New in Wagtail CMS 7.0 https://youtu.be/v92-6Dy4axI
Wagtail future proofs your CMS system, as it’s open source, continuously updated and built on Python, one of the most popular global programming languages, used widely in machine learning and big data. So you’re always ahead of the curve when it comes to CMS platforms.
Wagtail is the #1 choice for accessibility, is scalable and most importantly, secure.
👉 Get started with Wagtail CMS for free: https://wagtail.org/get-started/
and see how easy it is to build a website that works for you.
📊 Read why Google, NASA, and the British NHS, are powering their digital estates with Wagtail: https://wagtail.org/about-wagtail/
🎥 More Wagtail Videos: https://www.youtube.com/watch?v=cne2kxemMAQ&list=PLfwZ-fob20cPvSQ_v1hkjto8BAPN21tLJ
📣 Follow us on social:
#WagtailCMS #Django
Truth Initiative replaced separate campaign pages with a single Wagtail campaign page using RoutablePageMixin, so many readable URL paths can serve the same content while passing a campaign slug into the page context and signup form for attribution. This reduced editor and marketing-team work: new channels no longer require duplicate pages or coordinated URL and tracking changes. They also demonstrated Wagtail’s A/B testing add-on, including a custom form-submission conversion goal, and described plans to explore full-page tests and TikTok in-app form enrollments.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Speaker 1: Alright, uh on the dots we're gonna get going. Um thank you Doug and Chrissy for being here. I'll hand over to you to present your talk.
Speaker 2: All right, thank you. So our talk is called One URL to Rule Them All, Dynamic Landing Wag Landing Pages in Wagtail. So we are going to talk about campaign pages that we did for Truth's website and how we set them up to make life easier for the marketing team.
Speaker 3: I'm Doug Harris, Vice President of Software Development for Truth Initiative. Truth Initiative is the nation's largest nonprofit public health organization dedicated to preventing nicotine addiction and empowering quitting. And I guess I should qualify by nation. I mean the United States, sorry for my American-centric speaking there.
Speaker 2: And I'm Chrissy Wainwright. I am a senior developer for Six Feet Up, and we are a software consultancy. We worked with Truth Initiative to help them with their website, including these campaign pages that we're going to talk about.
Speaker 3: So X program is a site focused on it's a digital program for helping people quit tobacco using web and SMS tools. In December of 2024, we are launching a new marketing campaign for this for our site. It's a very content-heavy site, lots of information about methods about why tobacco is so addictive, about methods for um for quitting tobacco. But as you can see with these long wordy pages, they don't really get the attention. So we have these campaign landing pages that are meant to draw people's attention to what we do in a kind of a more pithy way. And with a direct call to action to have people join for the site.
Speaker 3: Join the site. Now the challenge we have on this is we want to support many, many possible URLs driving to these, and I'll have some examples coming with the same experience. with reliable tracking for our marketing group so that we're able to determine which channels, marketing channels are working well. And low effort for editors. So we want to have one place to make the change. So for example, users coming from ads on TikTok would go to a site like this, join. ex program slash TikTok, similarly for slash Reddit, but again showing the same content, really just a difference in URL. We might also have a different path for slightly different creative, so we have kind of two broad categories of creative.
Speaker 3: Some are meant to build awareness and others are meant to for conversion, meaning really encouraging people to join our program. Where we were previously is we were migrating from a non-Django CMS. I won't say what it is, but I'll just say that it rhymes with RuPal. Each path was a separate page, which meant if we had a new marketing channel, there was someone adding a new page, which meant that if there was a change to any language or creative, each page had to be edited. And adding a new marketing tag required three or four people doing different tasks. So creating this new page, a new keyword for SMS entry, new tracking. And we wanted to get past that. So that was part of our motivation to move this all to Wagtail.
Speaker 3: Additionally, our main Site X program was already running on Django and Wagtail.
Speaker 2: So the solution that we implemented in Wagtail was to use a routable page mix-in along with a campaign page model. The campaign page model is a typical model with its fields defined for the dynamic content, but the magic is in the routable page mix-in. And what this is, is it allows you to define various paths that can be used against the same content object. So just a single object in your site instead of creating many separate views and defining all of the paths in the URLs. py file. Everything is managed within the model file. So I have some examples to show you, and these are from the Wagtail docs showing the different functions that can be used for the different paths.
Speaker 2: So this example here would be like for events. You may have a page for a single event, but with that same concept object, you may want to be able to display previous events or events from a specific year. So the ways that Radable Page Mixing can help you with that , you can see defined I have in bold the decorators used on the functions So the app path decorator, the first one there, has an empty string, and that will override the default page serving mechanism from Wagtail. the next one down. Yeah, stay there. The path for past. So you can add these decorators onto separate functions. just that whatever path users come in through, you have a different function for each one to define
Speaker 2: exactly what that page is going to look like for them. A couple more examples. You can use multiple routes. You can define multiples for a single function. So the top one there uses you can just say like for any specific year. Or it has a variable in there, you know, whatever year they end up entering, and or also for the current year. And then the last example there you can see is slightly different. Instead of at path, it's at repath, because that allows you to use regex in your path matching. So if you want to use this yourself to install the routable page mixin, add the wagtail. contrib. routable under
Speaker 2: page to the list of installed apps. And then in for the model, you'll need to import the Radable Page Mixin and also path and repath if you are using that. In our case, we just needed path. And then you also need to import from Wagtail that models the page. So our custom campaign page model. will inherit from the routable page mixin and it also needs to import the from page. So you can see that in the class definition. And you can have other mixins in there as well, which we did, but these are the two important ones that you will need. So some further setup then including the path decorators on the serve function.
Speaker 2: So while the examples earlier defined different functions depending on the URL, we only needed a single function, no matter what path users were getting there from. We did not need to display different content per campaign. We just needed to know where the users were coming from and do it with a URL structure that matched what was already existing on the previous site. So the extra bit from the URL gets saved as a variable called campaign under slug, which we made sure to include in the objects context. You can see it gets passed to the context here in the function. So notice we aren't defining any specific campaigns peer here. It's just left open so that as new campaigns are added coming from other sources, they don't have to be managed here. This one just stays pretty open.
Speaker 2: like whatever URL is appended to the end, it just passes that on through. So tracking the user journey from here How you use a campaign slug to track users is going to vary depending on your setup. Truth's main goal was to track how many people fill out the form to join the program. So we just put the value of that campaign slug as a hidden input on the form. uh so it gets passed to the back end and then the back end can decide what to do with it. In this case it was passing it on to uh a third party site.
Speaker 3: All right, so now that we have this, we wanted to see, we wanted to expand what we could do now that we're controlling uh our campaign landing pages. We wanted to add A-B testing. So as an example here, notice that the call for action button here differs. We wanted to see which one would perform better. Just the result of this was join for free works better. If you're doing any testing. So we wanted to be able to do these sorts of tests where we could where we could try different creators or different languages or whatever and see how they perform. So this is something we're adding as well
Speaker 2: So we're setting up the A-B testing. Another one to add to installed apps is Wagtail under A-B under testing. Further setup in the URLs. py there is a specific path that needs to be entered, and this is all inside of the README for the add-on. And then for the templating, there are a couple template tags that need to be added to the base. html Now we had some problem with this because the Truth 's website is using Ginja for the templating language, and A-B testing doesn't support that by default. So we had to work around and uh the site already had this in place because um had the same issue with other add-ons.
Speaker 2: So we already had a ginger. py file and here we needed to add the to the globals that basically that template tag. uh to include that bring and bring that in. And so then it was slightly different what we ended up putting into the base. html If you go down to the next slide, just to include the tracking parameters and the JavaScript that gets added to the page. So in the end, this accomplished the same thing, just slightly different way to get around it. So I say tracking users and I want to explain exactly what how that is. For one, it uses local storage. So on on the user's computer
Speaker 2: in their browser when they go visit the page. It's going to keep track in their local storage whether or not they visited the page, if they saw the variant or the control. And this is going to make sure that they always see the same version of the page. So each time you get in there, you're always going to see the variant or you're always going to see the control. And this also makes sure that it's not going to count to multiple visits or conversions for a single user. So to explain the tracking conversions, like I said, we were tracking how many people actually fill out the form. So there's a solid example of how to set up this type of conversion in the A-B testing docs for tracking if the user
Speaker 2: basically clicks that submit button. So, and here's the code for that, mostly just taken from the example and then edited for our own purposes but it defines the submit mobile regform event, which is subclassing base event from the A B testing add-on, and then registering a hook that that works with that. Yep. And then along with that, JavaScript needed to go onto the page to just detect when the user clicks the submit button, and then it calls that hook that we had built in. So now I want to show a demo so that you can see what this A B testing add-on
Speaker 2: actually looks like for the admin users. So on this one, I'm just using a vanilla wagtail site. This is not using the example from truth. You can go ahead and start. So I built this custom page and I'm editing it. I'm going to make a couple little changes just so it's easy to see which version is the control and which version is the variant. Now when you save it, instead of like saving a draft or publishing, you will select Save and Create A B test. Now this will bring up an intermediary page that shows you the difference between the control and the variant and you can click the button to create the test and it'll give you some fields to fill out for this.
Speaker 2: You can name it, you can give it a hypothesis and this is just metadata And then choose a goal. So we created that custom goal for submitting the form. So I'm going to select that from here. And the button about which page you can ignore that for this case. That's used for others. Sample size, I'll just set that to 50. Let's test it on 50 users. And now we'll actually start the test. Now instead of editing the page or seeing the view, you get an activity log. And so this is the page you can come back to regularly to see how your A-B test is going. So I'll open up a few browsers. And see what we get. So here this one, we're seeing the control. I refresh a few times and I'm always seeing the control
Speaker 2: Try a couple more browsers. Now here's one you can see that we've got the variant, and if I refresh a couple times, I still have the variant. Now we'll go ahead and do this in in a couple more browsers just to make sure we have more sample users. So like I said, this uses local storage, so it is going to be different per browser And here's yeah, another one with the control. So now if we go back to the activity log and refresh. You can see there were two users that saw the control, two users that saw the variance, but no conversions at this point. So if we go back and actually submit the form, which in this case I'm just going to click the button.
Speaker 2: I'll do that in a couple browsers. And now go back to the activity log and refresh. You can see now both of those control users click the button. We have 100% conversion on that one. And so far no users on the variant actually submitted the form. So that's what um the A-B testing add-on looks like
Speaker 3: All right. And again, now we are running our campaign pages in Django and Wagtail. And in fact, they actually went live just yesterday. It's very exciting for me and my team. And uh because we are a Django shop and a Wagtail shop, it's all our Django app. So it's all possible now. But the other things we want to do are out of scope for a Wagtail conference. Thank you very much. And we'll take questions now.
Speaker 1: Thank you, Doug and Chris. This is very interesting and I really think lots of projects out there. We wanna take a similar approach. Uh I think I wanna ask first actually, what's those things that are out of scope for a white teleconference that you've been thinking about? Do you have any bits in particular you want to showcase?
Speaker 3: Ah, that's a good question. We we're gonna add more um, well, more more sophisticated A-B testing, really. Uh the Wagtail A-B is good for elements on the page. Um We want to look at exploring ways to do uh full page A-B testing as well. Um, you put me on the spot here. I'm trying to remember what else is on our roadmap for the campaign landing page. So it's escaping me at the moment.
Speaker 1: That's fine. If it comes back, feel free to go for it. I thought you'd mention something with other CMSs that sound like RuPle or something like that. So this is a relief.
Speaker 3: Well, I I guess, yeah, one thing that we are actively developing is um with uh TikTok offers in app forms. So that if you fill out the like a form and get provide a phone number, when it submits, it doesn't leave the TikTok Talk app, which TikTok collects a lot. And basically it implements it as a form submission, which then pings a webhook. And so we're implementing the the uh webhook listener to accept uh enrollments from in-app forms as well.
Speaker 1: Makes sense. Makes sense. It's definitely a recorded conference, but I'm sure lots of people, you know, are in this space and find this really interesting. Okay, uh we we only have two questions at the moment, so there's time for more folks. I'll ask the two and then see uh if more come up Um
Speaker 2: I see there's one in the chat about um asking what's the advantage of using this method as opposed to UTM tracking. So I'm assuming with Matt that you're talking about having the parameter at the end of the URL And actually that was my original go-to what I was thinking of using, but we needed to match the system that was already set up in the other CMS. And that was basically set up with with the paths. And so we wanted to make sure to keep that structure so that moving forward nothing was going to break.
Speaker 3: I think another advantage is it's just it's it's easier to read and if we put this on on a visual creative, you know, you could say, you know, go to join. com slash slash TikTok instead of, or you know, join. com. We work with a number of online influencers as well. There's a Instagram influencer named Katya. So we might have joined she can say join. com join. x program. com slash Katya. It's a lot easier to say than join. com question mark UTM equals whatever.
Speaker 1: A really good point. Definitely value in having nice links. I had a question about the packaging state of things and you know the A-B testing package that is out there right now. I feel like this is a really um useful problem space and definitely common one. Um what do you think of um Making more of this available for other projects to reuse in the current states of those packages
Speaker 3: Kind of packaging the custom work we've done so that it uh reflecting back into the into the upstream A-B testing package.
Speaker 1: Yeah, exactly.
Speaker 3: Yeah. Uh we have not thought about it. I'm certainly open to that
Speaker 1: Okay, great.
Speaker 2: Yeah, the only piece would be that, you know, that custom goal that we had added in and that was in the docs in the README for the A-B testing for how to do that. I mean that was a pretty easy copy paste for me uh to throw it in. But I think it would be nice to have that and some other ones out of the box already available. But of course there would need to be some more configuration with that to make sure that it works with your form.
Speaker 3: I see in the Q<unk>A, Tom Usher asks, what features or changes do we wish Wagtail had out of the box to support the features we want to build? I think adding A B testing native is an interesting idea. The routable plug mix in is already built in and that that's that was a really nice thing to have. And uh I think that as I've said, you know, maybe expanding the ABK testing capabilities will be looking to see how we might uh be able to contribute back on that And then Tom Dyson asks in the chat, how does our approach interact with caching proxies? We're actually using CloudFront in front of this. And well since it's
Speaker 3: since it's local storage and the A B testing, maybe Chrissy, you can expand on this, since it's local storage and it's a lot's happening in the browser. It's all being delivered to the to the end user regardless of how they come through.
Speaker 2: That's a good question. I don't remember exactly how that was working, but I know in the README for A-B testing it talks about how to make it work with caching.
Speaker 3: Yeah. The other thing is is we are being right now we have cloud front in the front of this and for certain paths we we disabled caching from the from the uh caching CDN proxy so that any requests go directly to Django to process this.
Speaker 1: Makes sense. Um I think that's the end of the questions I've seen so far. I'll give 20 seconds if anyone has any last minute questions. Is there anything else you wanna Charlotte or plug, Doug and Chrissy? Personally, I'm just looking forward to some of this being open source, maybe a blog version of this, because I know lots of people will want to read this. The rootable page mixing is a really good chart, but I think few people are aware of its existence even. Maybe a docs problem there for us to mention A B testing directly in the docs. Well thanks both. This has been pretty excellent and I'm looking forward to hearing more about it and
Speaker 1: catching up with the recording. I hope you have a good rest of the day, everyone. And uh yeah, next talk I think starts in nine minutes, so we have a bit of time for a stretch break.
Speaker 3: Thank you, everybody.
Speaker 2: Thank you.
Use Wagtail’s `RoutablePageMixin` on a campaign page model to match different URL paths to the same page object. Their implementation accepted the path suffix as a campaign slug, without hard-coding each campaign.
Discussed at 3:35Pass the campaign slug into the page context, then include it as a hidden form field. The form sends it to the backend, which can forward it to a third party or use it for tracking.
Discussed at 7:29Install the Wagtail A/B testing add-on, configure its URL and template integration, and register the relevant template tag as a Jinja global when using Jinja. The talk also shows defining a custom conversion goal for form submissions.
Discussed at 8:41It uses browser local storage to remember whether a user saw the control or variant, so they continue seeing the same one. A custom event and JavaScript detect form submissions and report conversions.
Discussed at 10:14They kept paths because the previous CMS already used that URL structure, avoiding breakage during migration. The paths are also easier to read and say aloud than a URL with UTM parameters.
Discussed at 16:52The A/B test stores variant information in the browser, and the team disabled CloudFront caching on certain paths so those requests go directly to Django. They also noted that the add-on README explains how to handle caching.
Discussed at 19:53Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published November 19, 2025
Published November 19, 2025
Published November 19, 2025
Published November 19, 2025
Published November 19, 2025
Published November 19, 2025