Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/smogon/pokemon-showdown-client/llms.txt

Use this file to discover all available pages before exploring further.

Pokémon Showdown persists nearly all user-controllable settings through a single model called PSPrefs. Unlike the server-side Config object, preferences are stored entirely in the browser’s localStorage under the key showdown_prefs and are shared across every room and server session automatically. Understanding PSPrefs is essential for any client feature that reads user intent — from theming to battle animations to chat formatting.

Architecture: PSPrefs extends PSStreamModel

PSPrefs is defined in play.pokemonshowdown.com/src/client-main.ts and inherits from PSStreamModel<string | null> (defined in client-core.ts). The stream model pattern means:
  • Every call to PS.prefs.set(key, value) internally calls this.update(key), which broadcasts the changed key to all active subscribers.
  • Subscribers that attach after the initial load will receive any buffered updates from the backlog before the backlog is cleared.
  • Passing null as the value to set() resets the preference to its compiled-in default.
// Simplified view of the class shape
class PSPrefs extends PSStreamModel<string | null> {
  // ... all preference fields with defaults ...

  set<T extends keyof PSPrefs>(key: T, value: PSPrefs[T] | null): void;
  load(newPrefs: object, noSave?: boolean): void;
  save(): void;
}
The singleton instance is exposed on the global PS object as PS.prefs.

Storage Engine

On startup, the constructor checks for window.localStorage. If available, it reads and parses showdown_prefs (a JSON blob) and calls load(). The storageEngine field reflects what was found:
ValueMeaning
'localStorage'Standard browser localStorage is available and in use
'iframeLocalStorage'Fallback for environments where direct localStorage is blocked
''No storage available; preferences exist only in memory for the current session
// From the PSPrefs constructor
if (window.localStorage) {
  this.storageEngine = 'localStorage';
  this.load(JSON.parse(localStorage.getItem('showdown_prefs')!) || {}, true);
}

Complete Preference Reference

Theme & Display

Controls the overall color scheme of the client. 'system' reads the OS preference via window.matchMedia('(prefers-color-scheme: dark)'). If the browser does not support the media query, 'system' is silently demoted to 'light' at load time via fixPrefs().
theme: 'light' | 'dark' | 'system' = 'light';
Controls the panel layout. false = two side-by-side panels. true = single panel. 'vertical' = stacked vertical layout, used automatically on narrow screens (under 800 px client width).
onepanel: boolean | 'vertical' = false;
April Fools’ Day mode. true enables full AFD text and sprites; 'sprites' enables only AFD sprites; false permanently disables it. undefined (the compiled default) defers to the server’s Config.server.afd flag. Controlled via the /afd chat command.
afd: boolean | 'sprites' = undefined!;

Graphics & Animation

Disables animated GIFs while keeping CSS animations intact. Originally a workaround for a Chrome 64 bug that caused GIF-based battle sprites to freeze. null = auto-detect Chrome 64 and enable the workaround only if needed. true = always disable GIFs. false = always enable GIFs.
nogif: boolean | null = null;
Disables all battle animations. null = animations enabled. true = disable all animations.
noanim: boolean | null = null;
Use static Black & White generation sprites instead of the modern animated sprites. null = use animated sprites. true = use BW sprites.
bwgfx: boolean | null = null;
Hide past-generation Pokémon sprites and only show the current-generation artwork. null = show all generations.
nopastgens: boolean | null = null;

Chat Preferences

Show private messages inline within chat rooms rather than as separate PM popups.
inchatpm: boolean | null = null;
Suppress highlight notifications when your own username appears in chat.
noselfhighlight: boolean | null = null;
Automatically close desktop notifications after 5 seconds. When null (the default), desktop notifications remain open until dismissed manually. When true, a setTimeout closes each notification after 5000 ms.
temporarynotifications: boolean | null = null;
Show a confirmation popup when navigating away from a connected chat room to prevent accidental disconnection.
leavePopupRoom: boolean | null = null;
Prompt the user before refreshing the page while in a battle or active chat session.
refreshprompt: boolean | null = null;
Fine-grained chat rendering controls. The default object is:
chatformatting: Record<string, boolean> = {
  hidegreentext:  false,  // hide ">greentext" styling
  hideme:         false,  // hide /me actions
  hidespoiler:    false,  // hide spoiler tags
  hidelinks:      false,  // hide hyperlinks
  hideinterstice: true,   // hide interstitial warning for external links
};
Controls timestamp display in chat rooms and PMs. Each sub-key can be 'minutes', 'seconds', or undefined (hidden).
timestamps: { chatrooms?: TimestampOptions, pms?: TimestampOptions } = {};
// type TimestampOptions = 'minutes' | 'seconds' | undefined;
Controls display of “User joined” / “User left” messages, keyed by serverid then roomid. Uses 1 and 0 instead of true/false for compact JSON storage.
showjoins: { [serverid: string]: { [roomid: string]: 1 | 0 } } | null = null;
A table of ignored user IDs. Uses 1 to indicate ignored and 0 to un-ignore while keeping the entry. Managed via /ignore, /unignore, and /ignorelist commands.
ignore: { [userid: string]: 1 | 0 } | null = null;
Custom highlight phrases keyed by server/room scope. Managed via the /highlight command family.
highlights: Record<string, string[]> | null = null;
null = notify only when you are in the room. 'notify' = notify for all tournaments. 'hide' = suppress all tournament UI.
tournaments: 'hide' | 'notify' | null = null;
Comma-separated room lists to auto-join per server. The Main server uses a plain string; multi-server setups key by server ID.
autojoin: { [serverid: string]: string } | string | null = null;

