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

ProductStatusNote
Analytics & custom eventsSupported
Feedback widgetSupported
Review badgeSupported
Web pushWith a stepNeeds a URL route at /userlily-sw.js
Roadmap embedSupported
Feedback, survey & roadmap linksSupported

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>

Notes

  • Using WhiteNoise? Putting the file in the folder named by WHITENOISE_ROOT serves it at the root instead of the URL route.
  • A site-wide login middleware must let /userlily-sw.js through - 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.