Django
Keys in settings, a context processor, and one template included from base.html.
Installed with script tags in your templates · Written for Django 5
What works
| Product | Status | Note |
|---|---|---|
| Analytics & custom events | Supported | |
| Feedback widget | Supported | |
| Review badge | Supported | |
| Web push | With a step | Needs a URL route at /userlily-sw.js |
| Roadmap embed | Supported | |
| Feedback, survey & roadmap links | Supported |
1. Add your keys
As environment variables, however your deployment sets them. Only the keys for products you use - the host is always needed.
Environment
USERLILY_HOST=https://dev.userlily.com USERLILY_TRACKING_ID=YOUR_TRACKING_ID USERLILY_CAMPAIGN=YOUR_CAMPAIGN_KEY USERLILY_BROADCAST_KEY=YOUR_PROJECT_KEY
2. Read them in settings
settings.py
import os
USERLILY = {
'host': os.environ.get('USERLILY_HOST', ''),
'tracking_id': os.environ.get('USERLILY_TRACKING_ID', ''),
'campaign': os.environ.get('USERLILY_CAMPAIGN', ''),
'broadcast_key': os.environ.get('USERLILY_BROADCAST_KEY', ''),
}3. Expose them to templates
A context processor makes userlily available in every template. Register it under TEMPLATES[0]["OPTIONS"]["context_processors"].
yourapp/context_processors.py
from django.conf import settings
def userlily(request):
return {'userlily': settings.USERLILY}settings.py - add to context_processors
'yourapp.context_processors.userlily',
4. Create the template
Each product loads only when its key is set.
templates/userlily.html
{% if userlily.tracking_id %}
<script src="{{ userlily.host }}/v1/tracker.js"
data-tracking-id="{{ userlily.tracking_id }}" async></script>
{% endif %}
{% if userlily.campaign %}
<script src="{{ userlily.host }}/v1/widget.js"
data-campaign-id="{{ userlily.campaign }}" async></script>
{% endif %}
{% if userlily.broadcast_key %}
<script src="{{ userlily.host }}/v1/broadcast.js"
data-project="{{ userlily.broadcast_key }}" defer></script>
{% endif %}5. Include it in base.html
Just before </body>.
templates/base.html
<!-- templates/base.html -->
<body>
{% block content %}{% endblock %}
{% include "userlily.html" %}
</body>6. Send custom events
From any page. window.userlily exists once the tracker has loaded - well before anyone can submit a form.
In a template
<script>
document
.querySelector('#signup-form')
.addEventListener('submit', function () {
window.userlily.track('signup_completed')
})
</script>7. Show the review badge
The badge renders exactly where the tag sits. Switch on “Enable syndication” on the campaign’s Syndication page first.
In a template
<!-- Where the badge should appear -->
<script src="{{ userlily.host }}/v1/syndication.js"
data-token="YOUR_CAMPAIGN_KEY" async></script>8. Host the push worker
The worker must be served from your own domain at /userlily-sw.js, as JavaScript, with no redirect and over HTTPS. Each of these fails silently. Django serves static files under /static/, not the root - so the worker needs its own URL.
Terminal, next to manage.py
curl -o userlily-sw.js https://dev.userlily.com/v1/userlily-sw.js
urls.py
from pathlib import Path
from django.conf import settings
from django.http import FileResponse
from django.urls import path
SW = Path(settings.BASE_DIR) / 'userlily-sw.js'
def userlily_sw(request):
return FileResponse(
open(SW, 'rb'),
content_type='text/javascript',
)
urlpatterns = [
path('userlily-sw.js', userlily_sw),
# ...your other routes
]9. Ask for push permission from your own button
Optional. Works when the project’s “When to ask” is set to “Only when your own code asks”.
In a template
<button id="notify-me">Get notified</button>
<script>
var button = document.querySelector('#notify-me')
button.addEventListener('click', function () {
// Exists once broadcast.js has started, and only
// when "When to ask" is "Only when your own code asks".
if (window.userlilyBroadcast) window.userlilyBroadcast.prompt()
})
</script>10. Embed the public roadmap
Turn on “Enable public roadmap” in Roadmap → Settings first. An Allowed domain on the roadmap limits which sites can embed it.
Where the roadmap should appear
<iframe
data-userlily-roadmap
src="https://dev.userlily.com/r/YOUR_ROADMAP_KEY?embed=1"
title="Product roadmap"
style="width:100%;height:720px;border:0"
loading="lazy"
></iframe>
<script>
(function () {
window.addEventListener("message", function (event) {
if (event.origin !== "https://dev.userlily.com") return;
var data = event.data;
if (!data || data.type !== "userlily:roadmap:height") return;
var frame = document.querySelector("iframe[data-userlily-roadmap]");
if (frame && data.height > 0) frame.style.height = data.height + "px";
});
}());
</script>11. Share links
Every campaign, survey and public roadmap also has a hosted page - nothing to install. Find each one under Share in the dashboard.
Hosted pages
Feedback form https://dev.userlily.com/f/YOUR_CAMPAIGN_KEY Survey https://dev.userlily.com/s/YOUR_SURVEY_KEY Roadmap https://dev.userlily.com/r/YOUR_ROADMAP_KEY
Notes
- Using WhiteNoise? Putting the file in the folder named by
WHITENOISE_ROOTserves it at the root instead of the URL route. - A site-wide login middleware must let
/userlily-sw.jsthrough - a redirect breaks it.
Every option, limit and troubleshooting step is in the main docs. Logos are trademarks of their owners and are shown only to indicate compatibility.