Engineering Journal
Ginexys
Ginexys

Building a VS Code Extension That Treats the Install as the Conversion Event

2026-06-03

TLDR

Gating core functionality inside a VS Code extension with usage counters (for example, "2 free runs remaining") fundamentally misinterprets developer conversion dynamics. While anonymous web visitors require usage paywalls to nudge account creation, installing an extension is already a high-intent conversion act. Extension authentication should exist strictly to surface feature tiers (free vs pro), while core functionality remains 100% accessible.
Surface ContextUser Acquisition FunnelConversion MechanismPaywall Strategy
Web ApplicationLow-intent anonymous visitsOutput / Export GateEncourage sign-up at download
VS Code ExtensionHigh-intent installationNon-blocking Tier SurfaceNever block core operations

Web-style paywalls frustrate high-intent IDE extension users

When developers ship both a web application and a VS Code extension, the temptation is to copy-paste the exact same usage-gating logic to both surfaces.

On the web, tracking guest usage in localStorage makes total sense: thousands of casual visitors evaluate the tool daily, and gating export actions nudges committed users to create accounts.

In a VS Code extension, the user has already browsed the Marketplace, reviewed extension details, clicked Install, and restarted their IDE. They have committed far more effort than filling out a web sign-up form. Blocking them with a counter stored in their OS keychain after two runs frustrates your most committed users.


Sandboxed webviews prevent standard browser authentication redirects

Beyond conversion economics, running auth inside a webview panel introduces technical failure modes:

  1. Webview panels operate in sandboxed contexts without access to browser cookie stores or standard OAuth redirects.
  2. Webviews cannot communicate across windows via BroadcastChannel.
Attempting to run client-side web auth scripts inside a webview results in broken OAuth redirects and orphaned state.


Extension host message passing resolves user tiers non-blockingly

Authentication must be handled entirely by the Extension Host (Node.js layer) communicating with the webview via message passing:

  Webview Panel              Extension Host (Node.js)             Ginexys Cloud API
       |                                |                                     |
       |--- 1. postMessage ------------>|                                     |
       |    {type: 'analyze-check'}     |                                     |
       |                                |--- 2. Check authProvider.getToken() |
       |                                |                                     |
       |                                |=== [If Token Present] ==============|
       |                                |                                     |
       |                                |--- 3. fetch(/api/me) -------------->|
       |                                |<-- Return { tier: 'pro' } ----------|
       |                                |                                     |
       |                                |=== [Else / Network Failure] ========|
       |                                |                                     |
       |                                |--- Fallback safely to 'free'        |
       |                                |=====================================|
       |<-- 4. postMessage -------------|                                     |
       |    {allowed: true, tier}       |                                     |
       |    (ALWAYS ALLOWED!)           |                                     |

Extension host handler (extension.ts)

The extension host checks credentials securely in the background and resolves the user tier non-blockingly:
// Extension Host: Always allow execution, resolve tier non-blockingly
let token: string | null = null;
try { token = await authProvider.getToken(); } catch { token = null; }

let allowed = true; // Core functionality is ALWAYS allowed let tier = 'free';

if (token) { try { const res = await fetch('https://api.ginexys.com/api/me', { headers: { Authorization: Bearer ${token} } }); if (res.ok) { const data = await res.json(); tier = data?.tier ?? 'free'; } } catch { // Network errors fall back safely to 'free' tier without blocking execution } }

panel.webview.postMessage({ type: 'analyze-response', payload: { allowed, tier } });

Webview message contract

The webview asks the extension host for permission state on startup:
// Webview sends message to host
vscode.postMessage({ type: 'analyze-check', __ginexys: true });

// Host responds with tier state; webview renders UI accents accordingly window.addEventListener('message', e => { if (e.data?.type === 'analyze-response') { const { tier } = e.data.payload; updateTierUI(tier); } });

Rule of thumb: Treat the extension installation as the primary conversion event. Never block core tool functionality inside a VS Code extension; use authentication exclusively to surface Pro feature indicators and personalized user state.
Read this post in the full Engineering Journal →