legal · 01

Privacy Policy

Effective date: August 10, 2026
Last updated: August 15, 2026

This policy explains what personal data the hosted Markup service collects, why, and what you can do about it. It is written to be read, not skimmed past — every claim in it is grounded in how the product actually behaves, and the source code is open if you want to verify one.

Who we are

Markup is operated by Pixelmatters, Unipessoal, Lda., a Portuguese company with registered offices at Avenida dos Aliados, nº 168, 4º Andar, 4000-064 Porto, Portugal (VAT PT 510 928 579) — "Pixelmatters", "we", "us".

For anything in this policy: hello@pixelmatters.com.

Who this policy covers

Markup touches three groups of people, and their situations are different:

  1. Dashboard users — people with a Markup account who create projects and install the widget on their own websites. For your account data, Pixelmatters is the data controller.
  2. End-user commenters — people who leave feedback through the Markup widget on a site that installed it. That site's owner decides why and how your feedback is collected, so the site owner is the controller and Pixelmatters processes the data on their behalf. There is a dedicated, plain-language notice for widget users, and our obligations to site owners are set out in the Data Processing Agreement.
  3. Visitors to this website — the marketing and documentation pages you may be reading right now. These pages load no analytics, no tracking scripts, and set no cookies. Our hosting provider (Cloudflare) processes IP addresses in the ordinary course of serving any website.

This policy covers the hosted service at markup.pixelmatters.dev and its backend. Markup is open-source (MIT); if someone else runs their own deployment of the code, that deployment is theirs, and this policy does not apply to it.

Data we collect from dashboard users

DataWhere it comes fromWhy we have it
Name, email, avatarYour Google account, at sign-inYour identity in the dashboard and in invitations
Google account identifierGoogle OAuthSigning you in without a password
Session recordsCreated when you sign inKeeping you signed in; spotting stale sessions
Workspace, project and team memberships and rolesActions you or your workspace's admins takeDeciding who can see and do what, in which workspace
Invitation recordsEmail addresses you type when inviting someoneEmailing the invitation and matching it to the account that accepts
Workspace billing emailTyped by the workspace owner in settingsThe workspace's billing contact
Audit-log entries you authorActions you take in the dashboardA forensic trail of security-relevant actions
API-key recordsKeys you createWidget authentication — we store a SHA-256 hash and a short prefix, never the raw key
Slack webhook URLsNotification settings you configureDelivering the digests you asked for
Agent-access grantsMCP connections you authoriseLetting a coding agent you approved read your feedback

We do not collect payment information — the hosted service does not currently charge for anything.

Data we process for site owners

When someone comments through the widget, the following reaches the project it belongs to. For this data we act on the site owner's instructions (see the DPA):

DataWhy
The comment textIt is the feedback itself
The display name typed in the composerShowing who said what
An email address, only if the commenter typed oneOptional contact for the project's team
A random per-site commenter id (markup_anon_…)Recognising the same anonymous commenter on the same site
Screenshots the commenter chose to attachVisual context — password, card and one-time-code fields are blacked out before the image is created
The page URL and routeAnchoring the comment to the page it was left on
Window size and browser user-agentLetting the site's team reproduce what the commenter saw
For signed-in commenters: user id, name, emailThe verified-identity flow the commenter opted into

The widget's own network traffic goes to the Markup backend and nowhere else: it loads no third-party scripts and no ad tech on the host page. See the widget notice for what is stored in the commenter's browser.

Product analytics

We measure how the product is used so we can tell what is worth improving. We deliberately keep this narrow:

  • Dashboard analytics run only inside the signed-in app — never on the marketing, documentation, or legal pages. Events go to PostHog's EU cloud, relayed through our own domain. Autocapture, session replay and surveys are switched off. URLs are masked to route patterns before they leave your browser (/workspaces/$workspaceSlug/projects/$slug/threads, never your actual workspace or project name), page titles are stripped, and the profile is keyed to your user id and email only.
  • Widget telemetry counts interactions (a comment posted, a screenshot captured) so we know which widget features are used. Every event is keyed to the project, never to a person: no commenter identifier, no comment text, no URLs, and no IP address is sent — events are relayed server-side with geolocation disabled. Site owners can turn it off entirely with analytics: false.

Error reporting

If the dashboard or the widget crashes, an error report goes to Sentry so we can fix it. Dashboard reports include your user id and email so we can tell you if a bug ate your work. Widget reports include the project id and the page origin — never the commenter's identity or what they were writing. The widget posts its crash reports to our own backend, which relays them; the host page never talks to Sentry.

What we don't do

  • We don't sell personal data, and we don't share it for advertising.
  • We don't run ads, tracking pixels, or fingerprinting anywhere.
  • We don't follow commenters across sites: widget identity is scoped per site, so the same browser on two different sites is two different commenters to us.
  • We don't store raw API keys, and we can't recover one for you.
PurposeDataLegal basis
Providing the dashboard and your accountAccount, sessions, membershipsPerformance of a contract (Art. 6(1)(b))
Operating the widget for site ownersWidget contentProcessed on the site owner's instructions (Art. 28)
Securing the service (audit log, rate limits, revocations)Audit entries, key hashes, token recordsLegitimate interests (Art. 6(1)(f))
Understanding product usageMasked analytics eventsLegitimate interests (Art. 6(1)(f))
Fixing crashesError reportsLegitimate interests (Art. 6(1)(f))
Sending Slack digestsWebhook URL, notification contentPerformance of a contract (Art. 6(1)(b))

