Chrome Extension Platform Detection — Best Practices
7 min readPlatform Detection
Overview
Chrome extensions run across Windows, macOS, Linux, and ChromeOS — each with different keyboard shortcuts, file paths, and user expectations. Detecting the platform and system context lets you adapt behavior, UI, and shortcuts for each environment.
Platform Info via chrome.runtime.getPlatformInfo()
The chrome.runtime.getPlatformInfo() API returns the OS, architecture, and platform type:
// utils/platform.ts
export interface PlatformInfo {
os: "mac" | "win" | "android" | "cros" | "linux" | "openbsd";
arch: "arm" | "x86-32" | "x86-64" | "arm64";
nacl_arch: "arm" | "x86-32" | "x86-64";
}
export async function getPlatformInfo(): Promise<PlatformInfo> {
return chrome.runtime.getPlatformInfo();
}
// Usage
const platform = await getPlatformInfo();
console.log(`Running on ${platform.os}/${platform.arch}`);
This runs in any extension context — popup, background, options page.
Chrome Version Detection
Detect the Chrome version to gate features or warn users:
// utils/chrome-version.ts
export function getChromeVersion(): number {
const match = navigator.userAgent.match(/Chrome\/(\d+)/);
return match ? parseInt(match[1], 10) : 0;
}
export function isChromeVersionAtLeast(minVersion: number): boolean {
return getChromeVersion() >= minVersion;
}
// Usage: Gate MV3-only features
if (isChromeVersionAtLeast(110)) {
// Use side panel API (MV3.9+)
}
Environment Detection: Dev vs Production
Distinguish between development and production contexts:
// utils/environment.ts
export function isDevelopment(): boolean {
return (
chrome.runtime.id?.includes("dev") ||
!chrome.runtime.id?.match(/^[a-hjkmnp-z]{32}$/) ||
location.hostname === "localhost"
);
}
export async function getExtensionId(): Promise<string> {
return chrome.runtime.id;
}
Development extensions have temporary IDs; production extensions have 32-character IDs.
Display and Window Info
Get display dimensions for positioning popups or side panels:
// utils/display.ts
export async function getPrimaryDisplay(): Promise<chrome.system.display.DisplayInfo> {
const displays = await chrome.system.display.getInfo();
return displays.find((d) => d.isPrimary) ?? displays[0];
}
export async function getWorkArea(): Promise<{ width: number; height: number }> {
const display = await getPrimaryDisplay();
return {
width: display.workArea.width,
height: display.workArea.height,
};
}
Color Scheme and Reduced Motion
Respect user accessibility preferences:
// utils/preferences.ts
export function getColorScheme(): "light" | "dark" {
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
return "dark";
}
return "light";
}
export function getReducedMotion(): boolean {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
// Apply theme
document.documentElement.setAttribute("data-theme", getColorScheme());
Language and Locale
Detect user language for i18n:
// utils/locale.ts
export function getLanguage(): string {
return navigator.language || "en";
}
export function getLanguages(): string[] {
return navigator.languages || [navigator.language || "en"];
}
// Usage: Load the right locale file
const lang = getLanguage().split("-")[0]; // "en-US" → "en"
Network Type Detection
// utils/network.ts
export function getNetworkType(): "online" | "offline" | "slow-2g" | "2g" | "3g" | "4g" {
const connection = (navigator as any).connection;
if (!navigator.onLine) return "offline";
if (connection) {
return connection.effectiveType || "online";
}
return "online";
}
Platform Utility: Unified Helper
Combine all detection into one utility:
// utils/system.ts
export interface SystemContext {
platform: PlatformInfo;
version: number;
isDev: boolean;
colorScheme: "light" | "dark";
reducedMotion: boolean;
language: string;
network: "online" | "offline";
}
export async function getSystemContext(): Promise<SystemContext> {
return {
platform: await getPlatformInfo(),
version: getChromeVersion(),
isDev: isDevelopment(),
colorScheme: getColorScheme(),
reducedMotion: getReducedMotion(),
language: getLanguage(),
network: getNetworkType(),
};
}
Pattern: Adaptive Shortcuts
Different platforms use different modifier keys:
// utils/shortcuts.ts
export function getModifierKey(): string {
const platform = getPlatformInfo(); // sync in content scripts
return platform.os === "mac" ? "Command" : "Ctrl";
}
export function formatShortcut(key: string): string {
const mod = getModifierKey();
return `${mod}+${key.toUpperCase()}`;
}
In the extension popup, show “Press Ctrl+S on Windows, Command+S on macOS”.
Pattern: Platform-Aware UI
Adjust UI based on platform conventions:
// ui/platform-aware.ts
export function applyPlatformStyles(): void {
const platformInfo = getPlatformInfo(); // Call from sync context
const isMac = platformInfo.os === "mac";
document.body.classList.toggle("is-mac", isMac);
document.body.classList.toggle("is-windows", !isMac);
// macOS uses Cmd instead of Ctrl in tooltips
const tooltips = document.querySelectorAll("[data-ctrl-label]");
tooltips.forEach((el) => {
const label = el.getAttribute("data-ctrl-label");
el.setAttribute("title", label?.replace("Ctrl", isMac ? "Cmd" : "Ctrl") || "");
});
}
See Also
- [System API Reference][api-reference_system-api] — Full chrome.system.* APIs
- Cross-Browser Compatibility — Feature detection and polyfills
- Accessibility Guide — Color scheme, reduced motion, keyboard nav
-e —
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.