Getting the Signed-In User's Profile Info

Read the browser user's email and id with chrome.identity.getProfileUserInfo: the identity.email permission, accountStatus ANY, empty results when sync is off, privacy implications and Firefox and Safari alternatives.

Published October 2, 2026 Updated October 2, 2026 7 min read
Table of Contents

You want to pre-fill the email field on your sign-up page, show “Signed in as ana@example.com” in the popup, or link the extension to an account without asking the user to type anything. Chrome exposes the Google account signed into the browser profile through chrome.identity.getProfileUserInfo. It is a small API with an outsized number of surprises: it returns empty strings for many users, needs a permission that adds an install warning, means nothing in other Chromium browsers, and does not exist in Firefox or Safari. This guide explains when it is worth using and how to use it without leaning on it. It belongs to identity and OAuth authentication.

What the API returns, and when it returns nothing

chrome.identity.getProfileUserInfo({ accountStatus }) resolves to { email, id } describing the Google account associated with the current Chrome profile. id is a stable, obfuscated Google account identifier; email is the account’s address and is only populated if the extension has the identity.email permission. With the default accountStatus: "SYNC", both fields are empty strings unless the user is signed in and has Chrome Sync enabled. With accountStatus: "ANY", Chrome also returns the primary account when the user is signed in without sync. Users who are not signed into Chrome, users of managed profiles with sign-in disabled, and users of most other Chromium browsers all get empty strings. The API never prompts and never fails — it simply reports what the profile knows.

What getProfileUserInfo returnsDecision tree: not signed in returns empty strings; signed in without sync returns data only with accountStatus ANY; signed in with sync returns id, plus email only with the identity.email permission.Profile state?not signed inEmpty stringsany accountStatusAsk the usernormal sign-insigned in, no syncEmpty with SYNCdata with ANYUse accountStatus ANYif you need itsigned in + syncid returnedemail needs permissionidentity.emailadds a warning
An empty result is common — design for it rather than treating it as an error.

Step-by-step: use profile info as a hint

1. Declare the permissions deliberately

1{
2  "permissions": ["identity"],
3  "optional_permissions": ["identity.email"]      // request only if the feature needs the address
4}

Execution context: the manifest. identity alone lets you read the account id, with no warning. identity.email adds “Know your email address” to the install dialog — a warning that lowers install rates for little benefit if the email is only used for pre-filling a form. Making it optional and requesting it from a button (“Use my Chrome account email”) puts the warning in context.

2. Read the profile with the right account status

1// sw.js
2export async function browserAccount() {
3  if (typeof chrome.identity?.getProfileUserInfo !== "function") return null;
4  const info = await chrome.identity.getProfileUserInfo({ accountStatus: "ANY" });
5  if (!info.id) return null;                         // not signed in, or not Chrome
6  return { id: info.id, email: info.email || null };
7}

Execution context: the service worker or an extension page. Feature detection covers Firefox and Safari, where getProfileUserInfo does not exist. "ANY" returns the primary account even when sync is off, which is what most features want. Treat an empty id as “unknown”, never as an error, and an empty email as “permission not granted”.

Profile info availabilityWhether getProfileUserInfo returns an id and email in Chrome with various sign-in states, in other Chromium browsers, and in Firefox and Safari.EnvironmentidemailChrome, signed in + syncYesWith identity.emailChrome, signed in, no syncWith ANYWith ANY + permissionChrome, not signed inEmptyEmptyEdge, Brave, OperaUsually emptyUsually emptyFirefox, SafariAPI missingAPI missing
Only Chrome users who are signed in produce a value — a minority in many audiences.

3. Request the email permission in context

1// options.js
2useChromeEmailBtn.addEventListener("click", async () => {
3  const ok = await chrome.permissions.request({ permissions: ["identity.email"] });
4  if (!ok) return;
5  const acct = await chrome.runtime.sendMessage({ type: "browser-account" });
6  emailInput.value = acct?.email ?? "";
7});

Execution context: the options page or a sign-up page inside the extension, from a click so the permission prompt is allowed. The user chooses to share the address at the moment it is useful. If they decline, the form simply stays empty.

