Type Definitions
Complete type reference for Chrome Extension Starter's shared types and interfaces.
Core Types
Message Types
Message<T, P>
Generic message structure for extension communication.
export type Message<T extends string = string, P = unknown> = {
type: T;
payload?: P;
};
Type Parameters:
T— Message type string (default:string)P— Payload type (default:unknown)
Properties:
type: T— Message identifierpayload?: P— Optional message data
Example:
const message: Message<'CHANGE_BG', { color: string }> = {
type: 'CHANGE_BG',
payload: { color: '#0ea5e9' }
};
MessageMap
Type-safe message map defining all extension messages.
export type MessageMap = MessageMapOf<typeof MSG, typeof MESSAGE_SPEC>;
Built from:
MSGenum (message types)MESSAGE_SPEC(request/response contracts)
Example:
type MessageMap = {
CHANGE_BG: {
req: { color: string };
res: { ok: boolean };
};
GET_USER: {
req: { userId: number };
res: { name: string; email: string };
};
};
MessageMapOf<T, O>
Utility type for merging message types with specifications.
export type MessageMapOf<
T extends Record<string, string>,
O extends Partial<{ [K in keyof T]: { req?: any; res?: any } }>
> = {
[K in keyof T]: O[K] extends object ? O[K] : { req?: unknown; res?: unknown };
};
Type Parameters:
T— Message type enumO— Message specifications object
ErrorResponse
Structured error response for messaging.
export interface ErrorResponse {
error: {
message: string;
code?: string;
details?: unknown;
};
}
Properties:
error.message: string— Error messageerror.code?: string— Optional error codeerror.details?: unknown— Additional error context
Example:
const errorResponse: ErrorResponse = {
error: {
message: 'User not found',
code: 'USER_NOT_FOUND',
details: { userId: 123 }
}
};
Storage Types
StorageSchema
Defines the complete storage schema for all storage areas.
export interface StorageSchema {
local: {
darkMode: boolean;
username: string;
};
sync: {
settings: unknown;
version: string;
};
managed: {
orgEnabled: boolean;
allowedHosts: string[];
};
session: {
lastVisited: string | null;
tempToken: string | null;
};
}
Storage Areas:
local — Local storage (large quota, no sync)
darkMode: boolean— Dark mode preferenceusername: string— User's name
sync — Synced storage (across devices)
settings: unknown— User settings objectversion: string— Extension version
managed — Managed storage (read-only, enterprise)
orgEnabled: boolean— Organization feature flagallowedHosts: string[]— Whitelisted hosts
session — Session storage (ephemeral)
lastVisited: string | null— Last visited URLtempToken: string | null— Temporary auth token
Customization:
// Extend schema in types.d.ts
export interface StorageSchema {
local: {
darkMode: boolean;
username: string;
// Add your keys
recentFiles: string[];
cacheExpiry: number;
};
sync: {
settings: MySettings; // Replace 'unknown'
version: string;
premium: boolean; // Add feature flags
};
}
Migration Types
Version
Semantic version string.
export type Version = string;
Format: "x.y.z" (e.g., "1.2.3")
Example:
const version: Version = '1.3.0';
Migration
Migration definition interface.
export interface Migration {
version: Version;
description: string;
migrate: MigrationFn;
}
Properties:
version: Version— Target version for this migrationdescription: string— Human-readable descriptionmigrate: MigrationFn— Migration function
Example:
const migration: Migration = {
version: '1.1.0',
description: 'Migrate settings to new format',
migrate: async (ctx) => {
const oldSettings = await ctx.getStorage('sync', 'oldSettings');
return {
sync: { settings: transform(oldSettings) }
};
}
};
MigrationFn
Migration function type.
export type MigrationFn = (
context: MigrationContext
) => Promise<void | MigrationResult>;
Parameters:
context: MigrationContext— Migration context object
Returns: Promise<void | MigrationResult>
void— No storage updatesMigrationResult— Storage updates to apply
MigrationContext
Context provided to migration functions.
export interface MigrationContext {
currentVersion: Version;
storedVersion: Version | null;
getStorage: <T = unknown>(
area: 'sync' | 'local',
key: string
) => Promise<T | undefined>;
getAllStorage: (
area: 'sync' | 'local'
) => Promise<Record<string, unknown>>;
}
Properties:
currentVersion: Version— Current extension versionstoredVersion: Version | null— Previously stored versiongetStorage<T>()— Get single storage valuegetAllStorage()— Get all values from area
Example:
const migrate: MigrationFn = async (ctx) => {
console.log('Migrating from', ctx.storedVersion, 'to', ctx.currentVersion);
const oldData = await ctx.getStorage('sync', 'oldKey');
const allLocal = await ctx.getAllStorage('local');
return {
sync: { newKey: transform(oldData) }
};
};
MigrationResult
Result object defining storage updates.
export interface MigrationResult {
sync?: Record<string, any>;
local?: Record<string, any>;
}
Properties:
sync?: Record<string, any>— Updates for sync storagelocal?: Record<string, any>— Updates for local storage
Example:
const result: MigrationResult = {
sync: {
settings: newSettings,
version: '1.1.0'
},
local: {
cache: newCache
}
};
Utility Types
DeepPartial<T>
Recursively makes all properties optional.
type DeepPartial<T> = T extends Function
? T
: T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T | undefined;
Usage:
interface Settings {
theme: {
mode: 'light' | 'dark';
accent: string;
};
notifications: boolean;
}
type PartialSettings = DeepPartial<Settings>;
// {
// theme?: {
// mode?: 'light' | 'dark';
// accent?: string;
// };
// notifications?: boolean;
// }
StrictPartial<T>
Makes properties optional but not undefined.
type StrictPartial<T> = { [K in keyof T]?: T[K] };
Usage:
interface User {
id: number;
name: string;
}
type PartialUser = StrictPartial<User>;
// { id?: number; name?: string }
ValueOf<T, K>
Extract value type from object property.
type ValueOf<T, K extends keyof T> = T[K];
Usage:
interface StorageSchema {
local: { theme: string };
sync: { version: string };
}
type LocalTheme = ValueOf<StorageSchema['local'], 'theme'>;
// string
Enum Types
MSG
Message type enumeration.
export enum MSG {
CHANGE_BG = 'CHANGE_BG'
}
Usage:
import { MSG } from '@/shared/constants';
await bus.sendToActive(MSG.CHANGE_BG, { color: 'red' });
Constant Types
FLAGS
Feature flags object.
export const FLAGS = {
ENABLE_OVERLAY: true
} as const;
export type Flags = typeof FLAGS;
Type:
type Flags = {
readonly ENABLE_OVERLAY: true;
}
ALARMS
Alarm identifiers.
export const ALARMS = {
POLL: 'poll',
DAILY_CLEANUP: 'daily_cleanup'
} as const;
export type AlarmName = typeof ALARMS[keyof typeof ALARMS];
Type:
type AlarmName = 'poll' | 'daily_cleanup';
RESTRICTED
Restricted URL patterns.
export const RESTRICTED = {
schemes: ['chrome', 'chrome-extension', 'chrome-untrusted', 'devtools', 'edge', 'about'],
hosts: [
/^(?:https?:\/\/)?chrome\.google\.com\/webstore\/?/i,
/^(?:https?:\/\/)?microsoftedge\.microsoft\.com\/addons\/?/i
]
} as const;
export type RestrictedScheme = typeof RESTRICTED.schemes[number];
Type:
type RestrictedScheme = 'chrome' | 'chrome-extension' | 'chrome-untrusted' | 'devtools' | 'edge' | 'about';
Type Guards
Example Type Guards
// Check if value is ErrorResponse
export function isErrorResponse(value: any): value is ErrorResponse {
return (
value &&
typeof value === 'object' &&
'error' in value &&
typeof value.error.message === 'string'
);
}
// Check if message has payload
export function hasPayload<T extends string, P>(
msg: Message<T, P>
): msg is Message<T, P> & { payload: P } {
return msg.payload !== undefined;
}
// Check if version is valid
export function isValidVersion(version: string): version is Version {
return /^\d+\.\d+\.\d+$/.test(version);
}
Usage:
const response = await fetchData();
if (isErrorResponse(response)) {
console.error(response.error.message);
} else {
console.log('Success:', response);
}
Extending Types
Custom Message Types
export enum MSG {
CHANGE_BG = 'CHANGE_BG',
GET_USER = 'GET_USER',
// Add your message types
NOTIFY = 'NOTIFY',
SAVE_SETTINGS = 'SAVE_SETTINGS'
}
export const MESSAGE_SPEC = {
[MSG.CHANGE_BG]: {
req: {} as { color: string },
res: {} as { ok: boolean }
},
// Add specifications
[MSG.NOTIFY]: {
req: {} as { title: string; message: string },
res: {} as { shown: boolean }
},
[MSG.SAVE_SETTINGS]: {
req: {} as { settings: Record<string, any> },
res: {} as { saved: boolean }
}
} as const;
Custom Storage Schema
export interface StorageSchema {
local: {
darkMode: boolean;
username: string;
// Add your local keys
favorites: string[];
lastSync: number;
};
sync: {
settings: MyAppSettings; // Custom type
version: string;
// Add your sync keys
isPremium: boolean;
};
managed: {
orgEnabled: boolean;
allowedHosts: string[];
// Add your managed keys
maxUploadSize: number;
};
session: {
lastVisited: string | null;
tempToken: string | null;
// Add your session keys
activeWorkspace: string | null;
};
}
// Define custom settings type
export interface MyAppSettings {
theme: 'light' | 'dark' | 'auto';
language: 'en' | 'ja' | 'zh_TW';
notifications: {
enabled: boolean;
sound: boolean;
frequency: 'instant' | 'hourly' | 'daily';
};
}
Best Practices
- Always define message specs — Add all messages to
MESSAGE_SPEC - Use strict types — Avoid
anyandunknownwhen possible - Document custom types — Add JSDoc comments
- Extend schemas properly — Update
StorageSchemafor new keys - Use type guards — Validate runtime types
- Export types — Make types available across modules
Next Steps
- Explore Messaging API for detailed function signatures
- Check Storage API for storage utilities
- Learn about Core Modules usage