onboardingactivationchrome-extensionsmanifest-v3

Chrome Extension Onboarding: First 60 Seconds Decide Retention

Roughly half of all Chrome extension installers never use the extension once. The fix isn't a fancier welcome page — it's a specific MV3 pattern for the first 60 seconds after install, with code and SQL for drop-off measurement.

Roughly half of all Chrome extension installers never use the extension once. They click Add to Chrome, the confirmation dialog disappears, the next tab they look at isn't yours, and the install becomes dormant within 24 hours and uninstalled within 7 days. The fix is not a fancier welcome page — every extension that has tried one knows that. The fix is a specific Manifest V3 pattern for the first 60 seconds, with three principles, four pieces of code, and one diagnostic SQL view to tell you whether it's working.

Why "the first 60 seconds" is the right unit

Activation isn't a feature; it's a time window. Three observations that converge on 60 seconds:

  • The post-install tab competes with everything else the user has open. A typical Chrome session has 5–8 tabs. Within 60 seconds the user's attention has moved to the next thing on their list. If you didn't deliver value in that window, you're a bookmark, not a tool.
  • Decision fatigue spikes immediately after the permission dialog. The dialog (covered in the permission warning guide) is a deliberate moment. After it, the user wants to verify the extension was worth installing, fast.
  • Activation-window data consistently shows the same curve. Across extensions, >60% of users who ever activate do so within the first 60 seconds. Users who don't activate in that window mostly never do — the tail of activation is small relative to the head.

Translation: design the first 60 seconds, not the "welcome page". The page is a vehicle; the user's time is the constraint. The patterns below are sorted by what actually moves activation rate inside that window.

Four onboarding patterns ranked by activation

From highest to lowest observed activation lift, with what each gives up:

  1. Inline-action onboarding (highest activation). The extension does something useful on install without the user navigating anywhere. Translator: detects the active tab, shows a tiny "Translate this page →" nudge. Clipper: detects the user is on a clip-eligible site and surfaces the clip button in-place. The user's muscle memory finds the value without leaving their current task.
  2. Post-install welcome tab with single CTA. One tab opens, one button on it, one outcome. "Click here to translate this page". The post-install tab is discussed in detail in the next section. Activation rates of 50–65% are achievable here when the CTA is one specific action.
  3. Multi-step wizard onboarding. The classic "3 of 5: Choose your preferences" pattern. Works for extensions that genuinely need configuration (password managers, dev tools tied to a workspace). Drops activation 15–35% versus single-CTA for everything else — every step doubles drop-off.
  4. No onboarding at all (silent install). Activation depends entirely on the user remembering they installed. Sometimes the right choice for tiny utilities (a one-shortcut tool) but generally bottom-tier.

Most extensions land in patterns 2–3. Moving from 3 to 2 is the single biggest activation lift available. Moving from 2 to 1 requires more product engineering but compounds the win.

The post-install welcome tab pattern

For everything that's not already inline-action, the post-install tab is the workhorse. Done correctly, it does five things:

  • Opens automatically in a foreground tab (with active: true) so the user doesn't have to find it.
  • Loads in <500ms from a bundled URL — no external fetch, no spinner. Cold-start latency here costs activation directly.
  • Has exactly one primary action above the fold. Not three. Not a tour. One button or one input.
  • Demonstrates the value visibly within the first second. A 30-second video doesn't count; something that animates or actively does the thing does.
  • Captures the activation event (extension.welcome.viewed, then the action event like translation.run) so you can measure drop-off precisely.

The activation event set you defined in the DAU/MAU guide is what gates the onboarding funnel — that's the same set the "never-activated" detector in why users uninstall uses. Optimizing this window directly reduces the never-activated bucket of churn.

Code: opening the welcome tab from onInstalled

The MV3 pattern, with the gotchas that trip teams up:

// background.js — REGISTER THE LISTENER SYNCHRONOUSLY AT TOP LEVEL.
// If you wrap this in any async work, Chrome can terminate the worker
// before the listener registers and the install event is lost.

chrome.runtime.onInstalled.addListener(async (details) => {
  // Only on fresh install, not on update/reload.
  if (details.reason !== 'install') return;

  // Open the welcome page. URL must be bundled (chrome-extension://...),
  // never a remote URL (MV3 forbids remote code paths).
  const url = chrome.runtime.getURL('welcome.html');
  await chrome.tabs.create({ url, active: true });

  // Fire the activation funnel's entry event so you can measure drop-off.
  await sendEvent({ name: 'extension.welcome.opened' });
});

