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:
- 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.
- 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.
- 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
| Data | Where it comes from | Why we have it |
|---|---|---|
| Name, email, avatar | Your Google account, at sign-in | Your identity in the dashboard and in invitations |
| Google account identifier | Google OAuth | Signing you in without a password |
| Session records | Created when you sign in | Keeping you signed in; spotting stale sessions |
| Workspace, project and team memberships and roles | Actions you or your workspace's admins take | Deciding who can see and do what, in which workspace |
| Invitation records | Email addresses you type when inviting someone | Emailing the invitation and matching it to the account that accepts |
| Workspace billing email | Typed by the workspace owner in settings | The workspace's billing contact |
| Audit-log entries you author | Actions you take in the dashboard | A forensic trail of security-relevant actions |
| API-key records | Keys you create | Widget authentication — we store a SHA-256 hash and a short prefix, never the raw key |
| Slack webhook URLs | Notification settings you configure | Delivering the digests you asked for |
| Agent-access grants | MCP connections you authorise | Letting 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):
| Data | Why |
|---|---|
| The comment text | It is the feedback itself |
| The display name typed in the composer | Showing who said what |
| An email address, only if the commenter typed one | Optional 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 attach | Visual context — password, card and one-time-code fields are blacked out before the image is created |
| The page URL and route | Anchoring the comment to the page it was left on |
| Window size and browser user-agent | Letting the site's team reproduce what the commenter saw |
| For signed-in commenters: user id, name, email | The 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.
Purposes and legal bases (GDPR)
| Purpose | Data | Legal basis |
|---|---|---|
| Providing the dashboard and your account | Account, sessions, memberships | Performance of a contract (Art. 6(1)(b)) |
| Operating the widget for site owners | Widget content | Processed on the site owner's instructions (Art. 28) |
| Securing the service (audit log, rate limits, revocations) | Audit entries, key hashes, token records | Legitimate interests (Art. 6(1)(f)) |
| Understanding product usage | Masked analytics events | Legitimate interests (Art. 6(1)(f)) |
| Fixing crashes | Error reports | Legitimate interests (Art. 6(1)(f)) |
| Sending Slack digests | Webhook URL, notification content | Performance 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
| Data | Retention |
|---|---|
| Project content (threads, comments, screenshots) | For the life of the project. Archiving hides a project; it does not delete it. |
| Audit log | 12 months from the entry, then swept nightly — or the life of the project, whichever ends first |
| API-key records | The hash is kept after revocation so audit entries keep their references; the key itself stopped working at revocation |
| Widget sign-in tokens | Access tokens live 15 minutes; refresh tokens 7 days — expired ones are swept daily |
| Token-revocation records | Until the token they revoke would have expired anyway; then swept |
| Popup sign-in exchange codes | 60 seconds; leftovers are swept daily |
| Unattached screenshot uploads | Swept hourly |
| Analytics and error events | Per PostHog's and Sentry's retention settings for our accounts |
| Backup copies of project content | Up 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