Solution Recipe 8: How to create a custom multilingual unsubscribe and preference page in Klaviyo
Solution Recipes are tutorials to achieve specific objectives in Klaviyo. They can also help you master Klaviyo, learn new third-party technologies, and come up with creative ideas. They are written mainly for developers & technically-advanced users.
Note: We do our best to make sure any code and API references are accurate and current when this is published, but you might need to update code and it’s always a best practice to leverage our latest API versions. If you have questions, feel free to hop over to our Developer Community.
How to use Klaviyo’s hosted pages feature to create customizable, multilingual unsubscribe and preference pages.
The standard Klaviyo subscribe and preference pages provide easy-to-use preset functionality, but for more advanced use cases, styling requirements or multilingual needs, they might not cover 100% of business/marketing cases.
High
Introduction
Klaviyo provides an easy-to-use editor for creating unsubscribe and preference pages to use in your email campaigns. For more advanced use cases it may not always be possible to achieve the level of customisation to fit your needs with these tools.
Klaviyo has a feature called “Hosted Pages” — these are essentially webpages hosted in your Klaviyo account where you can add your own custom HTML, CSS, Javascript and utilise our Django templating syntax to build your own unsubscribe and preference pages.
Challenge
Customers can have business/marketing use cases where our standard preference/unsubscribe pages do not have the required logic or customisation tools necessary to suit their needs. In these scenarios we can utilise the hosted pages feature in Klaviyo.
An example of this could be to dynamically change the language on an unsubscribe/preference page to match the customers preferred language. While Klaviyo supports multiple languages, the language is set on the account level. As such our standard unsubscribe/preference pages can only be displayed in one language per Klaviyo account.
This solution recipe will walk through how to setup a simple unsubscribe and preference page using Klaviyo’s hosted pages feature. Then it will show you how to add in logic to dynamically change the content depending on a users profile property (in this case country).
Ingredients
- Intermediate HTML and CSS formatting experience
- Intermediate level knowledge of the Django templating language
- A pinch of Javascript
Instructions
Step 1: Enable Hosted Pages in your Klaviyo account
You may not have the Hosted Pages feature enabled in your Klaviyo account. In order to enable it, login to your Klaviyo account and navigate to Account > Settings > Domains and Hosting.
Then click on the “Enable Custom Pages” button as per the screenshot below
Note: This setting is only available for an account on a paid plan that has passed account verification.
Once activated, you will see the “Hosted Pages” option appear in your Klaviyo account next to “Preference Pages”.
Step 2A: Create a basic unsubscribe page template
The first step is create an unsubscribe page template by creating a new page and giving it a name of your choice.
Klaviyo will then generate a blank HTML boilerplate template for this page like below:
When this hosted page is used as an unsubscribe link in an email, Klaviyo will automatically add some query parameters that will enable the hosted page to identify the user profile who clicked the link.
This will allow us to fetch and display information for the users profile automatically on the page.
In this example, we will add some HTML and Django syntax that will display the current users name and email address in a form.
Note: I’ve imported the Bootstrap CSS framework for demonstration purposes, but you are free to use your own CSS.
Step 2B: Add the logic to unsubscribe a profile
Now that we have the basic unsubscribe page populating the users details, we need to add some additional hidden fields so that Klaviyo knows what to do when the user submits this form.
In this example, we want Klaviyo to unsubscribe the user when the form is submitted.
In order to do this, we need to add some hidden fields to the form. For unsubscribing they are:
<!-- this hidden field tells Klaviyo what to do when the form is submitted -->
<input type="hidden" name="$unsubscribe" value="true" />
<!-- this hidden field tells Klaviyo where to redirect the user after they unsubscribe (optional) -->
<input type="hidden" name="$unsubscribed_url" value="http://example.com/unsubscribed" />
And that’s it! You now have a simple HTML page that will fetch a profile’s details and then unsubscribe them when they submit the form.
The full HTML for this example is below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Unsubscribe page</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3 mt-3">
<form action="" method="POST" class="card" >
<!-- this hidden field tells Klaviyo what to do when the form is submitted -->
<input type="hidden" name="$unsubscribe" value="true" />
<!-- this hidden field tells Klaviyo where to redirect the user after they unsubscribe -->
<!--<input type="hidden" name="$unsubscribed_url" value="https://example.com/unsubscribed" />-->
<div class="card-body">
<div class="mb-3">
<h3>{{person.first_name|default:'Subscriber'}}, we will be sad to see you go</h3>
</div>
<div class="mb-3">
<label for="emailInput" class="form-label">Your email address</label>
<input type="email" class="form-control" id="emailInput" name="$email" value="{{ person.email|default:'' }}" >
</div>
<button type="submit" class="btn btn-primary">Unsubscribe</button>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
Step 2C: Activate the unsubscribe page for your list
In order for Klaviyo to use your custom hosted page as the unsubscribe page in your emails, you will need to update the “Subscribe & Preference Pages” settings for your list or account.
In this example, I am updating the unsubscribe page to use the custom page I have just created for my list.
The last step is to send yourself an email campaign and make sure you use the standard Klaviyo unsubscribe tag {% unsubscribe %} . When you click on it, you should be presented with your custom unsubscribe page.
Note: You will need to send an actual campaign in order to generate a complete unsubscribe link, test sends will not work. Our hosted pages are powered by secure and dynamic URLs which are auto generated at the time of send by Klaviyo. As a result these pages will not have access to personal information as embeds or iframes.
Step 3A: Create a basic preference page template
Next up we will create our custom preference page, to do this create another hosted page in your Klaviyo account like we did for the unsubscribe page.
Similarly to the unsubscribe page, when you use the {% manage_preferences %} syntax in your email, Klaviyo will append a unique query string so that the profile can be identified.
This will allow you to retrieve information specific to that profile (such as their preferences) and display those on the preference page.
We can make use of the Django templating language again to generate the preference page. In this example we are presenting the user with the option to view and edit their email, first name, last name, their favourite breed of dog and how often they would like to receive the newsletter.
I have also added some simple Javascript and jQuery to improve the user experience to allow only one checkbox to be selected at a time.
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Preference page</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3 mt-3">
<form action="" method="POST" class="card" >
<div class="card-body">
<div class="mb-3">
<h3>Update your preferences</h3>
</div>
<div class="mb-3">
<div class="row">
<div class="row">
<div class="col">
<label for="firstName" class="form-label">First Name</label>
<input type="text" id="firstName" class="form-control" name="$first_name" value="{{ person.first_name|default:'' }}">
</div>
<div class="col">
<label for="lastName" class="form-label">Last Name</label>
<input type="text" id="lastName" class="form-control" name="$last_name" value="{{ person.last_name|default:'' }}">
</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Your email address</label>
<input type="email" class="form-control" id="email" name="$email" value="{{ person.email|default:'' }}" >
</div>
<div class="mb-3">
<label for="favouriteDog">What's your favourite breed of dog?</label>
<select class="form-select" id="favouriteDog" name="Favourite Dog">
<option value="" selected>I love them all!</option>
<option value="Springer Spaniel" {% if person|lookup:'Favourite Dog' == "Springer Spaniel" %}selected{%endif%}>Springer Spaniel</option>
<option value="Poodle" {% if person|lookup:'Favourite Dog' == "Poodle" %}selected{%endif%}>Poodle</option>
<option value="Pug"{% if person|lookup:'Favourite Dog' == "Pug" %}selected{%endif%}>Pug</option>
<option value="Golden Retriever"{% if person|lookup:'Favourite Dog' == "Golden Retriever" %}selected{%endif%}>Golden Retriever</option>
</select>
</div>
<div class="mb-3">
<div class="form-group">
<h5>How ofen would you like to receive our newsletter?</h5>
<div class="checkbox">
<label for="newsletterFrequencyWeekly">
<input type="checkbox" name="Newsletter Frequency" value="Weekly" id="newsletterFrequencyWeekly" {% if person|lookup:'Newsletter Frequency' == "Weekly"%}checked{%endif%}>
Weekly
</label>
</div>
<div class="checkbox">
<label for="newsletterFrequencyBiWeekly">
<input type="checkbox" name="Newsletter Frequency" value="BiWeekly" id="newsletterFrequencyBiWeekly" {% if person|lookup:'Newsletter Frequency' == "BiWeekly"%}checked{%endif%}>
Every 2 Weeks
</label>
</div>
<div class="checkbox">
<label for="newsletterFrequencyMonthly">
<input type="checkbox" name="Newsletter Frequency" value="Monthly" id="newsletterFrequencyMonthly" {% if person|lookup:'Newsletter Frequency' == "Monthly"%}checked{%endif%}>
Monthly
</label>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Update Preferences</button>
<div class="mt-5 text-center">
Or <a href="#" id="unsubscribeLink">Unsubscribe from all emails</a>
</div>
</div>
</form>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
<script>
$(function(){
// the selector will match all input controls of type :checkbox
// and attach a click event handler
$("input:checkbox").on('click', function() {
// in the handler, 'this' refers to the box clicked on
var $box = $(this);
if ($box.is(":checked")) {
// the name of the box is retrieved using the .attr() method
// as it is assumed and expected to be immutable
var group = "input:checkbox[name='" + $box.attr("name") + "']";
// the checked state of the group/box on the other hand will change
// and the current value is retrieved using .prop() method
$(group).prop("checked", false);
$box.prop("checked", true);
} else {
$box.prop("checked", false);
}
});
});
</script>
</body>
</html>
Step 3B: Add the logic to update profile properties
Now that we have our base preference page template, we need to add some extra information so Klaviyo knows which profile properties to update.
The main thing we need to do is include a hidden input field which contains a list of all the profile properties that you wish to update.
For this preference page, it looks like:
<input type="hidden" name="$fields" value="$first_name,$last_name,$email,Favourite Dog,Newsletter Frequency" />
You need to make sure the name attribute of the input fields on your form match up with the names of the profile properties you want to update.
By default when a user submits this preference page they will be redirected to your list’s preferences confirmation page. You can adjust this and redirect a user to another custom hosted page or URL of your choice by including a hidden field like below:
<input type="hidden" name="$updated_profile_url" value="https://example.com/updated_preferences" />
3C) Activate the preference page for your list
Similarly for the unsubscribe page, we now need to adjust the list settings to use this hosted preference page instead of the standard Klaviyo preference page.
Navigate back to the “Subscribe & Preference Pages” setting on your list and now select the hosted page.
Once you have done that, any new campaigns sent to this list which include the {% manage_preferences %} tag in their template will use this new preference page.
Bonus: How to give users the option to unsubscribe from a preference page
You may have noticed in the preference page we also had a link to allow a profile to “Unsubscribe from all emails”.
In order to tell Klaviyo that you want to unsubscribe a profile, you will need to include a hidden field like below:
<input type="hidden" name="$unsubscribe" value="true" />
If this value of this input is set to “true” then Klaviyo will unsubscribe the profile from that list. We will add some Javascript to automatically populate this field when a user clicks on the unsubscribe link and submit the form.
We will add some Javascript to automatically populate this field when a user clicks on the unsubscribe link and submit the form.
$('#unsubscribeLink').on('click',function(e){ e.preventDefault();//stops the page refreshing/jumping $('#unsubscribeFromList').val('true'); //submit the form $('form').submit(); });
Just like for when someone updates their preferences, we can also choose where to redirect a user after they unsubscribe by adding the below field:
<input type="hidden" name="$unsubscribed_url" value="https://example.com/unsubscribed" />
The complete code for the page is below:
The complete code for the page is below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Preference page</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3 mt-3">
<form action="" method="POST" class="card" >
<input type="hidden" name="$fields" value="$first_name,$last_name,$email,Favourite Dog,Newsletter Frequency" />
<!-- uncomment fields below as needed, otherwise the default Klaviyo pages will be used-->
<!--<input type="hidden" name="$unsubscribed_url" value="https://example.com/unsubscribed" /> -->
<!-- <input type="hidden" name="$updated_profile_url" value="https://example.com/preferences_updated" /> -->
<div class="card-body">
<div class="mb-3">
<h3>Update your preferences</h3>
</div>
<div class="mb-3">
<div class="row">
<div class="row">
<div class="col">
<label for="firstName" class="form-label">First Name</label>
<input type="text" id="firstName" class="form-control" name="$first_name" value="{{ person.first_name|default:'' }}">
</div>
<div class="col">
<label for="lastName" class="form-label">Last Name</label>
<input type="text" id="lastName" class="form-control" name="$last_name" value="{{ person.last_name|default:'' }}">
</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Your email address</label>
<input type="email" class="form-control" id="email" name="$email" value="{{ person.email|default:'' }}" >
</div>
<div class="mb-3">
<label for="favouriteDog">What's your favourite breed of dog?</label>
<select class="form-select" id="favouriteDog" name="Favourite Dog">
<option value="" selected>I love them all!</option>
<option value="Springer Spaniel" {% if person|lookup:'Favourite Dog' == "Springer Spaniel" %}selected{%endif%}>Springer Spaniel</option>
<option value="Poodle" {% if person|lookup:'Favourite Dog' == "Poodle" %}selected{%endif%}>Poodle</option>
<option value="Pug"{% if person|lookup:'Favourite Dog' == "Pug" %}selected{%endif%}>Pug</option>
<option value="Golden Retriever"{% if person|lookup:'Favourite Dog' == "Golden Retriever" %}selected{%endif%}>Golden Retriever</option>
</select>
</div>
<div class="mb-3">
<div class="form-group">
<h5>How ofen would you like to receive our newsletter?</h5>
<div class="checkbox">
<label for="newsletterFrequencyWeekly">
<input type="checkbox" name="Newsletter Frequency" value="Weekly" id="newsletterFrequencyWeekly" {% if person|lookup:'Newsletter Frequency' == "Weekly"%}checked{%endif%}>
Weekly
</label>
</div>
<div class="checkbox">
<label for="newsletterFrequencyBiWeekly">
<input type="checkbox" name="Newsletter Frequency" value="BiWeekly" id="newsletterFrequencyBiWeekly" {% if person|lookup:'Newsletter Frequency' == "BiWeekly"%}checked{%endif%}>
Every 2 Weeks
</label>
</div>
<div class="checkbox">
<label for="newsletterFrequencyMonthly">
<input type="checkbox" name="Newsletter Frequency" value="Monthly" id="newsletterFrequencyMonthly" {% if person|lookup:'Newsletter Frequency' == "Monthly"%}checked{%endif%}>
Monthly
</label>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Update Preferences</button>
<!-- this gets set to true if a user clicks the unsubscribe text -->
<input id="unsubscribeFromList" type="hidden" name="$unsubscribe" value="" />
<div class="mt-5 text-center">
Or <a href="#" id="unsubscribeLink">Unsubscribe from all emails</a>
</div>
</div>
</form>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
<script>
$(function(){
$('#unsubscribeLink').on('click',function(e){
e.preventDefault();//stops the page refreshing/jumping
$('#unsubscribeFromList').val('true');
//submit the form
$('form').submit();
});
// the selector will match all input controls of type :checkbox
// and attach a click event handler
$("input:checkbox").on('click', function() {
// in the handler, 'this' refers to the box clicked on
var $box = $(this);
if ($box.is(":checked")) {
// the name of the box is retrieved using the .attr() method
// as it is assumed and expected to be immutable
var group = "input:checkbox[name='" + $box.attr("name") + "']";
// the checked state of the group/box on the other hand will change
// and the current value is retrieved using .prop() method
$(group).prop("checked", false);
$box.prop("checked", true);
} else {
$box.prop("checked", false);
}
});
});
</script>
</body>
</html>
Step 4: Making the pages multilingual
At this stage you should have a fully functional custom unsubscribe and preference page. However you may have customers who speak different languages and you want to offer them an unsubscribe/preference page in their preferred language.
If you had separate lists you could re-create these hosted pages for each language and assign them to the relevant list. However this is a time consuming and tedious process. Thankfully there is a better way!
We can make use of the Django templating language to dynamically change the language of our pages.
Step 4A: Fetch the profile property to determine the profiles language
You might have a profile property such as “Language” that you wish to use, but for this example I will be using Klaviyo’s built in “Location.Country” profile property to determine which language to present to the user.
First we fetch the profile property and store it in a variable like below:
{# We assign the profiles country to a variable so we can reference it in the if statement #} {% with personCountry=person|lookup:'Country' %} {% endwith %}
Step 4B: Build the conditional logic
Once we have this, we can then build a series of IF/ELSE statements and create variables to store the translated version of the text we want to use throughout our page.
{# We assign the profiles country to a variable so we can reference it in the if statement #}
{% with personCountry=person|lookup:'Country' %}
{% if personCountry == 'United Kingdom' %}
{% render_variable_assign "en" as language_code%}
{% render_variable_assign "We will be sad to see you go" as unsubscribe_header%}
{% render_variable_assign "Your email email address" as email_label%}
{% render_variable_assign "Unsubscribe" as unsubscribe_text%}
{% elif personCountry == 'Spain' %}
{% render_variable_assign "es" as language_code%}
{% render_variable_assign "Estaremos tristes de verte partir" as unsubscribe_header%}
{% render_variable_assign "Tu correo electrónico" as email_label%}
{% render_variable_assign "Darse de baja" as unsubscribe_text%}
{% elif personCountry == 'France' %}
{% render_variable_assign "fr" as language_code%}
{% render_variable_assign "Nous serons tristes de te voir partir" as unsubscribe_header%}
{% render_variable_assign "Votre adresse e-mail" as email_label%}
{% render_variable_assign "Se désabonner" as unsubscribe_text%}
{%else%}
{# this is our default fallback in the event we have no country match #}
{% render_variable_assign "en" as language_code%}
{% render_variable_assign "We will be sad to see you go" as unsubscribe_header%}
{% render_variable_assign "Your email email address" as email_label%}
{% render_variable_assign "Unsubscribe" as unsubscribe_text%}
{%endif%}
{% endwith %}
Step 4C: Update the HTML to use the language variables
Now we have all the translated versions of the text stores as variables, we simply need to replace the hardcoded text with these variables.
To do this, we use the {% render_variable variable_name %} template tag. For example to render the variable “unsubscribe_text” we would use the following template tag:
{% render_variable unsubscribe_text %}
One thing to be careful with is to ensure the closing {% endwith %} tag is right at the end of your page, otherwise you won’t be able to access the variable data.
Below you can see the completed HTML for the unsubscribe page, with the language variables used to populate the text.
{# We assign the profiles country to a variable so we can reference it in the if statement #}
{% with personCountry=person|lookup:'Country' %}
{% if personCountry == 'United Kingdom' %}
{% render_variable_assign "en" as language_code%}
{% render_variable_assign "We will be sad to see you go" as unsubscribe_header%}
{% render_variable_assign "Your email address" as email_label%}
{% render_variable_assign "Unsubscribe" as unsubscribe_text%}
{% elif personCountry == 'Spain' %}
{% render_variable_assign "es" as language_code%}
{% render_variable_assign "Estaremos tristes de verte partir" as unsubscribe_header%}
{% render_variable_assign "Tu correo electrónico" as email_label%}
{% render_variable_assign "Darse de baja" as unsubscribe_text%}
{% elif personCountry == 'France' %}
{% render_variable_assign "fr" as language_code%}
{% render_variable_assign "Nous serons tristes de te voir partir" as unsubscribe_header%}
{% render_variable_assign "Votre adresse e-mail" as email_label%}
{% render_variable_assign "Se désabonner" as unsubscribe_text%}
{%else%}
{# this is our default fallback in the event we have no country match #}
{% render_variable_assign "en" as language_code%}
{% render_variable_assign "We will be sad to see you go" as unsubscribe_header%}
{% render_variable_assign "Your email address" as email_label%}
{% render_variable_assign "Unsubscribe" as unsubscribe_text%}
{%endif%}
<!DOCTYPE html>
<html lang="{% render_variable language_code %}">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Unsubscribe page</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3 mt-3">
<form action="" method="POST" class="card" >
<!-- this hidden field tells Klaviyo what to do when the form is submitted -->
<input type="hidden" name="$unsubscribe" value="true" />
<!-- this hidden field tells Klaviyo where to redirect the user after they unsubscribe -->
<!--<input type="hidden" name="$unsubscribed_url" value="/p/preferences_updated" />-->
<div class="card-body">
<div class="mb-3">
<h3>{{person.first_name|default:'Subscriber'}}, {% render_variable unsubscribe_header %}</h3>
</div>
<div class="mb-3">
<label for="emailInput" class="form-label">{% render_variable email_label %}</label>
<input type="email" class="form-control" id="emailInput" name="$email" value="{{ person.email|default:'' }}" >
</div>
<button type="submit" class="btn btn-primary">{% render_variable unsubscribe_text %}</button>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
{% endwith %}
You’ll now see the unsubscribe page will change language depending on the rules you have set automatically.
This same approach can also be used to dynamically change the language of your preference page.
Global unsubscribe and preference pages
This guide has covered how you can set each of these hosted pages to a specific list in Klaviyo. You will likely also wish to use these custom pages for your “Global” unsubscribe and preference pages too. These are typically what users see when they unsubscribe from an email sent to a flow or segment.
To update these global pages, navigate to “Preferences Pages” and select “use custom page”
Bonus: Subscribing and unsubscribing from multiple lists
If you are using the list specific unsubscribes setting in Klaviyo, you might want to use lists instead of profile properties to manage your customers preferences.
In this scenario, you simply need to include the list IDs in the hidden fields section in the format like below:
<input type=“hidden” name=“$fields” value=“$list:[LIST_ID1_HERE], $list:[LIST_ID2_HERE], $list:[LIST_ID3_HERE],Favourite Dog,Newsletter Frequency” />
and add a corresponding input field to the page with the same name.
<input type=“checkbox” name=“$list:[LIST_ID1_HERE]” value=“true” {% if ‘[LIST_ID1_HERE]’ in person|lookup:‘$lists’ or request.POST|lookup:‘$list:[LIST_ID1_HERE]’ %} checked{% endif %} />
Here is a working example using the preference page from earlier. This will also work for an unsubscribe page (remember to replace the [LIST_ID_X] placeholders as appropriate, for example $list:A1b2C3).
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Preference page</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3 mt-3">
<form action="" method="POST" class="card" >
<input type="hidden" name="$fields" value="$first_name,$last_name,$email,Favourite Dog,Newsletter Frequency,$list:[LIST_ID_1],$list:[LIST_ID_2]" />
<!--<input type="hidden" name="$unsubscribed_url" value="/p/preferences_updated" />-->
<!--<input type="hidden" name="$updated_profile_url" value="/p/preferences_updated" />-->
<div class="card-body">
<div class="mb-3">
<h3>Update your preferences</h3>
</div>
<div class="mb-3">
<div class="row">
<div class="row">
<div class="col">
<label for="firstName" class="form-label">First Name</label>
<input type="text" id="firstName" class="form-control" name="$first_name" value="{{ person.first_name|default:'' }}">
</div>
<div class="col">
<label for="lastName" class="form-label">Last Name</label>
<input type="text" id="lastName" class="form-control" name="$last_name" value="{{ person.last_name|default:'' }}">
</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Your email address</label>
<input type="email" class="form-control" id="email" name="$email" value="{{ person.email|default:'' }}" >
</div>
<div class="mb-3">
<label for="favouriteDog">What's your favourite breed of dog?</label>
<select class="form-select" id="favouriteDog" name="Favourite Dog">
<option value="" selected>I love them all!</option>
<option value="Springer Spaniel" {% if person|lookup:'Favourite Dog' == "Springer Spaniel" %}selected{%endif%}>Springer Spaniel</option>
<option value="Poodle" {% if person|lookup:'Favourite Dog' == "Poodle" %}selected{%endif%}>Poodle</option>
<option value="Pug"{% if person|lookup:'Favourite Dog' == "Pug" %}selected{%endif%}>Pug</option>
<option value="Golden Retriever"{% if person|lookup:'Favourite Dog' == "Golden Retriever" %}selected{%endif%}>Golden Retriever</option>
</select>
</div>
<div class="mb-3">
<div class="form-group">
<h5>Choose which lists you would like to be subscribed to</h5>
<div class="checkbox">
<label for="breakingNewsList">
<input type="checkbox" name="$list:[LIST_ID_1]" id="breakingNewsList" value=“true” {% if '[LIST_ID_1]' in person|lookup:'$lists' or request.POST|lookup:'$list:[LIST_ID_1]' %} checked{% endif %} >
Breaking News
</label>
</div>
<div class="checkbox">
<label for="opinionPieces">
<input type="checkbox" name="$list:[LIST_ID_2]" id="opinionPieces" value=“true” {% if '[LIST_ID_2]' in person|lookup:'$lists' or request.POST|lookup:'$list:[LIST_ID_2]' %} checked{% endif %} >
Opinion Pieces
</label>
</div>
</div>
</div>
<div class="mb-3">
<div class="form-group">
<h5>How ofen would you like to receive our newsletter?</h5>
<div class="checkbox">
<label for="newsletterFrequencyWeekly">
<input type="checkbox" name="Newsletter Frequency" value="Weekly" id="newsletterFrequencyWeekly" {% if person|lookup:'Newsletter Frequency' == "Weekly"%}checked{%endif%}>
Weekly
</label>
</div>
<div class="checkbox">
<label for="newsletterFrequencyBiWeekly">
<input type="checkbox" name="Newsletter Frequency" value="BiWeekly" id="newsletterFrequencyBiWeekly" {% if person|lookup:'Newsletter Frequency' == "BiWeekly"%}checked{%endif%}>
Every 2 Weeks
</label>
</div>
<div class="checkbox">
<label for="newsletterFrequencyMonthly">
<input type="checkbox" name="Newsletter Frequency" value="Monthly" id="newsletterFrequencyMonthly" {% if person|lookup:'Newsletter Frequency' == "Monthly"%}checked{%endif%}>
Monthly
</label>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Update Preferences</button>
<!-- this gets set to true if a user clicks the unsubscribe text -->
<input id="unsubscribeFromList" type="hidden" name="$unsubscribe" value="" />
<div class="mt-5 text-center">
Or <a href="#" id="unsubscribeLink">Unsubscribe from all emails</a>
</div>
</div>
</form>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
<script>
$(function(){
$('#unsubscribeLink').on('click',function(e){
e.preventDefault();//stops the page refreshing/jumping
$('#unsubscribeFromList').val('true');
//we also need to check uncheck all the lists to unsubscribe from since we are not using the global unsubscribe setting
$('#breakingNewsList').prop('checked',false);
$('#opinionPiecesList').prop('checked',false);
//submit the form
$('form').submit();
});
// the selector will match all input controls of type :checkbox
// and attach a click event handler
$("input:checkbox").on('click', function() {
// in the handler, 'this' refers to the box clicked on
var $box = $(this);
if ($box.is(":checked")) {
// the name of the box is retrieved using the .attr() method
// as it is assumed and expected to be immutable
var group = "input:checkbox[name='" + $box.attr("name") + "']";
// the checked state of the group/box on the other hand will change
// and the current value is retrieved using .prop() method
$(group).prop("checked", false);
$box.prop("checked", true);
} else {
$box.prop("checked", false);
}
});
});
</script>
</body>
</html>
NB: There is some additional Javascript logic added to uncheck all the list checkbox fields when the “unsubscribe from all” option is chosen. It is not possible to unsubscribe from the list the email was sent to unless the $unsubscribe hidden field is included.
Impact
Klaviyo supports unsubscribe and preference pages out of the box, but sometimes you might want to add additional branding, features and customisation. The Klaviyo Hosted Pages feature lets you leverage all the benefits of HTML/CSS and Javascript and integrate this with your preference/unsubscribe pages to customise them to your requirements.
Learn More!
If you’re interested in learning more about Klaviyo’s developer experience and APIs, please visit developers.klaviyo.com!