# Anonymous report

URL: https://appflare.dev/telemetry/

> What leaving the anonymous report on does, what Appflare sends and never sends, where it goes, and the ways to turn it off.

Appflare collects anonymous usage data to decide what to build and fix: the manager
sends an anonymous report once a day, with an event for each job, and the installer
sends one event when it ends. It is on by default and easy to turn off. The manager's first report
goes out with its first scheduled run after setup finishes (or, on a manager updated
from a version without the report, after the update). No banner in the manager
announces it: this page, **Settings > Anonymous report** in the manager, and the
installer's notice when it starts are where it is disclosed, and nothing waits for an
answer. A development build never sends anything, and a pre-release of the manager (a
version with a pre-release part, such as `0.4.0-rc.1`) sends no report, so test
managers stay out of the numbers. **Settings > Anonymous report** says so on either,
and the Worker's log notes the skipped report once.

## What it does for you

Every manager runs in someone's own Cloudflare account, where the maintainers cannot
see it. The anonymous report is how they find out what happens there. Leaving it on lets them:

* **Keep the versions you run working.** They see which versions of the manager are
  in use, so they know which ones to keep supporting and which upgrade paths to test
  an update against.
* **Fix what breaks, sooner.** They learn which install paths, apps and steps fail,
  and how often, so the most common failures get fixed first and updates break less
  often.
* **Work on what matters.** They see which features people use, such as passkeys,
  automatic updates or notification channels, so time goes where it helps most.

Turning it off changes nothing about how Appflare works for you.

The anonymous report is not the usage Appflare shows you. What each of your apps uses
of Cloudflare, and what it costs, stays in your Appflare; the report only counts
pausing and resuming an app as jobs, like the others below. See
[Usage and spend protection](/guides/spend-protection/).

## What is sent