We do not currently send marketing email and do not rely on consent for any processing described here. If that changes, we will ask first.

Sub-processors and international transfers

We use a small set of infrastructure providers to run the service — Convex (database and backend, EU region), Cloudflare (dashboard hosting), Google (sign-in), PostHog (analytics, EU region), Sentry (error monitoring), Resend (invitation emails) and, where a project configures it, Slack (digest delivery). The current list, with locations and what each one processes, lives at /legal/subprocessors and is updated in the open-source repository.

The service's primary data store is hosted in the European Union: feedback content, screenshots and account data live in Convex's EU (Ireland) region, and analytics events go to PostHog's EU cloud. What can involve processing outside the EEA is narrower — error reports going to Sentry, Resend delivering invitation emails, Cloudflare's global edge serving the dashboard's pages, Google handling sign-in, and Slack delivering the digests a project opts into. Where personal data does leave the EEA, transfers rest on the European Commission's adequacy decision for the EU–US Data Privacy Framework and/or Standard Contractual Clauses, as set out in each provider's data-processing terms.

How long we keep things

DataRetention
Project content (threads, comments, screenshots)For the life of the project. Archiving hides a project; it does not delete it.
Audit log12 months from the entry, then swept nightly — or the life of the project, whichever ends first
API-key recordsThe hash is kept after revocation so audit entries keep their references; the key itself stopped working at revocation
Widget sign-in tokensAccess tokens live 15 minutes; refresh tokens 7 days — expired ones are swept daily
Token-revocation recordsUntil the token they revoke would have expired anyway; then swept
Popup sign-in exchange codes60 seconds; leftovers are swept daily
Unattached screenshot uploadsSwept hourly
Analytics and error eventsPer PostHog's and Sentry's retention settings for our accounts
Backup copies of project contentUp to 90 days, plus one most-recent copy per project while the project exists. See "Backups and erasure" below

Deleting your account or a project triggers the erasure described below.

Backups and erasure

We take a weekly backup of each project — a point-in-time copy of the same project content described above, including comment text and the name and email attached to each comment. Copies older than 90 days are deleted automatically; the most recent copy of a project is kept for as long as the project exists, so that a project deleted or damaged by accident can be restored. Where an operator has configured off-site backup storage, a copy is also written to that storage and kept for the period that storage is configured to keep it.

An erasure request removes the data from the live product straight away. Backup copies are not rewritten in place — doing so would defeat the purpose of a backup — so a copy taken before the request can still hold the data until it ages out. On request we will purge a project's backup copies rather than wait for that window, and we do not restore erased data back into the live product from a backup.

Your rights

Under the GDPR you can ask for access, rectification, erasure, restriction, portability, and to object. Much of that is built into the product so you don't have to ask:

  • Rectification — your display name can be edited on the Account page; name, email and avatar otherwise mirror your Google account.
  • Erasure (dashboard users) — the Delete account button on the Account page is self-serve. It removes your workspace, project and team memberships, revokes your active widget sessions, and erases the content you authored as a commenter: your threads and comments are replaced with a neutral tombstone (so other people's replies still make sense) and your reactions are deleted. If you are the only admin of a project, or you own a workspace, the dashboard asks you to hand it over or delete it first.
  • Erasure (commenters) — the widget's Privacy & data panel has a Forget me on this site action that erases every comment and reaction that browser left on that site and clears the name and email held for it.
  • Access and portability — write to us and we will provide a machine-readable export; per-project JSON exports are part of the product's standard tooling.

For anything not covered by a button: hello@pixelmatters.com. We answer within 30 days. If a commenter asks us directly about feedback they left on someone else's site, we will help where we can, but the site owner is the controller of that data — they may be the right first stop.

You can also complain to a supervisory authority. Ours is the Portuguese CNPD — Comissão Nacional de Proteção de Dados (cnpd.pt) — but you may use the authority where you live or work.

Children

The hosted service is not directed at children, and you must be at least 16 to hold a dashboard account. Site owners who embed the widget on services directed at children are responsible for any parental consent their situation requires (GDPR Art. 8, COPPA, or local equivalents); the widget gives them no reason to need it — it works without collecting any commenter identity at all — and we recommend configuring it that way in those contexts.

Security

Tenant isolation is enforced in every backend function, API keys are stored only as hashes, widget sessions use short-lived signed tokens with server-side revocation, per-project domain allowlists control where the widget may run, and security-relevant dashboard actions land in a per-project audit log. The codebase — including all of this — is open source and independently auditable. Vulnerability reports: see SECURITY.md.

No transmission or storage is perfectly secure. If a breach affects your personal data, we will notify you and the competent authority as the GDPR requires.

Changes to this policy

We will post changes here, adjust the date at the top, and — for material changes — tell dashboard users in the product or by email before they take effect. The page's history is public in the repository, so you can always see exactly what changed and when.

Contact

Pixelmatters, Unipessoal, Lda. Avenida dos Aliados, nº 168, 4º Andar 4000-064 Porto, Portugal hello@pixelmatters.com