Skip to main content

django-snapnote

Let your users report a problem from any page: a floating button captures the tab, lets them draw on it and creates a ticket you can review in the admin or in built-in views.

Requires only Django (4.2–5.2) and Pillow. No DRF, no Node, no build step for you. The widget is a self-contained bundle rendered in a Shadow DOM, so your CSS and ours don't interfere.

Install

pip install django-snapnote
# settings.py
INSTALLED_APPS += ["snapnote"]          # needs django.contrib.auth and staticfiles

# urls.py
path("snapnote/", include("snapnote.urls")),   # report endpoint + management views
{# in your base template (the view needs the request context processor) #}
{% load snapnote_tags %}
{% snapnote_button %}

Then python manage.py migrate. Uploaded images use your MEDIA_ROOT/storage.

Settings

Setting Default Purpose
SNAPNOTE_CAN_REPORT "snapnote.permissions.is_authenticated" Dotted path to user -> bool: who sees the button and may report
SNAPNOTE_NOTIFIERS ["snapnote.notifiers.email"] Callables (ticket, absolute_url) run after a ticket is created
SNAPNOTE_NOTIFY_EMAILS [] Recipients of the email notifier (nothing is sent if empty)
SNAPNOTE_BASE_TEMPLATE None Template the management views extend; it only needs {% block content %}
SNAPNOTE_ENABLE_MANAGEMENT_VIEWS True List/detail views (the admin is always registered)
SNAPNOTE_SEARCH_UNACCENT False Accent-insensitive search (PostgreSQL + django.contrib.postgres only)
SNAPNOTE_MAX_UPLOAD_MB 5 Per-image size limit
SNAPNOTE_MAX_DIMENSION 8000 Max width/height in pixels
SNAPNOTE_UPLOAD_TO "snapnote/" Prefix in the storage
SNAPNOTE_THROTTLE "10/hour" Reports per user (second, minute, hour, day); uses Django's cache. None disables
SNAPNOTE_AUTO_INJECT False Also add snapnote.middleware.AutoInjectMiddleware to MIDDLEWARE to insert the button automatically. Incompatible with strict CSP and streaming responses

Customise the look by overriding CSS variables on #snapnote-root (--snapnote-accent, --snapnote-accent-text, --snapnote-z-index, --snapnote-font).

Permissions

Reporting follows SNAPNOTE_CAN_REPORT. Managing uses the model permissions snapnote.view_ticket, change_ticket and delete_ticket (superusers have them all). Anonymous users are redirected to LOGIN_URL; authenticated users without permission get 403.

Notifiers

The signal snapnote.signals.ticket_created(sender, ticket, request_url) is sent after commit. A notifier is any callable; a failing one is logged and never breaks the report:

# myproject/notifiers.py — Slack recipe
import requests
def slack(ticket, url):
    requests.post(settings.SLACK_WEBHOOK, json={"text": f"New ticket: {ticket.title} {url}"}, timeout=5)

SNAPNOTE_NOTIFIERS = ["snapnote.notifiers.email", "myproject.notifiers.slack"]

Jira notifier

Built in, opt-in and dependency-free (stdlib only). It creates a Jira Cloud issue per ticket, with the page URL, a link to the Snapnote ticket and, optionally, the screenshot and annotations attached:

SNAPNOTE_NOTIFIERS = ["snapnote.notifiers.email", "snapnote.notifiers.jira_issue"]
SNAPNOTE_JIRA_URL = "https://yourcompany.atlassian.net"
SNAPNOTE_JIRA_EMAIL = "bot@yourcompany.com"
SNAPNOTE_JIRA_API_TOKEN = os.environ["JIRA_API_TOKEN"]   # https://id.atlassian.com/manage-profile/security/api-tokens
SNAPNOTE_JIRA_PROJECT = "APP"                            # project key
# optional: SNAPNOTE_JIRA_ISSUE_TYPE = "Bug", SNAPNOTE_JIRA_LABELS = ["snapnote"],
#           SNAPNOTE_JIRA_ATTACH_IMAGES = True, SNAPNOTE_JIRA_TIMEOUT = 10

manage.py check warns (snapnote.W002) if a required setting is missing. Use a dedicated Jira account with access only to that project. Notifiers run synchronously after commit with the timeout above; for heavy use, wrap it in your own task queue. Screenshots may contain sensitive data: they are attached to the issue, so keep SNAPNOTE_JIRA_ATTACH_IMAGES = False if the Jira project is broader than who may see them.

Export tickets for an AI coding assistant

python manage.py snapnote_export 42            # one or more IDs
python manage.py snapnote_export --open        # every open ticket
python manage.py snapnote_export 42 --output /tmp/tickets

Writes snapnote-export/ticket-42/ with ticket.md, ticket.json, screenshot.png, annotations.png and screenshot_annotated.png (the screenshot with the user's marks drawn on it), so a tool like Claude Code can open the folder and see the problem without database access. It reads from the database of the environment where it runs: to export production tickets, run it there (or on a restored dump) and copy the folder.

The text of a ticket is written by an end user, so ticket.md labels it as untrusted data and places it in code fences; still review any change an assistant proposes before merging it, and do not export into a repository you will commit (screenshots can contain sensitive data).

Security and deployment notes

  • Screenshots can contain sensitive data. Serve MEDIA_ROOT only to authenticated staff (or use a private storage); do not expose it publicly. Purge old reports with python manage.py snapnote_purge --older-than 90 (--dry-run to preview).
  • Permissions-Policy: the browser needs display-capture=(self). With display-capture=() (also sent by some proxies) capture never works and users get the upload fallback.
  • CSP: allow img-src blob: data: and style-src 'unsafe-inline' (the Shadow DOM stylesheet is inline) and script-src for the static bundle.
  • Uploads are validated (PNG/JPEG/WebP only, no SVG), size/dimension limited and re-encoded with Pillow.
  • page_url must be http(s) and is never rendered as a clickable link otherwise.
  • The report endpoint uses Django's CSRF protection. The widget is compatible with CSRF_COOKIE_HTTPONLY = True and CSRF_USE_SESSIONS = True: the token is embedded in the page (like {% csrf_token %} in a form), not read from the cookie. If you cache whole pages, a cached token can go stale; exclude pages with the button from the cache or use per-user caching.
  • Behind a proxy, set CSRF_TRUSTED_ORIGINS and SECURE_PROXY_SSL_HEADER as Django requires.
  • Capture uses getDisplayMedia (desktop browsers). On mobile the user attaches an image.

Development

pip install -e . pytest pytest-django
pytest                      # SQLite; SNAPNOTE_TEST_DB=postgres for PostgreSQL
cd frontend && npm ci && npm run build   # writes src/snapnote/static/snapnote/snapnote.js
npm test && npm run e2e

The built snapnote.js is committed/packaged so installing the wheel never needs Node.

Metadata

Release files for django-snapnote 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-snapnote 0.2.1
File Size Uploaded Provenance Actions
django_snapnote-0.2.1.tar.gz 112.8 kB With provenance Details for django_snapnote-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-snapnote 0.2.1
File Interpreter ABI Platform Actions
django_snapnote-0.2.1-py3-none-any.whl Python 3 none any Details for django_snapnote-0.2.1-py3-none-any.whl

Total release size: 191.9 kB

Release files / django_snapnote-0.2.1.tar.gz

Download URL django_snapnote-0.2.1.tar.gz
Size 112.8 kB
Tags Source
SHA-256 checksum
How to use checksums
88a962f963143ed7e541c9eb5b7552ab3ee6e30f62dc9431bd7982e1efab3c3d
BLAKE2b-256 checksum
How to use checksums
6ea87848b6109db96511cd612499d6c3b55f844a186d11c74f6cf0c11714cf70
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.

Transparency log

Release files / django_snapnote-0.2.1-py3-none-any.whl

Download URL django_snapnote-0.2.1-py3-none-any.whl
Size 79.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d47108645c41c98a01a04ee87ff46064bed0e3b27b35045d6cb25bf38f9da6ab
BLAKE2b-256 checksum
How to use checksums
26f06b3c19f165a2d65d322abd38e80c907f33f36fce0d1eb405825209b1332c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.

Transparency log

Security for django-snapnote 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_snapnote-0.2.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page