Four common mistakes:

  • Listener registered inside an async wrapper. Top-level await or wrapping in an async IIFE delays registration past the install event. The pattern from the service worker survival guide applies here directly — register synchronously.
  • Checking details.reason wrong. Without the check, your welcome tab opens on every browser restart after an extension update. Users hate this.
  • Using a remote URL. chrome.tabs.create({ url: 'https://yoursite.com/welcome' }) partially works but loses the install identity (the anonymous_id isn't on yoursite.com) and is slower. Bundle the page.
  • Forgetting active: true. Without it, the tab opens in background and the user never sees it.

Code: tracking onboarding step completion

Every step of the funnel needs an event so you can find the drop-off later. For a single-CTA welcome page:

// welcome.js — runs inside welcome.html.

// 1. Welcome page actually rendered (not just tab opened).
sendEvent({ name: 'onboarding.welcome.rendered' });

// 2. User clicked the primary CTA.
document.getElementById('try-it').addEventListener('click', async () => {
  sendEvent({ name: 'onboarding.cta.clicked' });
  // Trigger the actual feature inline if possible — don't navigate elsewhere.
  await tryTheFeature();
});

// 3. The feature completed successfully (= activation).
async function tryTheFeature() {
  try {
    const result = await translate('Hello, world!');
    showResult(result);
    sendEvent({ name: 'translation.run', properties: { source: 'onboarding' } });
  } catch (err) {
    sendEvent({
      name: 'onboarding.cta.failed',
      properties: { error: String(err?.message ?? err) },
    });
  }
}

// 4. User left the welcome tab without acting.
window.addEventListener('beforeunload', () => {
  // beforeunload is unreliable, so use sendBeacon for the abandonment ping.
  navigator.sendBeacon(
    'https://your-api.example.com/v1/events',
    new Blob([JSON.stringify({ name: 'onboarding.abandoned' })], { type: 'application/json' })
  );
});

Five events from one page: opened → rendered → clicked → ran (= activated) → abandoned. The funnel between them tells you exactly where the drop-off is. The navigator.sendBeacon in the abandon handler is important — a normal fetch aborts when the tab closes; the beacon survives.

Defaults beat configuration screens

The single largest activation-killer we see is the configure-before-use pattern: the user is asked to pick a language, paste an API key, choose a workspace, or grant a host permission before anything happens. Each step approximately doubles drop-off:

  • 1 step (no config): activation 60–70%.
  • 2 steps: activation 30–40%.
  • 3 steps: activation 15–20%.
  • 4+ steps: activation usually below 10%.

The fix is to default for the 80% case and let the 20% configure afterward. A translator that defaults to detect-source-language + English-target loses precision for advanced users but gains 2× activation. The advanced users find the settings page; the casual users get value immediately.

Three places to default rather than ask:

  • Language. Use chrome.i18n.getUILanguage() for source/target guesses. The user can override.
  • Workspace selection. If they have one workspace, pre-select it. If they have multiple, pick the most recently active one and surface a small switcher.
  • API keys. Where possible, offer a free tier that doesn't require one, and gate "bring your own key" behind an opt-in. The free tier is the activation tier; the BYOK tier is the retention tier.

Test the default vs configure approach via the pattern in A/B testing without a backend. The activation delta is usually clear within a week.

SQL: where users drop off in your funnel

With the four events from the step-tracking section, the drop-off is a single query:

-- Onboarding funnel, last 14 days of installs.
WITH installs AS (
  SELECT anonymous_id, MIN(timestamp) AS installed_at
  FROM events
  WHERE project_id = $1 AND event_name = 'ext.installed'
    AND timestamp >= now() - interval '14 days'
  GROUP BY 1
),
step AS (
  SELECT
    i.anonymous_id,
    BOOL_OR(e.event_name = 'extension.welcome.opened') AS opened,
    BOOL_OR(e.event_name = 'onboarding.welcome.rendered') AS rendered,
    BOOL_OR(e.event_name = 'onboarding.cta.clicked') AS clicked,
    BOOL_OR(e.event_name = 'translation.run' AND e.properties->>'source' = 'onboarding') AS activated
  FROM installs i
  LEFT JOIN events e
    ON e.project_id = $1
   AND e.anonymous_id = i.anonymous_id
   AND e.timestamp BETWEEN i.installed_at AND i.installed_at + interval '1 hour'
  GROUP BY i.anonymous_id
)
SELECT
  COUNT(*) AS installs,
  COUNT(*) FILTER (WHERE opened) AS opened,
  COUNT(*) FILTER (WHERE rendered) AS rendered,
  COUNT(*) FILTER (WHERE clicked) AS clicked,
  COUNT(*) FILTER (WHERE activated) AS activated,
  ROUND(100.0 * COUNT(*) FILTER (WHERE opened) / COUNT(*), 1) AS opened_pct,
  ROUND(100.0 * COUNT(*) FILTER (WHERE rendered) / NULLIF(COUNT(*) FILTER (WHERE opened), 0), 1) AS rendered_of_opened_pct,
  ROUND(100.0 * COUNT(*) FILTER (WHERE clicked) / NULLIF(COUNT(*) FILTER (WHERE rendered), 0), 1) AS clicked_of_rendered_pct,
  ROUND(100.0 * COUNT(*) FILTER (WHERE activated) / NULLIF(COUNT(*) FILTER (WHERE clicked), 0), 1) AS activated_of_clicked_pct
FROM step;

Three drop-off patterns and what each implies:

  • opened >> rendered. The welcome tab opens but the page doesn't render. Almost always a load failure: an external dependency, slow JS, the tab closed before paint. Bundle everything; profile the cold start.
  • rendered >> clicked. Users see the page and bounce without engaging. The CTA isn't clear enough, the page asks for too much, or the value isn't obvious. Test radical simplification.
  • clicked >> activated. The CTA fires but the feature fails. Cross-reference with the onboarding.cta.failed events from the step tracking — usually an environment issue (no current tab, host permission missing, network error). The error tracking patterns from the error tracking guide apply.

Three onboarding anti-patterns

The video wall

A 90-second product video as the first thing the user sees. Watch rates are below 10% in the activation window. The user doesn't install to be educated; they install to use. A 20-second silent loop showing the feature working can replace a 90-second narrated tour with no activation loss.

The forced sign-up

Asking for an account before the user has seen the value. Account creation drops activation by 30–50% on its own. If you must have accounts, gate them behind a clear "Save your work" step that comes after the user has used the feature once. Anonymous-first works for almost every extension.

The permission carousel

Asking for every optional permission upfront, one at a time. Each permission dialog is a bounce risk. The fix is in the permission warning guide: ask for permissions at the moment the user invokes the feature that needs them, not upfront.

FAQ

What activation rate should I aim for?

60–75% for an extension with inline-action onboarding; 50–65% for a single-CTA welcome tab; 35–45% for a multi-step wizard. Below 30% is a signal that one of the anti-patterns above is hurting you, not that your audience is fundamentally unlikely to activate.

Should I open the welcome tab on extension update too?

Rarely. Check details.reason === 'install' not === 'update'. For substantial feature releases you can open a smaller in-extension notice (badge, popup hint) — never a full tab. Users hate forced tabs on update.

What about side panel onboarding?

Works well for extensions where the side panel is the product (research tools, dev panels, sidebar reading mode). Use chrome.sidePanel.open() from the install handler instead of chrome.tabs.create. Don't use side panels for extensions whose primary surface is the page or popup; the side panel feels intrusive when it isn't the core surface.

Can I A/B test the welcome page?

Yes — pair this with the pattern in A/B testing without a backend. The deterministic-hash variant on the first event ensures every user sees a stable variant through the entire onboarding flow. Common winning experiments: single-CTA vs multi-step, headline copy, default values.

What if my extension genuinely needs configuration before it works?

Three escapes: (1) defer configuration to the first use — translator runs on a default pair, asks language only when the user clicks "change languages"; (2) detect sensible defaults from context (workspace from the active tab's URL, language from chrome.i18n.getUILanguage()); (3) skip configuration and provide a free tier that works without any keys.

Does activation rate affect Chrome Web Store ranking?

Probably indirectly — activation feeds retention, retention feeds review rate, review velocity is a CWS ranking signal per the Chrome Web Store SEO guide. So the compound chain is: better onboarding → more activated users → more reviews → better rank → more installs. Slow but real.

Should I show a sign-up prompt right after activation?

Only if your retention story actually needs it. For most extensions, anonymous-first works through the first session and even the first week. A sign-up prompt after the user has completed three feature uses converts dramatically better than one shown immediately after first use, with no activation cost.

See where your onboarding funnel breaks
Crxlytics tracks the four onboarding events automatically and renders the install → opened → rendered → clicked → activated funnel per release, source, and variant. The never-activated churn bucket shrinks the moment you can see it.
Get started free →