Skip to main content
Pokemon Showdown welcomes contributions from the community. This guide covers everything you need to know to contribute effectively.

Building and Running

1

Start the Server

Run ./pokemon-showdown to start a local server.
Windows users should use node pokemon-showdown instead of ./pokemon-showdown
2

Access the Local Server

If your browser forces HTTPS and you cannot access the local server:
  • Firefox: Disable “DNS over HTTPS”
  • Chrome: Disable “Async DNS resolver” in chrome://flags
3

Run Tests

Execute unit tests with npm testFor specific tests:
npx mocha -g "text"
Or edit the test to use it.only instead of it

Useful Commands

Explore other command-line options:
./pokemon-showdown help

Getting Started

Bug Fixes

Bug fix pull requests are always welcome

Feature Additions

Discuss feature additions at psim.us/development or psim.us/devdiscord first

Ideas Issue

Check out GitHub Issue #2444 for inspiration

Mechanics Bugs

We try to respond to pull requests within a few days. Feel free to bump your PR if it seems forgotten.

Code Standards

Most code standards are enforced by eslint. Run npm test to check for issues.

String Conventions

Pokemon Showdown uses specific quote styles:
// Backticks for interpolation
const moveId = `move-${move.id}`;

// Backticks for HTML/protocol code
const html = `<strong>Fire Blast</strong>`;

// Single quotes for IDs (not displayed to users)
const id = 'fireblast';

// Double quotes for user-facing text
const name = "Fire Blast";

Optionals: null vs undefined vs false

Standard convention: Use null for optionals
// Good
function getUser(id: string): User | null {
  // ...
}

// Legacy code may use undefined or false - convert to null
Simulator-specific: Event handlers may return false | null | undefined
  • false = action failed
  • null = action failed silently (suppress “But it failed!” messages)
  • undefined = action should be ignored
// Thunder Wave hits Ground type
immunityChecker.return(false); // Shows immunity message

// Volt Absorb absorbs Thunder Wave
voltAbsorb.return(null); // No additional failure message

// Water Absorb doesn't interact with Thunder Wave
waterAbsorb.return(undefined); // Continue checking other handlers

Operators: || vs ??

Prefer || for fallback values:
const value = input || defaultValue;
Use ?? only when 0, '', or false should not trigger fallback.

Comment Standards

C1. Don’t Teach JavaScript

Avoid documenting obvious language features:
// Increase counter by 1.
counter++;

C2. Document in Names

Use descriptive variable and function names:
/** move name */
let value = "Stealth Rock";
Create variables to give expressions meaningful names:
// if ten seconds have passed and the user is staff
if (now > then + 10_000 && '~@%'.includes(user.tempGroup)) {

C3. Doc Comments

Use /** */ comments for information that shows up in editor tooltips:
/** null = not accepting connections */
let numConnections: number | null = null;

C4. Explain Complex Code

Comment code that requires domain knowledge:
// Chrome won't update unless you do this
elem.innerHTML = elem.innerHTML;

C5. Jokes Are Welcome

// GET IT? BECAUSE WE DON'T KNOW WHAT SPECIES IT IS???
if (!species) species = 'Unown';

Commit Message Standards

CM1. What, Not How

Describe what the code does, not how:
Change Wonder Guard from onBeforeMove to onTryHit

CM2. Imperative Mood

Start with imperative verbs:
Adding namefilter
Adds namefilter

CM3. Grammar Rules

  • Keep first line under 50 characters
  • Capitalize first letter
  • No period at the end
refactor users to use classes
Refactor Users to use classes.

CM4. Tag Your Changes

Prefix commits with the affected component:
Ban Genesect

CM5. Squashing (Optional)

GitHub now supports squash-merging, so squashing commits in pull requests is optional. However:
  • Commits that fix previous commits in the same PR should be squashed
  • Each commit in a rebase-merge should make sense standalone
Rebase Guide - Never close and recreate a broken PR, ask for help instead

Modern JavaScript/TypeScript

We prefer modern syntax supported by the latest Node LTS:
FeatureStatus
.forEachAvoid - use for...of instead
.reduceUsually prefer for...of for readability
Multiline template stringsAvoid - explicitly use \n
async/awaitPreferred for readability
Getters/setters/ProxyAvoid magic - be explicit
Constant EnumsDon’t use - prefer union types
Default PropertiesUse
// Prefer union types over enums
type Category = 'Physical' | 'Special' | 'Status';

// Prefer for...of over forEach
for (const item of items) {
  // ...
}

Dependencies

We avoid adding NPM dependencies casually:
For functionality that can be implemented in ~30 lines, we write it ourselves in lib/ to avoid dependency issues and optimize for our use case.
We do accept dependencies where they make sense (like SockJS), but evaluate them more critically than most Node projects.

License

All submitted code must be MIT licensed. The GitHub ToS ensures this, but keep it in mind:
  • Server code: MIT license (inherited from your fork)
  • Client code: MIT license (you’ll be asked to confirm on first client PR)

Design Principles

We prioritize usability and accessibility over trendy aesthetics.

Less Text is Better

“1234 battles” > “There are currently 1234 active battles being played”

Buttons Say What They Do

Use “[Delete] [Cancel]” instead of “Delete this file? [Yes] [No]”

Remove Unnecessary Clicks

Replace “Are you sure?” dialogs with “Undo” buttons when possible

Remove Unnecessary Scrolling

Use expand/collapse for large content instead of tiny scroll regions

Affordances Matter

Buttons should look like buttons. Clickable things should look clickable.

Feedback is Important

Show loading states, disable clicked buttons, display error messages

Resources

Development Chat

Join the development discussion

Development Discord

Discord server for contributors

Suggestions Forum

Browse approved suggestions

GitHub Repository

Source code and issue tracker

Build docs developers (and LLMs) love