Building a VS Code Extension That Treats the Install as the Conversion Event
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 Context | User Acquisition Funnel | Conversion Mechanism | Paywall Strategy |
|---|---|---|---|
| Web Application | Low-intent anonymous visits | Output / Export Gate | Encourage sign-up at download |
| VS Code Extension | High-intent installation | Non-blocking Tier Surface | Never 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:
- Webview panels operate in sandboxed contexts without access to browser cookie stores or standard OAuth redirects.
- Webviews cannot communicate across windows via
BroadcastChannel.
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.