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.

The Pokémon Showdown teambuilder is a full-featured team editor that lives inside the panel system as a PSRoom subclass. It coordinates between three layers: the TeambuilderRoom model (which tracks UI state like the selected folder and export mode), the PSTeams model on PS.teams (which owns the canonical list of teams in memory and in localStorage), and the Teams utility object from battle-teams.ts (which handles pack/unpack serialisation).

TeambuilderRoom

TeambuilderRoom extends PSRoom and is defined in panel-teambuilder.tsx. It is not exported — it is registered with the panel system as the handler for the 'teambuilder' room type.
class TeambuilderRoom extends PSRoom {
  readonly DEFAULT_FORMAT = Dex.modid;

  /**
   * Controls which teams are visible in the team list:
   * - ''               — show all teams
   * - 'gen[N][ID]'     — format folder (e.g. 'gen9randombattle')
   * - 'gen[N]'         — uncategorised gen folder
   * - '[ID]/'          — named folder (e.g. 'Tournament/')
   * - '/'              — teams not assigned to any folder
   */
  curFolder: string = '';
  curFolderKeep: string = '';
  searchTerms: string[] = [];

  exportMode: boolean | 'partial' = false;
  exportCode: string | null = null;
}

Client commands

TeambuilderRoom registers client commands that are triggered by user actions. These strings are also used in PS.room.send() calls within the UI.
CommandExampleEffect
newteam/newteamCreates a new team and opens the editor
newteam box/newteam boxCreates a Pokémon storage box instead of a team
deleteteam <key>/deleteteam pikachu-teamDeletes the team identified by key
undeleteteam/undeleteteamRestores the last deleted team

Export mode

setExportMode(exportMode: boolean): void {
  // 'partial' when a search filter or folder is active
  const partial = this.searchTerms.length || this.curFolder ? 'partial' : true;
  const newExportMode = exportMode ? partial : false;
  this.exportMode = newExportMode;
  this.update();
}
exportMode is false (edit mode), true (show all teams in a textarea), or 'partial' (show only the currently filtered teams). exportCode holds the generated packed text.

The curFolder system in depth

Folders in PS are not real filesystem directories — they are a naming convention stored on each Team object. The curFolder string on TeambuilderRoom tells the UI which virtual directory to display:

'' (empty string)

All teams. No filtering — every team and box is shown.

'gen9randombattle'

Format folder. Shows teams whose format matches gen9randombattle.

'Tournament/'

Named folder. Shows teams whose folder property equals 'Tournament'.

'/'

Unfoldered. Shows teams with an empty folder property.

PSTeams: the team list model

PS.teams is an instance of PSTeams, a PSStreamModel<'team' | 'format'>. It streams 'team' whenever the list changes and 'format' when the format metadata changes.
class PSTeams extends PSStreamModel<'team' | 'format'> {
  list: Team[];
  byKey: { [key: string]: Team | undefined };
  deletedTeams: [Team, number][];
  usesLocalLadder: boolean;

  push(team: Team): void;
  unshift(team: Team): void;
  delete(team: Team): void;
  undelete(): void;
  spliceIn(index: number, teams: Team[]): void;

  packAll(teams: Team[]): string;
  unpackAll(buffer: string | null): void;
  save(): void;
}

Team interface

interface Team {
  name: string;
  format: ID;       // e.g. 'gen9randombattle'
  folder: string;   // e.g. 'Tournament' or ''
  packedTeam: string;
  isBox: boolean;
  key: string;      // unique key for byKey lookup
  teamid?: number;  // server-assigned ID (for uploaded teams)
}

Adding and looking up teams

// Add a new team to the end of the list
PS.teams.push({
  name: 'My Rain Team',
  format: 'gen9ou' as ID,
  folder: 'Competitive',
  packedTeam: 'Pelipper||choicescarf|drizzle|hurricane,scald,...',
  isBox: false,
  key: '',  // assigned by push()
});

// Look up a team by its generated key
const team = PS.teams.byKey['myrainteam'];

// Save all teams to localStorage
PS.teams.save();
push() and unshift() automatically assign a unique key (derived from toID(team.name), with a numeric suffix if there’s a collision). Always use these methods rather than mutating PS.teams.list directly.

Teams: serialisation utilities

The Teams object from battle-teams.ts provides the canonical pack/unpack format used for localStorage, clipboard export, and the PS team paste feature.

Teams.PokemonSet

export interface PokemonSet extends Partial<FullPokemonSet> {
  species: string;
  moves: string[];
}

export interface FullPokemonSet {
  name: string;
  species: string;
  item?: string;
  ability?: string;
  moves: string[];
  nature?: Dex.NatureName;
  gender?: string;
  evs: Partial<Dex.StatsTable>;
  ivs: Dex.StatsTable;
  level: number;
  shiny: boolean;
  happiness: number;
  pokeball: string;
  hpType?: string;
  dynamaxLevel?: number;
  gigantamax?: boolean;
  teraType?: string;
}

Teams.pack and Teams.unpack

// Serialize an array of PokemonSet into a pipe/bracket string
Teams.pack(team: Teams.PokemonSet[] | null): string;

// Deserialize a packed string back into PokemonSet[]
Teams.unpack(buf: string): Teams.PokemonSet[];

Packed team format

A packed team is a ]-delimited sequence of Pokémon, each represented as a |-delimited field string:
Pelipper||choicescarf|drizzle|hurricane,scald,focusblast,roost|Modest|,,,252,4,252|||||
]
Barraskewda||choiceband|swiftswim|liquidation,flip-turn,crunch,closecom|Jolly|,252,,,,252|||||
Fields in order: name | species | item | ability | moves | nature | evs | gender | ivs | shiny | level | happiness [, hpType, pokeball, gigantamax, dynamaxLevel, teraType]
const sets: Teams.PokemonSet[] = [
  {
    species: 'Pelipper',
    item: 'Choice Scarf',
    ability: 'Drizzle',
    moves: ['Hurricane', 'Scald', 'Focus Blast', 'Roost'],
    nature: 'Modest',
    evs: { spa: 252, spe: 252, spd: 4 },
    ivs: { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 },
    level: 100,
    shiny: false,
    happiness: 255,
  },
];

const packed = Teams.pack(sets);
// "Pelipper||choicescarf|drizzle|hurricane,scald,focusblast,roost|Modest|,,,252,4,252|||||"

packAll / unpackAll (PSTeams)

PSTeams.packAll() wraps each team’s metadata (format, folder, name, box flag) around the per-set packed string:
[teamid][format-box]folder/Team Name|<packed sets>
// e.g. a non-box team in format gen9ou, folder "Competitive":
"[gen9ou]Competitive/My Rain Team|Pelipper||choicescarf|..."

// a box with no format:
"[-box]My Storage|Eevee||..."
unpackAll() reads this format line-by-line from the showdown_teams localStorage key and reconstructs the full Team[] list on page load.

Practical workflow: creating and exporting a team

1

Create a new team

// Equivalent to the user clicking 'New Team' in the UI
PS.room.send('newteam');
// → TeambuilderRoom creates a blank Team via PS.teams.push()
2

Edit Pokémon sets

The team editor panel populates team.packedTeam as the user fills in each Pokémon’s species, moves, item, and stats.
3

Save to localStorage

PS.teams.save();
// → localStorage.setItem('showdown_teams', PS.teams.packAll(PS.teams.list))
4

Export for sharing

// Toggle export mode in the teambuilder
PS.room.send('backup');
// → exportMode is set and exportCode is populated with the human-readable
// Showdown paste format. Users can copy it to the clipboard and share on Smogon forums.

Build docs developers (and LLMs) love