{"slug": "190-countries-zero-api-calls-shipping-static-data-in-a-chrome-extension", "title": "190 Countries, Zero API Calls: Shipping Static Data in a Chrome Extension", "summary": "This article explains how the Chrome extension EntryCheck avoids common patterns of using external APIs or local user storage by bundling a static dataset of visa requirements for over 190 countries directly into the extension. All lookups are performed client-side with zero network requests, using a compressed JSON matrix that enables instant, synchronous checks. The tradeoff of a larger initial bundle is justified by the infrequent nature of visa policy changes, eliminating latency, authentication, and network dependency issues.", "body_md": "Most Chrome extensions that need data fall into one of two patterns: they call an external API, or they store a small amount of user-specific data locally. [EntryCheck](https://chromewebstore.google.com/detail/gfbdmeieifamgheecbmdpadniofodkfa) does neither. It bundles a static dataset of visa requirements for 190+ passport/destination combinations directly into the extension and resolves every lookup client-side with zero network requests.\n\nThis tradeoff — large bundle, instant lookups, no API dependency — turns out to be the right call for travel data. Here's why, and how it works.\n\n## Why Static Over API\n\nVisa requirements don't change often. A country might update its visa-on-arrival list two or three times a year. An API would add latency, require authentication, and create a failure mode (network unavailable, API down, rate-limited) in a context where the user is typically trying to quickly check something before booking a flight.\n\nMore practically: there's no reliable free public API for visa requirements. The data sources are government websites and reference databases. Scraping or licensing these for a real-time API isn't worth it for a tool whose value is speed and simplicity.\n\nA local JSON file loaded at extension startup sidesteps all of this.\n\n## Data Structure\n\nThe core dataset is a JSON object keyed by two-letter ISO passport code, then by two-letter destination code:\n\n```\ntype VisaStatus =\n  | 'visa_free'\n  | 'visa_on_arrival'\n  | 'e_visa'\n  | 'visa_required'\n  | 'not_admitted';\n\ninterface EntryRequirement {\n  status: VisaStatus;\n  maxStay?: number;        // days, undefined if no limit\n  notes?: string;\n}\n\ntype VisaMatrix = Record<string, Record<string, EntryRequirement>>;\n```\n\nA lookup is just two array accesses:\n\n```\nfunction lookup(matrix: VisaMatrix, passport: string, destination: string): EntryRequirement | null {\n  return matrix[passport]?.[destination] ?? null;\n}\n```\n\nThe matrix itself compresses well: `visa_free`\n\nand `visa_required`\n\ncover the majority of combinations, so the JSON has a lot of repeated structure. Gzipped, the full dataset is under 30KB.\n\n## Bundling with WXT\n\nThe matrix lives in `public/visa-matrix.json`\n\n. WXT (the extension framework) copies the `public/`\n\ndirectory to the output root verbatim. The background service worker loads it once on install and caches the result:\n\n``` js\nlet cachedMatrix: VisaMatrix | null = null;\n\nasync function getMatrix(): Promise<VisaMatrix> {\n  if (cachedMatrix) return cachedMatrix;\n  const url = chrome.runtime.getURL('visa-matrix.json');\n  const resp = await fetch(url);\n  cachedMatrix = await resp.json();\n  return cachedMatrix;\n}\n```\n\n`chrome.runtime.getURL`\n\nconverts the relative path to the extension's internal `chrome-extension://`\n\nURL. This is the standard pattern for accessing bundled assets from a service worker — it works in MV3 without any special permissions.\n\n## Content Script Injection on Google Flights\n\nThe lookup popup works fine on its own, but the more useful feature is automatic injection on Google Flights. When a user searches for a flight and the page shows a destination, EntryCheck's content script detects the destination, looks up the requirements for the user's saved passport, and injects a badge next to the search results.\n\nThe content script reads the current destination from the URL parameters and page DOM, calls the background for a lookup via `chrome.runtime.sendMessage`\n\n, and renders a small badge component inline.\n\n```\nchrome.runtime.sendMessage(\n  { type: 'VISA_LOOKUP', passport: savedPassport, destination: detected },\n  (response: EntryRequirement | null) => {\n    if (response) renderBadge(response);\n  }\n);\n```\n\nThe background receives this, calls `getMatrix()`\n\n, and returns the result. Because the matrix is in memory after the first load, the response is synchronous from the content script's perspective.\n\n## The Maintenance Problem\n\nStatic data has one obvious downside: it goes stale. My current approach is to update the JSON file with each extension version bump and push it as a normal CWS update. This is manual but tractable — visa requirements change infrequently enough that quarterly checks cover 95% of changes.\n\nFor something that updates more frequently (exchange rates, business hours), this model breaks down and an API makes more sense. Visa requirements are the rare case where static actually wins.\n\n🔗 **EntryCheck on Chrome Web Store**: [Install](https://chromewebstore.google.com/detail/gfbdmeieifamgheecbmdpadniofodkfa)", "url": "https://wpnews.pro/news/190-countries-zero-api-calls-shipping-static-data-in-a-chrome-extension", "canonical_source": "https://dev.to/ktg0215/190-countries-zero-api-calls-shipping-static-data-in-a-chrome-extension-597i", "published_at": "2026-05-23 15:01:23+00:00", "updated_at": "2026-05-23 15:33:55.632688+00:00", "lang": "en", "topics": ["developer-tools", "data", "products"], "entities": ["EntryCheck", "Chrome"], "alternates": {"html": "https://wpnews.pro/news/190-countries-zero-api-calls-shipping-static-data-in-a-chrome-extension", "markdown": "https://wpnews.pro/news/190-countries-zero-api-calls-shipping-static-data-in-a-chrome-extension.md", "text": "https://wpnews.pro/news/190-countries-zero-api-calls-shipping-static-data-in-a-chrome-extension.txt", "jsonld": "https://wpnews.pro/news/190-countries-zero-api-calls-shipping-static-data-in-a-chrome-extension.jsonld"}}