How Annot8 is organised
Workspaces, projects, environments, and threads — and which settings live where.
Four objects, nested. Almost every "where do I change this?" question resolves by working out which level a setting belongs to.
Workspace ── billing, members, integration credentials
└── Project ── project key, widget appearance, feature flags
└── Environment ── hostname patterns; which lane feedback lands in
└── Thread ── one pinned comment and its repliesWorkspace
A workspace (an organization) is the billing and membership boundary. It owns:
- The subscription and its seats
- Members and their roles — see Team members
- Integration credentials, which are stored once and reused by every project
- Workspace-wide notification preferences
You can belong to several workspaces and switch between them from the sidebar. Seats, projects, and usage limits are counted per workspace, never across them.
Project
A project is one site or app. It holds:
- Project key (
pj_...) — the public identifier in your script tag - Widget appearance — colour, corner, launcher label, pin style
- Feature flags — Analytics and Live chat on/off, and whether the widget is visible in production
- AI model and the visitor audience classification
- Its own inbox, labels, issue types, automations, and public links
One project per site. Two sites that need separate inboxes are two projects.
Environment
An environment is a named set of hostname patterns. On every page load the widget reports the hostname, and Annot8 matches it against the project's environments, most specific pattern first.
| Pattern | Matches |
|---|---|
app.example.com | That host exactly |
*.example.com | Any subdomain of example.com |
*.vercel.app | Preview deployments |
localhost | Local development |
pr-*.example.com | A prefix wildcard |
A single pattern field can hold several patterns separated by spaces or commas:
localhost *.local 127.0.0.1.
Three things follow from the match:
- The widget loads at all. A hostname matching no environment gets nothing. This is what stops your script tag being lifted onto someone else's site.
- Which lane the thread is filed into. You can filter the inbox by environment, so a local experiment never pollutes your production triage.
- Whether production rules apply. The default environment — and any environment literally named "Production" — counts as production, where the annotation widget stays hidden unless you explicitly enable it.
A bare * is rejected. There is no way to make the widget embeddable from
anywhere. See Environments.
Thread
A thread is one piece of feedback: the pinned element, the original comment, and every reply. It carries:
- Anchor — the CSS selector plus a percentage offset inside that element, so the pin survives scrolling, resizing, and most re-deploys
- Page context — URL path, full URL, page title, referrer
- Device context — viewport, device type, user agent, pixel ratio, language, timezone, colour scheme
- Element context — tag name, id, classes, inner text,
href/src - Diagnostics — console errors, failed network requests, performance timings
- Attachments — screenshot, screen recording, voice note
- Triage state — status, priority, assignee, labels, issue type, sentiment
Threads created from a hosted feedback form are marked form; threads pinned
through the widget are marked widget. Both land in the same inbox and you can
filter on the difference.
Where a setting lives
| I want to change… | Go to |
|---|---|
| Plan, seats, invoices | Workspace → Billing |
| Who's on the team | Workspace → Team |
| Slack / Linear / Jira credentials | Workspace → Integrations |
| Widget colour, corner, label | Project → Settings → Appearance |
| Which hosts the widget runs on | Project → Settings → Environments |
| Analytics or Live chat on/off | Project → Settings → Features |
| Show the widget on production | Project → Settings → Features |
| AI model, visitor audience | Project → Settings → Configuration |
| Which integrations fire for this project | Project → Integrations |
| Labels and issue types | Project → Labels |