Chrome Extension Writing Test Data to Production? Add a DRY-RUN Switch
TL;DRโ
Chrome extensions submitting collected data via API write directly to the production database โ even during development and testing. A 3-layer DRY-RUN switch solves this: set an env variable in .env.development โ client reads it and adds an X-Dry-Run header โ server intercepts the header and returns a data preview without writing. Production never sets the variable, so it's completely unaffected.
Problemโ
A Chrome extension collects e-commerce data and submits it via API. During development, every test run writes a record to the production database. After a few rounds of testing, the database is full of dirty data that corrupts production analytics.
There's no "preview only" switch โ either comment out the submission code (easy to forget reverting) or accept dirty data in production.
Root Causeโ
The Chrome extension shares the same API endpoint for both development and production. The fetch request has no marker to distinguish "this is a test submission" from "this is a real one." The backend processes all requests identically โ write to the database.
What's needed: a "preview mode" that shows what data would be submitted without actually writing it.
Solutionโ
Three layers: environment variable โ HTTP header โ server-side interception.
Step 1: Declare the env variable typeโ
// client/src/env.d.ts
interface ImportMetaEnv {
// ... other variables
readonly VITE_INQUIRY_DRY_RUN?: string;
}
Step 2: Development environment configโ
# client/.env.development (development only)
VITE_INQUIRY_DRY_RUN=true
# client/.env.production (do NOT set this variable)
# Production always uses real writes
Step 3: Client reads env, conditionally adds headerโ
In the Service Worker (background script):
// background.ts
chrome.storage.local.get('sessionToken', (result) => {
const sessionToken = result.sessionToken;
if (!sessionToken) return;
// Read DRY-RUN switch
const dryRun = import.meta.env.VITE_INQUIRY_DRY_RUN === 'true';
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
};
// Add marker header in DRY-RUN mode
if (dryRun) {
headers['X-Dry-Run'] = 'true';
}
fetch(`${baseURL}/inquiry/collect`, {
method: 'POST',
headers,
body: JSON.stringify({ memberId, rows }),
});
});
WXT/Vite inlines import.meta.env.VITE_INQUIRY_DRY_RUN at build time. Dev build (.env.development) gets "true", production build gets undefined, so === 'true' naturally evaluates to false.
Step 4: Server intercepts DRY-RUN requestsโ
// server/src/functions/analytics/inquiry.ts
router.post('/collect', async (req, res) => {
// ... auth, memberId mapping, etc.
const dryRun = req.headers['x-dry-run'] === 'true';
if (dryRun) {
// Skip database write, return preview
return res.json({
code: 200,
message: 'dry-run ok',
data: {
dryRun: true,
shop_id: match.shop_id,
platform_id: match.platform_id,
memberId,
rowCount: rows.length,
sampleRows: rows.slice(0, 3),
},
timestamp: Date.now(),
});
}
// Normal flow: write to database
const result = await analyticsClient.post('/internal/data/import/inquiry', payload);
res.json({ code: 200, data: result });
});
In DRY-RUN mode, the server still runs auth and validation (ensuring data format is correct) โ it only skips the final database write. This lets you verify both data format and permissions without producing dirty data.
Important Notesโ
Never set DRY-RUN in production .env
.env.production should NOT set VITE_INQUIRY_DRY_RUN. In production builds, the variable is undefined and the condition naturally evaluates to false. Never enable this switch in production config.
Client must check the dryRun flag
DRY-RUN mode returns HTTP 200 with { dryRun: true, data: {...} }. Client code that only checks code === 200 will mistakenly treat it as a successful write. Always check response.data.dryRun to distinguish preview from actual submission.
DRY-RUN should still run auth and validation
Don't intercept DRY-RUN requests before authentication. Keep the full request chain (auth โ validate โ intercept) so you can verify request format and permissions โ just skip the final write step.