Battle Preferences

Ignore custom nicknames in battles and display species names instead.
ignorenicks: boolean | null = null;
Ignore “paying respects” messages during battles.
ignorespects: boolean | null = null;
Mute all chat messages from your opponent during a battle.
ignoreopp: boolean | null = null;
Automatically start the battle timer when a game begins.
autotimer: boolean | null = null;
Automatically enable Hardcore mode (timer + no extensions) for all battles.
autohardcore: boolean | null = null;
When joining a battle in progress, start replaying from the beginning rather than the current state.
spectatefromstart: boolean | null = null;
Open new battle rooms in the right panel instead of the left.
rightpanelbattles: boolean | null = null;
Block spectators from joining your battles by default.
disallowspectators: boolean | null = null;
Formats the user has starred/bookmarked, keyed by format ID. Managed via /star and /unstar commands.
starredformats: { [formatid: string]: true | undefined } | null = null;
Show ongoing battles in the room list. Unlike most boolean preferences, this defaults to true rather than null.
showbattles = true;
Receive debug messages from battle events in the chat log. Enabled via the /showdebug command; disabled via /hidedebug.
showdebug: boolean | null = null;

Audio Preferences

Master mute switch for all sounds.
mute = false;
Volume for battle sound effects (0–100).
effectvolume = 50;
Volume for background music (0–100).
musicvolume = 50;
Volume for notification sounds (0–100).
notifvolume = 50;

Account & Server Settings

The user’s selected avatar ID. Persisted locally and synced to the server via the /avatar command.
avatar: string | null = null;
Server-mirrored account settings. These are also stored locally so the client can apply them optimistically before the server responds.
serversettings: {
  blockPMs?: boolean | string,
  blockChallenges?: boolean,
  language?: string,
} = {};
Controls whether replays uploaded from this client are private by default.
uploadprivacy = false;
Tracks the timestamp of the last seen message per server and room, used for unread-message indicators.
logtimes: { [serverid: ID]: { [roomid: RoomID]: number } } | null = null;

Reading a Preference

Access any preference directly as a property on the singleton PS.prefs:
// Reading the current theme
const currentTheme = PS.prefs.theme;
// => 'light' | 'dark' | 'system'

// Checking if GIFs are disabled
if (PS.prefs.nogif) {
  console.log('GIFs are currently disabled');
}

// Reading a nested preference
const pmTimestamps = PS.prefs.timestamps.pms;
// => 'minutes' | 'seconds' | undefined

Setting a Preference

Use PS.prefs.set(key, value) to change a preference. This writes to storage, updates the in-memory property, broadcasts the changed key to all subscribers, and saves to localStorage — all in one call.
// Set the theme to dark mode
PS.prefs.set('theme', 'dark');

// Disable battle animations
PS.prefs.set('noanim', true);

// Reset a preference to its compiled default (pass null)
PS.prefs.set('noanim', null);

// Update a nested object preference
PS.prefs.set('timestamps', { chatrooms: 'minutes', pms: 'seconds' });
Do not mutate a preference property directly (e.g. PS.prefs.theme = 'dark'). Direct assignment bypasses localStorage persistence and does not notify subscribers. Always use PS.prefs.set().

Subscribing to Preference Changes

Because PSPrefs extends PSStreamModel<string | null>, you can reactively listen for changes using subscribe(). The listener receives the key of the preference that changed, or null if the entire preference object was reloaded.
// Subscribe to all preference changes
const subscription = PS.prefs.subscribe((changedKey) => {
  if (changedKey === 'theme' || changedKey === null) {
    applyTheme(PS.prefs.theme);
  }
});

// Later, when you no longer need the subscription:
subscription.unsubscribe();
You can also use subscribeAndRun() to fire the listener immediately with the current state, which is useful for initializing UI components:
PS.prefs.subscribeAndRun((changedKey) => {
  // Runs once immediately with changedKey = null,
  // then on every future change with the changed key name
  renderOptionsPanel();
});
Preferences are global across all rooms and servers within a single browser session. A change made in one room tab is immediately visible in all other tabs on the same page. Preferences also persist across browser restarts because they are saved to localStorage.

Example: A Custom Preference Watcher

// Watch only for theme changes and apply a CSS class
PS.prefs.subscribe((key) => {
  if (key !== 'theme' && key !== null) return;

  const root = document.documentElement;
  root.classList.remove('theme-light', 'theme-dark', 'theme-system');
  root.classList.add(`theme-${PS.prefs.theme}`);
});
The PSPrefsDefaults object (a module-level plain object in client-main.ts) holds a snapshot of every default value captured in the PSPrefs constructor. When you call set(key, null), the client restores the value from PSPrefsDefaults[key] rather than re-instantiating PSPrefs. This means defaults are determined at class definition time, not at reset time.

Build docs developers (and LLMs) love