Data & transparency
What Pier stores, what leaves your machine, and what we can't see. Written from the code.
Pier runs on your machine and works without an account. That makes most of this page short: your projects, your logs, and your notes stay where they are. The rest covers the places data does move — the cloud features you opt into, and the app checking for updates.
Everything below comes from auditing our own code, and we'd rather understate than round up. If you ever catch the app doing something this page doesn't mention, that's a bug. Tell us.
What stays on your machine
Pier keeps its state in one folder: ~/Library/Application Support/pier on macOS, %APPDATA%\pier on Windows. Inside it:
- the project list (a folder path and an optional display name; stack detection reruns live and is never saved)
- run history, with each run's full output as a local file. Pier keeps the last 50 runs per project by default; you can change that in Settings
- your per-project notes
- a small log of agent activity when you use MCP: timestamp, client, method, ok. No payloads. It's kept for three days, and turning it off deletes the file
Two things deliberately don't live in that folder. Secrets (your sign-in token, generated database passwords) go to the macOS Keychain or Windows Credential Manager instead of files. And notes are stored per-user rather than in your repo, so a token you paste into a scratchpad can't end up in a commit.
The one thing Pier writes inside a repo is .pier/config.json: IDE and terminal preferences, shortcuts, bookmarks, pinned commands, saved layouts, local domains, and a passwordless list of the services the project uses. It's meant to be committed and shared with a team. Gitignore it if you'd rather not.
None of this syncs. That isn't a policy we're promising to follow; the storage code has no networking in it. Your code, your paths, your script output, your env vars, your notes: Pier has no way to send them anywhere.
Share links
When you share a dev server, traffic flows from the visitor to Cloudflare's edge, then to a Cloudflare connector running on your machine, then to your local port. It never passes through our servers; we couldn't read your request bodies if we wanted to. Cloudflare can, though. It terminates TLS at its edge, so tunnel traffic is visible to Cloudflare inside its network, under Cloudflare's terms. That's the trade every hosted tunnel makes, and it's worth knowing before you share something sensitive.
The traffic inspector in the app works because Pier's local proxy sits in front of your dev server, on your machine. It reads the request line and the Host header. That gives it a method, a path (including any query string), a status, and a timing. It holds the last 100 requests per tunnel in memory and forgets them when the tunnel closes. Nothing is written to disk.
What our server does know about a tunnel: the hostname, the subdomain, the Cloudflare resource ids, your user id, and when it started and ended. That row sticks around as your tunnel history, and you can delete it. There's one other thing our server sees: requests to a share URL that isn't live. With no tunnel to answer, the request lands on us instead, gets the "not live" page, and leaves an ordinary access-log line (method, path, status). A live tunnel never touches our server.
The connector is Cloudflare's own cloudflared, unmodified. When Pier installs it, the download is verified against a pinned checksum before it ever runs.
Analytics
There's nothing to disclose yet, and we're being precise about why: Pier ships with analytics dormant. The code exists, but the key that would turn it on isn't set, so no build sends anything.
At some point we'll want to know things like how many people actually use the terminal. When we flip it on, this is what the code already enforces:
- Events are named and counted, nothing more.
app_launched,dev_server_started,tunnel_created, and eleven others like them. A sanitizer drops any property that isn't a boolean, a number, or a short slug, so a path, URL, or email can't get through even by accident. - No autocapture. No pageview tracking. No session recording.
- No link to your account. The only identifier is an anonymous id the analytics SDK mints itself.
- The off switch is in Settings → General. One admission: flipping it off sends a single final event that says "someone opted out" (we want to count those), and after that, silence. Opting out also discards the anonymous id.
- The app's sandbox allows exactly one analytics hostname. A bug can't quietly add more.
Separately, and always on: the app posts {version, os, arch, channel} to our server on launch and every four hours. That's the kill switch — if we ever ship a build broken enough to need remote flagging, this is how we'd reach it. It carries no identifier, we don't store it, and if the request fails the app just carries on. The auto-updater is similar: it fetches a static manifest file from our CDN and compares versions locally, so the request doesn't even say which version you're on.
No crash reporting in the app. No analytics on this website.
If you sign in
You never need an account for the local features. Signing in exists for share links, reserved subdomains, and billing. Here's what we hold:
- your GitHub name, email, avatar, and username, plus a plan field
- your sign-in sessions, each with the IP address and browser/OS it came from. The app shows these back to you as your devices list, and you can revoke any of them
Payments go through Polar as the merchant of record. Your card details go to Polar and stay there. What lands in our database after a purchase is one word: your plan.
The waitlist
The form stores your email, the GitHub username if you added one, whatever you typed in the note, and the IP the submission came from. The IP is for spam triage and nothing else. We use the rest for exactly one purpose: sending you an invite. No mailing list. We don't share it. If you want your entry gone, ask and we'll delete it.
Questions
If anything here doesn't match what the code actually does, we want to know: github.com/azaek. A wrong sentence on this page is a bug like any other.