Every event carries the Appflare version, whether it came from the manager or the
installer, and the random id described [below](#how-it-is-sent). Everything else is
a count, a yes or no, a version, a duration, or a value from a fixed list, apart from
the filtered Cloudflare error message a failed job can carry ([below](#jobs-manager)).

### Daily report (manager)

Once a day, the manager sends one report with:

* **Manager:** its database schema version, the Workers plan chosen in **Settings > Your account** (`free`,
  `paid` or unset), whether it uses Appflare's catalog or a custom one, how many
  catalogs were added in **Settings > Catalogs** (a count only, never their URLs or
  labels), days since setup, and whether a newer manager release exists.
* **Users:** how many users and admins there are, whether passkeys are in use, and
  how many users have one.
* **Features:** whether Cloudflare Access protection is on, whether the sandbox
  Worker is connected, whether Appflare and apps update automatically, and how many
  apps use the account setting for automatic updates or override it with on or off,
  and how many notification channels there are of each kind (Telegram, Slack,
  Discord, webhook), and how many GitHub access tokens there are (the count only: never
  a label, a repository or a token).
* **Apps:** how many are installed, counted by status (installed, failed,
  installing, updating, uninstalling), by build type (prebuilt, sandbox,
  self-deploying), and by version age (current, or, when the catalog has a newer
  version, how long ago the running one was installed: under 7 days, 7 to 30, 30 to
  90, over 90 days); how many have a custom domain,
  Email Routing, or cron triggers; how many install as several Workers; how many
  come from added catalogs; how many
  removed apps still keep data; and the catalog slugs of the installed apps, only
  for apps in Appflare's own catalog.

### Jobs (manager)

For each install, update, settings change, rollback, database restore, uninstall,
deletion of a removed app's kept data, build for review, pause, resume, manager
self-update, and manager rollback, one event when it starts and one when it ends, with:

* the kind of job, the app's catalog slug (`custom` for an app from a custom
  catalog or from a repository), the version it moves from and to (none for a custom
  catalog's app, an app from a repository, or an app built from source), the build
  type, where the app comes from (`catalog`, `repository` or `source`), how many
  Workers the app installs, and what started it (an admin, or the schedule for
  automatic updates and automatic pauses); a manager rollback
  carries its kind only, never the versions it moves between. Nothing about a
  repository is sent: not its address, owner, name, branch or commit;
* when it ends: whether it succeeded, how long it took, and for a failure, its
  error category (such as `cloudflare_permission`, `plan_limit`, `d1_migration` or
  `access_conflict`), the phase it failed in (such as `resources`, `upload`,
  `health` or `access`), and, for a failed Cloudflare API request, the HTTP status
  and Cloudflare's numeric error code. When the failure is in the Cloudflare
  categories (`cloudflare_permission`, `cloudflare_rate_limited`,
  `cloudflare_unavailable`, `cloudflare_rejected`), it also carries an excerpt of
  Cloudflare's own error message (`cf_message`, at most 200 characters, such as
  `incomplete input: SQLITE_ERROR`). The excerpt starts after Cloudflare's error
  code, so it never holds the request's method or path. It ends at the closing
  bracket when Appflare put Cloudflare's message in brackets, and otherwise at the
  first semicolon that does not start another of Cloudflare's errors, or at a line
  break. Before it is sent, anything in quotes, anything shaped like a hostname and
  every IP address is replaced (Cloudflare's own hostnames, such as
  `dash.cloudflare.com`, and its error keys, such as `access.api.error.conflict`,
  are kept), and so is everything the [failure report](#failure-reports) filter
  takes out: your account id, other ids, email addresses, anything that looks like a
  token or password, your workers.dev subdomain, and every hostname, Worker name and
  other name Appflare recorded. Then every other word of three characters or more
  with a hyphen, an underscore or a digit in it, such as `acme-payroll-api`, is
  replaced (`[name]`), except error constants in capitals such as `SQLITE_ERROR`,
  error codes in brackets, numbers of up to six digits (HTTP statuses, error codes
  and limits), versions and dates. A name made only of letters, such as
  `Intranet Portal`, cannot be told apart from Cloudflare's own words, so it stays
  in the excerpt unless Appflare recorded it. If Appflare cannot read the names it
  recorded when it reports, the excerpt is left out.

### Setup and use (manager)

* Once, after setup: how many minutes passed between connecting Cloudflare and
  creating the owner, and whether the manager continues the installer's id.
* At most once a day: that the manager was opened that day, and whether the first
  person to open it was an admin or a member.

### Installer

The installer sends one event per run, when the install ends:

* whether it succeeded, failed or was cancelled, how long it took, the last step it
  reached, and an error category (such as `wrangler_login`, `wrangler_deploy` or
  `health_timeout`);
* the installer's version, the operating system, CPU architecture and Node.js major
  version;
* whether it ran interactively, in CI, or with `--yes`; whether the Worker name was
  the default one; whether the login had several accounts; whether a wrangler login
  was needed; the coding agent it ran under, if any (such as `claude`, `codex` or
  `cursor`, from the variables those agents set).

## What is never sent

Your Cloudflare account id or name, email addresses, user names, Worker or instance
names, domains or URLs, secret or variable names or values, tokens, resource ids,
logs, error messages, or anything from a custom catalog, its app names and versions
included. The exceptions to logs and error messages are the filtered excerpt of a
Cloudflare API error message described [above](#jobs-manager), and a
[failure report](#failure-reports), which only an admin sends, by hand.

## How it is sent

Every event is tied to a random id created at install, never to a person. Setup
with the installer creates the id and hands it to the manager, so both report under
the same id. The manager sends from its Worker, not from your browser: there is no
tracking script, no cookie and no page view. The installer sends from your machine,
and gives up after 3 seconds so a run is never held up.

**Preview** in **Settings > Anonymous report** shows the next daily report, built the
same way the scheduled one is.

## Where it goes

Events go to [PostHog](https://posthog.com) Cloud in the EU region (Frankfurt), at
`eu.i.posthog.com`, as anonymous events: every event asks PostHog to create no
person profile and to skip its IP-based location lookup.

Two more protections are PostHog project settings, not something the code
enforces: the Appflare project is set to discard IP addresses, and events are kept
for as long as PostHog's plan retains them (a year on the free plan).

## Failure reports

When a job fails, admins see **Send a report** on the failure message: on the job's
page, and on the sandbox and Appflare update cards. Nothing is sent until an admin
opens it, reads what the report contains, and clicks **Send**. A report is never sent
automatically, and each failed job can be reported once.

A report contains:

* the kind of job, the app's catalog slug and version (with the same rules as the
  job events above, so `custom` for an app from a custom catalog or a repository),
  the Appflare version, and the Workers plan;
* the step the job stopped at, its error, the error category and phase, and every
  Cloudflare error code in the log;
* the job's log (its last 400 lines at most);
* a note, if the admin writes one;
* the same random id as the anonymous report, and whether the anonymous report is on.

Before the report leaves, the error, the step, the log and the note are passed once
more through a filter that takes out anything that looks private: tokens, keys and
passwords, including every value of a `NAME=value` environment line and of flags
such as `--password` (shown as `[redacted]`); email addresses (`[email]`); your
Cloudflare account id (`[account id]`); other Cloudflare ids and UUIDs (`[id]`);
your workers.dev subdomain and your custom hostnames (`[domain]`); the names of
your Workers (`[worker]`); and the other names Appflare recorded: the apps'
databases, namespaces, buckets, queues and Access applications, and the names you
gave apps (`[name]`). What stays readable is what a fix needs: Cloudflare's
error codes and HTTP statuses, step names, versions, and the app's catalog slug.
The dialog's **See exactly what is sent** shows the report exactly as it will leave,
after that filter.

A failure report goes the same way as the anonymous report: from the manager's Worker
to the Appflare PostHog project in the EU, as an event called `job_failure_report`. It
is sent even when the anonymous report is turned off, because the admin chose to send it; the dialog says
so. A development build never sends one.

## Turning it off

Any one of these stops everything:

* The **Send the anonymous report** switch in **Settings > Anonymous report**
  (admins).
* The installer's flag: `npx create-appflare --no-telemetry`.
* `APPFLARE_TELEMETRY=off` (`0` and `false` work too) or `DO_NOT_TRACK=1` in the
  installer's environment.
* The same variables on the manager's Worker, set in the Cloudflare dashboard under
  the Worker's **Settings**, **Variables and Secrets**. Either one locks the anonymous
  report off: **Settings > Anonymous report** shows the switch off and disabled, with "Turned off by the
  APPFLARE\_TELEMETRY variable on this Worker. Remove the variable to change this
  here." (naming `DO_NOT_TRACK` when that is the one set). Updates of the manager
  keep the variable.

When the installer's report is off by the flag or a variable, it deploys the
manager with `APPFLARE_TELEMETRY=off`, so the manager's is locked off too.

When it is off, nothing is sent, not even the fact that it is off, apart from a
failure report an admin chooses to send. Turning it on
again continues with the same random id, and jobs that ran while it was off are not
reported.

## Wrangler's own metrics

The installer runs wrangler to deploy the manager. Wrangler has usage metrics and
error reports of its own, which go to Cloudflare. The installer turns both off for
every wrangler command it runs, whether Appflare's anonymous report is on or off, unless
you set `WRANGLER_SEND_METRICS` or `WRANGLER_SEND_ERROR_REPORTS` yourself.
