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.
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.
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”.
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.
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.emailas 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.identityis not available there.
Cross-browser variation
- Chrome:
getProfileUserInfowithaccountStatusfrom Chrome 84;onSignInChangedavailable. - 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
- In a Chrome profile signed in with sync, call
browserAccount(): id present, email present only after grantingidentity.email. - Turn sync off and compare
"SYNC"and"ANY"results. - In a guest profile, confirm
nullis returned and the UI falls back to manual entry. - 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.
Related
- Choosing between getAuthToken and launchWebAuthFlow — the sign-in itself.
- Handling multiple accounts in an extension — when the browser account is not the right one.
- Justifying sensitive data permissions — explaining identity.email to reviewers.
- Identity and OAuth authentication — the parent topic.