4. Never use the profile id as authentication

1// WRONG — anyone can send any id to your server
2await fetch("/api/link", { method: "POST", body: JSON.stringify({ googleId: info.id }) });
3
4// RIGHT — authenticate with a token the server can verify
5const token = await signInWithOAuth();                 // launchWebAuthFlow or getAuthToken
6await fetch("/api/link", { headers: { Authorization: `Bearer ${token}` } });

Execution context: the service worker. getProfileUserInfo tells the extension who the browser user is; it gives your server nothing it can verify. A request claiming a Google id or email can be forged by anyone. Use the profile info for convenience in the UI — pre-filling, display, choosing a default account — and real OAuth for anything the server trusts, as described in authenticating against your own backend with JWTs.

Profile info as a hint, OAuth as the proofThe popup reads the browser account email to pre-fill the sign-in screen; the user signs in through launchWebAuthFlow; the server verifies the resulting token, not the profile email.PopupService workerIdentity providerYour APIbrowser-accountana@example.com (hint)sign in (login_hint)launchWebAuthFlowcode → tokenBearer token (verified)
The profile makes sign-in faster; it never replaces it.

5. Pass the email as a login hint

1const acct = await browserAccount();
2const url = new URL("https://accounts.google.com/o/oauth2/v2/auth");
3url.searchParams.set("client_id", CLIENT_ID);
4url.searchParams.set("redirect_uri", chrome.identity.getRedirectURL());
5url.searchParams.set("response_type", "code");
6url.searchParams.set("scope", "openid email");
7if (acct?.email) url.searchParams.set("login_hint", acct.email);

Execution context: the service worker, building an authorisation URL for launchWebAuthFlow. login_hint lets the identity provider pre-select the right account, saving the user a step when they have several Google accounts. It is a hint only; the user can choose another account, and your code must use whichever account the token actually belongs to.

6. React to account changes

1chrome.identity.onSignInChanged?.addListener((account, signedIn) => {
2  if (!signedIn) clearCachedBrowserAccount();
3  else refreshBrowserAccount();
4});

Execution context: the service worker, top-level listener. onSignInChanged fires when the user signs in or out of the browser profile. It does not sign the user out of your service — that is a separate session — but cached display values (“Signed in to Chrome as…”) should update. See handling multiple accounts in an extension.

7. Disclose what you read

If you read the email, say so in the store listing’s privacy section and in your privacy policy: what you read, why, and whether it leaves the device. Reading an email address only to pre-fill a field locally is easy to justify; sending it to a server without a sign-in is collection that reviewers and users will question.

Common mistakes

  • Treating empty strings as errors. They are the normal result for many users.
  • Using identity.email as a required permission for a convenience feature. It costs installs.
  • Trusting the profile id on the server. It is not proof of identity.
  • Assuming Edge or Brave return a Google account. They usually do not.
  • Calling it from a content script. chrome.identity is not available there.

Cross-browser variation

  • Chrome: getProfileUserInfo with accountStatus from Chrome 84; onSignInChanged available.
  • Edge / Brave / Opera: the API exists but browser sign-in is not a Google account in the same way; results are usually empty.
  • Firefox / Safari: the API is not available. Ask the user to sign in to your service directly.

Verification

  1. In a Chrome profile signed in with sync, call browserAccount(): id present, email present only after granting identity.email.
  2. Turn sync off and compare "SYNC" and "ANY" results.
  3. In a guest profile, confirm null is returned and the UI falls back to manual entry.
  4. Confirm no server request uses the profile id or email as proof of identity.

FAQ

Is the id the same as the Google account’s “sub” claim?

It is a stable identifier for the account but not guaranteed to equal the OpenID sub your server sees. Do not use one to look up the other.

Can I get the user’s name or avatar?

Not from this API. Request profile scopes in an OAuth flow if you need them.

Does reading profile info show a prompt?

No. Only the identity.email permission request does, if you make it optional.

Should I cache the result?

Cache it in chrome.storage.session for display, and refresh it on onSignInChanged and on startup. Do not persist it to local or sync — it describes the browser profile, which can change while your extension is installed.

Other Core APIs & Cross-Browser Data Management Resources