Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Phaser Admin & Architecture Guide

Generated June 2026 from phaserjs/phaser at 9e67ded

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is the deep technical reference for Phaser 4 — aimed at engineers integrating Phaser into a larger application, architects evaluating it, build engineers shipping production games, and contributors working on the framework itself. It documents the internal architecture, the complete configuration surface, the build and distribution model, security considerations, and the structure of the source tree.

For end-user, gameplay-focused documentation (how to create sprites, handle input, play sounds), see the companion User Guide. This guide assumes you already understand the basics.

Note

This guide documents Phaser 4.1.0 ("Salusa"), a major release built on an entirely new WebGL renderer. Phaser is MIT-licensed and commercially developed by Phaser Studio Inc. The only runtime dependency is eventemitter3.


Table of Contents

  1. Executive Summary
  2. System Architecture
  3. Requirements & Compatibility
  4. Configuration Mastery
  5. Build & Distribution
  6. Integrating Phaser into an App
  7. The Rendering Architecture
  8. Subsystem Reference
  9. Repository Structure
  10. Security Analysis
  11. Performance & Operations
  12. Code Quality & Conventions
  13. Testing Strategy
  14. Known Issues & Tech Debt
  15. Extending Phaser
  16. Contributing
  17. Quick Reference

Executive Summary

What Phaser is. Phaser is a 2D HTML5 game framework that renders with WebGL (primary) or Canvas (fallback) across desktop and mobile browsers. It provides a complete game-development stack: a scene-based architecture, a parallel asset loader, two physics engines (Arcade and Matter.js), animation, tweening, input (keyboard/mouse/touch/gamepad), audio (Web Audio + HTML5), cameras, tilemaps, particles, and a comprehensive math/geometry library.

Why it's chosen.

  • Maturity: Over a decade of active development; tens of thousands of shipped games.
  • Single codebase, many targets: Web, plus native wrapping for iOS/Android/Steam, YouTube Playables, Discord Activities, Twitch overlays.
  • Framework-agnostic: Drops into React, Vue, Angular, Svelte, or vanilla JS/TS.
  • Tiny footprint: 345 KB gzipped for the full minified build.
  • First-class TypeScript: Generated .d.ts definitions cover the entire API.
  • Zero hard runtime dependencies beyond eventemitter3.

Architectural philosophy. Phaser is built around a single Game controller that owns a set of global managers, with Scene instances as the primary unit of composition. The filesystem-of-modules pattern is consistent: each subsystem lives in its own folder under src/, exports a namespace, and is assembled into the top-level Phaser global. Game objects are constructed by mixing component objects into prototypes rather than deep inheritance — a flat, composable design.

The v4 leap. Phaser 4 replaced the entire v3 rendering pipeline with a node-based RenderNode architecture that centralizes WebGL state management, handles context loss gracefully, and is significantly faster. It also unified the FX and Mask systems into a single Filter system, overhauled tinting, and added GPU-accelerated game objects (SpriteGPULayer, TilemapGPULayer) capable of rendering millions of elements.

Attribute Value
Version 4.1.0 ("Salusa")
License MIT
Language JavaScript (ES5-targeted source, built with Webpack)
Runtime dependency eventemitter3 ^5.0.4
Renderers WebGL (primary), Canvas (fallback/deprecated)
Physics Arcade (AABB), Matter.js (rigid-body)
Module formats UMD (dist/phaser.js), ESM (dist/phaser.esm.js)
Source size ~10 M chars across ~3,500 files
Maintainer Phaser Studio Inc.

System Architecture

High-level structure

A running Phaser game is a single Game instance owning global managers, driving one or more Scene instances each frame.

                          new Phaser.Game(config)
                                   │
                    ┌──────────────┴───────────────┐
                    │         Phaser.Game            │
                    │  (the single root controller)  │
                    └──────────────┬─────────────────┘
                                   │ owns global managers
   ┌───────────────┬──────────────┼──────────────┬───────────────┐
   │               │              │              │               │
┌──▼───┐     ┌─────▼─────┐  ┌─────▼─────┐  ┌─────▼─────┐   ┌──────▼──────┐
│Render│     │  Texture  │  │  Sound    │  │  Input    │   │  TimeStep   │
│ -er  │     │  Manager  │  │  Manager  │  │  Manager  │   │ (game loop) │
└──────┘     └───────────┘  └───────────┘  └───────────┘   └──────┬──────┘
                                                                  │ drives
   ┌───────────────┬──────────────┬──────────────┐               │
┌──▼───┐    ┌──────▼──────┐  ┌─────▼─────┐  ┌─────▼─────┐         │
│Anim  │    │   Cache     │  │   Scale   │  │  Plugin   │         │
│ Mgr  │    │  Manager    │  │  Manager  │  │  Manager  │         │
└──────┘    └─────────────┘  └───────────┘  └───────────┘         │
                                                                  │
                    ┌──────────────────────────────┐             │
                    │       SceneManager            │◄────────────┘
                    │  (ordered list of Scenes)     │   step(time, delta)
                    └──────────────┬────────────────┘
                                   │ each scene has its own:
              ┌────────────────────┼─────────────────────┐
        ┌─────▼─────┐       ┌──────▼──────┐        ┌──────▼──────┐
        │   Scene   │       │   Scene     │        │   Scene     │
        │  Systems  │       │  Systems    │        │  Systems    │
        │ add/load/ │       │ cameras/    │        │ input/      │
        │ physics/  │       │ tweens/time │        │ tweens/...  │
        │ tweens... │       └─────────────┘        └─────────────┘
        └─────┬─────┘
              │ display list
        ┌─────▼─────────────────────────────┐
        │  Game Objects (Sprite, Image,     │
        │  Text, Container, Particles, ...) │
        └───────────────────────────────────┘

The two-tier manager model

Phaser separates global managers (owned by Game, shared by all scenes) from scene-local systems (one per scene):

Global (on game.*) Scene-local (on this.* via injection)
renderer, textures, cache add (factory), make (creator)
anims (AnimationManager) load (LoaderPlugin)
sound (SoundManager) cameras (CameraManager)
input (InputManager) input (InputPlugin — scene wrapper)
scene (SceneManager) tweens, time, data
scale (ScaleManager) physics / matter (if configured)
plugins (PluginManager) events, children (display list)
registry (global DataManager) lights
loop (TimeStep) scene (ScenePlugin)

The Scene Injection Map (src/scene/InjectionMap.js) defines which systems are mounted onto a Scene instance and under what property name. This is configurable per scene via the scene config's map property.

Frame execution model

The TimeStep (src/core/TimeStep.js) requests animation frames and calls Game.step(time, delta):

TimeStep (requestAnimationFrame)
   │
   ▼
Game.step(time, delta)
   ├─ emit PRE_STEP                     (input/sound managers update)
   ├─ emit STEP                          (user-land + plugins)
   ├─ SceneManager.update(time, delta)   (each active scene, REVERSE order)
   │     └─ Scene.sys.step → scene.update()
   ├─ emit POST_STEP
   ├─ renderer.preRender()
   ├─ emit PRE_RENDER
   ├─ SceneManager.render(renderer)      (each visible scene, FORWARD order)
   ├─ renderer.postRender()
   └─ emit POST_RENDER

Scenes update in reverse array order (top scene first, for input priority) and render in forward order (top scene last, so it draws on top). This asymmetry is intentional and is how overlay/UI scenes get input priority while appearing on top.

There is a separate headlessStep for the HEADLESS renderer that skips actual rendering but still emits PRE_RENDER/POST_RENDER.

Boot sequence

From Game.boot() (src/core/Game.js):

  1. Constructor parses config into Config, instantiates every global manager.
  2. Waits for DOMContentLoaded, then boot():
    • config.preBoot(game) callback.
    • scale.preBoot().
    • CreateRenderer(game) — picks WebGL/Canvas/Headless, creates the canvas.
    • CreateDOMContainer(game) (if dom.createContainer).
    • DebugHeader(game) — the styled console banner.
    • AddToDOM(canvas, parent).
    • Emits BOOT.
  3. When TextureManager emits READY → texturesReady() emits READY, calls start().
  4. start() begins the TimeStep loop, installs the VisibilityHandler, runs config.postBoot(game).

Requirements & Compatibility

Runtime requirements

  • A modern browser with WebGL (Canvas is a deprecated fallback). WebGL is effectively baseline today.
  • The DOM. Even the HEADLESS renderer requires a DOM environment — Phaser is not designed to run server-side in Node.
  • No build tooling at runtime — the distributed bundles are self-contained.

Development requirements (building from source)

From package.json:

Tool Purpose
Webpack 5 + webpack-cli Bundling (config/webpack.config.js)
Terser plugin Minification
ESLint 10 (+ eslint-plugin-es5) Linting; enforces ES5-compatible source
Vitest 4 Unit testing
jsdom DOM simulation for tests
TypeScript 6 + jsdoc + dts-dom Generating .d.ts type definitions from JSDoc
node-sloc Source line counting

The source is authored to be ES5-compatible (enforced by eslint-plugin-es5) and transpiled/bundled by Webpack. The single runtime dependency is eventemitter3.

Platform targets

Games built with Phaser run on:

  • Desktop browsers (Chrome, Firefox, Safari, Edge).
  • Mobile browsers (iOS Safari, Android Chrome).
  • Native app wrappers (Cordova/Capacitor → iOS/Android, Electron/NW.js → desktop).
  • Steam (via Electron/NW.js).
  • YouTube Playables, Discord Activities, Twitch overlays.

Note

The Loader recognizes file:// and capacitor:// as local schemes by default (loader.localScheme), which matters for Cordova/Capacitor wrapping where assets are served from the app bundle.


Configuration Mastery

The full GameConfig is parsed by src/core/Config.js into a read-only Config instance, available as game.config. Every value below is resolved with the default shown if not provided. Some properties can be set at the top level or nested in a sub-object (e.g. width or scale.width); the nested sub-object takes priority.

Top-level properties

Property Type Default Description
type number Phaser.AUTO (0) Renderer: AUTO, CANVAS, WEBGL, HEADLESS
width number|string 1024 Canvas width (overridden by scale.width)
height number|string 768 Canvas height (overridden by scale.height)
zoom number 1 Canvas zoom multiplier (overridden by scale.zoom)
parent HTMLElement|string|null undefined Parent element/id. undefined = body, null = don't inject
canvas HTMLCanvasElement null Use your own canvas element
context Rendering context null Use your own rendering context
canvasStyle string null CSS applied to the canvas
customEnvironment boolean false Skip feature detection (non-browser). Forbids AUTO
scene SceneType|SceneType[] null Scene class or array
seed string[] random Seed for Phaser.Math.RND
title / url / version string '' Console banner metadata
autoFocus boolean true Auto-focus window on boot and mousedown
stableSort number|boolean -1 -1 auto-detect, 0 built-in sort, 1 native ES2019 sort
disableContextMenu boolean false Disable right-click menu
backgroundColor string|number 0x000000 Canvas background (ignored if transparent)
banner boolean|object shown false to hide the console banner

scale sub-config

Property Type Default Description
width / height number|string 1024 / 768 Base game dimensions
zoom number 1 Zoom; constants NO_ZOOM/ZOOM_2X/ZOOM_4X/MAX_ZOOM
parent HTMLElement|string undefined DOM parent
mode number NONE (0) Scale mode (see below)
expandParent boolean true Adjust parent CSS height to 100%
autoRound boolean false Round display/style sizes
autoCenter number NO_CENTER (0) Centering mode
resizeInterval number 500 ms between browser-size checks
fullscreenTarget HTMLElement|string null Element for fullscreen
min / max {width, height} {0,0} Min/max canvas dimensions (0 = none)
snap {width, height} {0,0} Snap canvas size to multiples

Scale modes: NONE (0), WIDTH_CONTROLS_HEIGHT (1), HEIGHT_CONTROLS_WIDTH (2), FIT (3), ENVELOP (4), RESIZE (5), EXPAND (6, new in v4). Center modes: NO_CENTER (0), CENTER_BOTH (1), CENTER_HORIZONTALLY (2), CENTER_VERTICALLY (3).

render sub-config

These can be set top-level or under render (the sub-object wins).

Property Type Default Description
antialias boolean true Linear interpolation for scaled/rotated textures
antialiasGL boolean true Antialias on WebGL context creation
pixelArt boolean false Sets antialias:false, roundPixels:true
smoothPixelArt boolean false WebGL: blocky pixels with smoothed edges (new in v4)
roundPixels boolean false Snap texture objects to integer positions (was true in v3)
transparent boolean false Transparent canvas (forces bg to rgba(0,0,0,0))
clearBeforeRender boolean true Clear canvas each frame
preserveDrawingBuffer boolean false Preserve WebGL buffers between frames
premultipliedAlpha boolean true Pre-multiplied alpha in WebGL
desynchronized boolean false Desynchronized context
failIfMajorPerformanceCaveat boolean false Abort WebGL if browser reports poor perf
powerPreference string 'default' 'high-performance', 'low-power', 'default'
batchSize number 16384 Max quads per WebGL batch
maxTextures number -1 Max GPU textures (-1 = all available)
maxLights number 10 Max lights visible per camera
autoMobileTextures boolean true Restrict to 1 texture/batch on iOS/Android
selfShadow boolean false Self-shadowing on lit textured objects
pathDetailThreshold number 1 Graphics WebGL path point-combining threshold (new in v4)
skipUnreadyShaders boolean false Skip objects whose shader is still compiling (new in v4)
mipmapFilter string '' Mipmap mag filter ('NEAREST', 'LINEAR', ...)
mipmapRegeneration boolean false Regenerate framebuffer mipmaps (expensive)
renderNodes object {} Custom WebGL render nodes (new in v4)

Warning

mipmapRegeneration is expensive (10+ microseconds per texture) and affects filters and DynamicTextures. Leave it false unless you specifically need framebuffer mipmaps.

input sub-config

Property Default Description
keyboard true Enable Keyboard plugin
keyboard.target window DOM target for keyboard events
keyboard.capture [] Key codes to preventDefault
mouse true Enable Mouse plugin
mouse.target game canvas DOM target for mouse events
mouse.preventDefault{Down,Up,Move,Wheel} true preventDefault toggles
touch device support Enable Touch plugin
touch.capture true Call preventDefault on touch
activePointers 1 Number of Pointer objects created
smoothFactor 0 Pointer movement smoothing
windowEvents true Listen for input on window
gamepad false Enable Gamepad plugin

loader sub-config

Property Default Description
baseURL '' Prepended to all relative URLs
path '' Prepended after baseURL
maxParallelDownloads 32 (6 on Android) Concurrent downloads
crossOrigin undefined CORS setting ('anonymous', etc.)
responseType '' XHR response type
async true Async XHR
user / password '' XHR credentials
timeout 0 XHR timeout (ms)
maxRetries 2 Retries per failed file (was 0 in early v3)
withCredentials false XHR withCredentials
imageLoadType 'XHR' 'XHR' or 'HTMLImageElement'
localScheme ['file://','capacitor://'] Schemes treated as local

Other sub-configs

Key Source typedef Purpose
physics PhysicsConfig default: 'arcade'|'matter' plus per-system config
fps FPSConfig min, target, limit, forceSetTimeOut, smoothStep, deltaHistory, panicMax
audio AudioConfig disableWebAudio, noAudio, context
callbacks CallbacksConfig preBoot(game), postBoot(game)
dom DOMContainerConfig createContainer, pointerEvents
plugins PluginObject global/scene/default plugin registration
images ImagesConfig default, missing, white base64 textures
filters.glow — quality (10), distance (10) defaults for the Glow filter

Important

Config.js checks for window.FORCE_WEBGL and window.FORCE_CANVAS globals at the end of parsing and overrides type accordingly. This is intended for development/testing — be aware of it if a build behaves unexpectedly.


Build & Distribution

npm package entry points

From package.json:

{
  "main": "./src/phaser.js",
  "types": "./types/phaser.d.ts",
  "browser": "./dist/phaser.js",
  "module": "./dist/phaser.esm.js",
  "exports": {
    ".": {
      "types": "./types/phaser.d.ts",
      "import": "./dist/phaser.esm.js",
      "require": "./dist/phaser.js",
      "default": "./dist/phaser.esm.js"
    }
  }
}

Bundlers resolve the ESM build (dist/phaser.esm.js) via import, the UMD build (dist/phaser.js) via require, and the .d.ts for types.

Build entry points (source)

src/ contains multiple entry points that produce different bundles by including or excluding subsystems:

Entry file Bundle contents
src/phaser.js Full build — all features, both physics engines
src/phaser-arcade-physics.js Excludes Matter.js
src/phaser-no-physics.js Excludes both physics engines
src/phaser-core.js Minimal core — basic game objects only
src/phaser-esm.js ESM build with named + default exports

Each entry assembles the Phaser namespace object from subsystem require()s, merges in CONST, and assigns global.Phaser. Conditional features are gated behind Webpack DefinePlugin flags (FEATURE_SOUND, WEBGL_RENDERER, CANVAS_RENDERER, WEBGL_DEBUG) that are inlined at build time so dead code is stripped.

npm scripts

Script Command Purpose
build webpack --config config/webpack.config.js Development build
watch webpack --watch ... Rebuild on change
dist webpack --config config/webpack.dist.config.js Production distribution
lint / lintfix eslint --config .eslintrc.json "src/**/*.js" Lint (and auto-fix)
bundleshaders node scripts/bundle-shaders.js Bundle GLSL shaders
tsgen jsdoc -c jsdoc-tsd.conf.json Generate TypeScript defs from JSDoc
test-ts tsc --build tsconfig.json Compile-check the generated types
test / test:watch vitest run / vitest Run unit tests
sloc node-sloc "./src" --include-extensions "js" Count source lines

The TypeScript definition pipeline

Phaser's .d.ts files are generated from JSDoc comments (not hand-written), which is why the readable source is so large. The pipeline: build-tsgen compiles the custom tsgen tool (scripts/tsgen/), tsgen runs jsdoc with a TSD plugin to emit types/phaser.d.ts, and test-ts compiles a test project against the generated types to catch regressions.

Note

Because types are JSDoc-derived, the accuracy of the TypeScript API depends entirely on the JSDoc annotations in source. Several open issues (e.g. #7249, #7248, #7260) concern improving the strictness and literal-typing of the generated definitions.


Integrating Phaser into an App

Framework integration pattern

Phaser is a self-contained DOM widget — it owns a <canvas>. The integration pattern with React/Vue/etc. is to mount the game into a container element and tear it down on unmount:

// React example (concept)
useEffect(() => {
    const game = new Phaser.Game({
        type: Phaser.AUTO,
        parent: containerRef.current,
        scene: [GameScene]
    });
    return () => game.destroy(true);   // clean up on unmount
}, []);

Caution

game.destroy(removeCanvas, noReturn) is asynchronous — it flags the game for destruction on the next frame. Listen for Phaser.Core.Events.DESTROY to know when cleanup completed. Pass noReturn: true only if you will never create another Phaser instance on that page; it releases additional internal references that prevent re-creation.

Communicating between app and game

  • App → game: Keep a reference to the game instance; reach scenes via game.scene.getScene('Key') and call public methods, or push data into game.registry.
  • Game → app: Emit events on game.events or a scene's events, which your framework code listens to.
  • Shared state: The global registry (a DataManager) is the cleanest cross-boundary store.

SPA / WebAudio context reuse

For single-page apps that recreate the game without a full page reload, reuse the audio context to avoid leaking suspended contexts:

const config = { audio: { context: existingAudioContext } };
// or at runtime: game.sound.setAudioContext(newContext);   // WebAudio only

Lifecycle and visibility

game.events emits 'blur', 'focus', 'hidden', 'visible', 'pause', 'resume'. The VisibilityHandler auto-pauses the loop when the tab is hidden. The SoundManager's pauseOnBlur (default true) auto-pauses audio on blur. iOS 17/18+ audio interruptions are handled via context.suspend()/resume() on visibility events.


The Rendering Architecture

This is the defining change in Phaser 4 and the most important section for anyone doing custom rendering or evaluating performance.

RenderNodes replace Pipelines

In v3, a Pipeline was a rendering subsystem that often held multiple responsibilities and managed WebGL state independently — leading to conflicts (one pipeline could corrupt another's assumptions). In v4, the RenderNodeManager (on the WebGL renderer) owns a graph of RenderNodes, each handling exactly one task via a run() method. Some nodes also have a batch() method to accumulate state from multiple sources before drawing.

Game objects reference nodes through role-based maps:

gameObject.defaultRenderNodes   // built-in nodes per role
gameObject.customRenderNodes    // per-role overrides
gameObject.renderNodeData       // data keyed by node name

// Roles: 'Submitter' (drives other roles per element),
//        'Transformer' (vertex coordinates), 'Texturer' (textures)
gameObject.setRenderNodeRole('Submitter', 'MyCustomSubmitter', { customProp: 42 });
gameObject.setRenderNodeRole('Submitter', null);   // revert to default

Built-in node categories:

  • Batch handlers (multi-object draws): BatchHandlerQuad, BatchHandlerQuadSingle, BatchHandlerTileSprite, BatchHandlerTriFlat, BatchHandlerPointLight, BatchHandlerStrip.
  • Submitters: SubmitterQuad, SubmitterTile, SubmitterTileSprite, SubmitterSpriteGPULayer, SubmitterTilemapGPULayer.
  • Transformers: TransformerImage, TransformerStamp, TransformerTile, TransformerTileSprite.
  • Texturers: TexturerImage, TexturerTileSprite.
  • Filters: FilterBlur, FilterGlow, FilterColorMatrix, FilterBlend, FilterMask, FilterPixelate, FilterShadow, FilterVignette, FilterWipe, and ~20 more.
  • Compositing/utility: Camera, FillRect, FillPath, DrawLine, StrokePath, ShaderQuad, ListCompositor, RebindContext, YieldContext, DynamicTextureHandler.

Performance benefits in v4: quads now use index buffers (≈⅓ fewer vertex uploads), smarter multi-texture batching (fewer batch breaks on mobile), just-in-time rendering (nothing hits the GPU until necessary), and removal of genericVertexBuffer/genericVertexData (~16 MB RAM/VRAM freed).

Caution

Do not make direct WebGL gl calls in a v4 game. The internal WebGLGlobalWrapper tracks state; raw gl calls desync it and cause unpredictable behavior. If you need direct WebGL access, use an Extern game object, which resets state when it finishes.

Render flow via RenderSteps

The RenderSteps component lets game objects inject custom logic into their render. Steps are stored in _renderSteps and executed via renderWebGLStep(); the first step is responsible for invoking subsequent steps. The Filters system is built on RenderSteps — it defers and controls the renderWebGL flow this way.

The Filter system

Filters unify v3's FX and Masks. A filter takes an input image and produces an output via a shader pass, and applies to any game object or camera (WebGL only). Two lists:

  • filters.internal — runs before the camera transform (object-local space, cheaper).
  • filters.external — runs after (screen space, full resolution).
sprite.enableFilters();
sprite.filters.internal.addGlow(0xff00ff, 4, 0, 1);
sprite.filters.external.addBlur(0, 2, 2, 1);
sprite.filters.internal.addMask(maskImage);   // BitmapMask replacement
camera.filters.external.addColorMatrix().grayscale();

Built-in filters: Barrel, Blend, Blocky, Blur (Low/Med/High), Bokeh, ColorMatrix, CombineColorMatrix, Displacement, Glow, GradientMap, ImageLight, Key (chroma), Mask, NormalTools, PanoramaBlur, ParallelFilters, Pixelate, Quantize, Sampler, Shadow, Threshold, Vignette, Wipe.

Renderer selection

CreateRenderer (src/core/CreateRenderer.js) resolves the renderer:

  1. customEnvironment or a provided canvas with type: AUTO throws (must specify explicit type).
  2. AUTO → WebGL if Features.webGL, else Canvas.
  3. WEBGL → throws if WebGL unavailable (no fallback).
  4. The build's WEBGL_RENDERER/CANVAS_RENDERER DefinePlugin flags determine which renderer classes are even included.

Canvas renderer status

Canvas is deprecated in v4 (still functional). It can't do any of v4's WebGL techniques. Its one remaining advantage — 27 blend modes vs WebGL's 4 native (NORMAL, ADD, MULTIPLY, SCREEN) — is now recoverable in WebGL via the new Blend filter (with indirection through a CaptureFrame/DynamicTexture). New projects should use WebGL.


Subsystem Reference

A map of the major subsystems, their entry namespace, and the source folder that owns them.

Namespace Folder Responsibility
Phaser.Core src/core/ Game, Config, TimeStep, CreateRenderer, VisibilityHandler
Phaser.Scenes src/scene/ Scene, SceneManager, ScenePlugin, Systems, InjectionMap
Phaser.GameObjects src/gameobjects/ All game objects, factories, creators, component mixins
Phaser.Loader src/loader/ LoaderPlugin, File, 30+ filetype loaders
Phaser.Textures src/textures/ TextureManager, Texture, Frame, parsers
Phaser.Cache src/cache/ CacheManager and typed sub-caches
Phaser.Animations src/animations/ AnimationManager, Animation, AnimationState
Phaser.Tweens src/tweens/ TweenManager, Tween, TweenChain, builders, easing
Phaser.Input src/input/ InputManager, InputPlugin, Pointer, Keyboard, Gamepad
Phaser.Physics.Arcade src/physics/arcade/ World, Body, StaticBody, Collider, groups
Phaser.Physics.Matter src/physics/matter-js/ Matter.js wrapper + bundled engine
Phaser.Cameras src/cameras/ CameraManager, Camera, effects, controls
Phaser.Sound src/sound/ WebAudio/HTML5/NoAudio managers and sounds
Phaser.Tilemaps src/tilemaps/ Tilemap, layers, parsers (Tiled/CSV/Impact), GPU layer
Phaser.Scale src/scale/ ScaleManager and scale/center/zoom constants
Phaser.Math src/math/ Vectors, matrices, RNG, easing, interpolation
Phaser.Geom src/geom/ Circle, Ellipse, Line, Polygon, Rectangle, Triangle, intersects
Phaser.Display src/display/ Color, masks, bounds, align, canvas utils
Phaser.Curves src/curves/ Bezier, spline, path curves
Phaser.Time src/time/ Clock, TimerEvent
Phaser.Data src/data/ DataManager (registry + per-scene data)
Phaser.Filters src/filters/ Filter definitions (the v4 FX/Mask system)
Phaser.Renderer src/renderer/ WebGL + Canvas renderers, RenderNodes, shaders, wrappers
Phaser.Plugins src/plugins/ PluginManager, PluginCache, BasePlugin, ScenePlugin
Phaser.Structs src/structs/ List, ProcessQueue, RTree, Size
Phaser.Utils src/utils/ Class, object/array/string helpers, base64
Phaser.Actions src/actions/ Bulk operations over arrays of game objects
Phaser.Events src/events/ The shared EventEmitter export

Game Object component model

Game objects are composed by mixing component objects into the prototype rather than inheriting deeply. The base GameObject mixes in Filters and RenderSteps; Sprite and Image share an identical set:

Alpha, BlendMode, Depth, Flip, GetBounds, Lighting, Mask, Origin,
RenderNodes, ScrollFactor, Size, TextureCrop, Tint, Transform, Visible

The difference between Sprite and Image is just the AnimationState (sprite.anims) and a render-specific mixin. The authoritative reference for a method like setAlpha() is its component source file (src/gameobjects/components/Alpha.js), not the game object class. The complete component list is in src/gameobjects/components/index.js.

New v4 components: Lighting, RenderSteps, RenderNodes.

Physics: Arcade internals

File Responsibility
ArcadePhysics.js The this.physics scene plugin
World.js Simulation: bodies, gravity, bounds, colliders, RTree, step loop
Body.js Dynamic body — velocity, acceleration, bounce, drag, mass, collision state
StaticBody.js Immovable body with optimized RTree lookup
Factory.js this.physics.add.*
Collider.js Persistent collision/overlap with callbacks
PhysicsGroup.js / StaticPhysicsGroup.js Groups with per-member body defaults

The World uses an RTree spatial index for dynamic bodies by default (useTree), which becomes expensive to rebuild beyond ~5,000 dynamic bodies — disable it for very large simulations. Fixed timestep (fixedStep: true, fps: 60) is the default; collision categories use 32-bit masks (max 32 categories).

Sound: backend selection

SoundManagerCreator.create() picks the backend: NoAudioSoundManager if disabled/unsupported, else WebAudioSoundManager (preferred — precise timing, panning, spatial audio), else HTML5AudioSoundManager. There is one manager per game.


Repository Structure

phaser/
├── src/                    Core source (~3,500 files, ES5-authored)
│   ├── phaser.js           Full build entry point
│   ├── phaser-*.js         Alternate build entry points
│   ├── const.js            Global constants (VERSION, AUTO/CANVAS/WEBGL/HEADLESS)
│   ├── core/               Game, Config, TimeStep, renderer creation
│   ├── scene/              Scene, SceneManager, ScenePlugin, Systems
│   ├── gameobjects/        All game objects + component mixins + factories
│   ├── loader/             LoaderPlugin + filetypes/
│   ├── renderer/           webgl/ (RenderNodes, shaders) + canvas/
│   ├── physics/            arcade/ + matter-js/
│   ├── input/              keyboard/ mouse/ touch/ gamepad/
│   ├── animations/  tweens/  sound/  cameras/  tilemaps/
│   ├── math/  geom/  curves/  display/  structs/  utils/
│   ├── filters/            v4 Filter system
│   ├── data/  cache/  textures/  time/  scale/  device/  dom/  plugins/
│   └── polyfills/          requestVideoFrame (most v3 polyfills removed)
├── types/                  Generated TypeScript definitions (phaser.d.ts)
├── config/                 Webpack configs (webpack.config.js, dist, nospector)
├── scripts/                Build helpers (tsgen/, bundle-shaders.js, help.js)
├── tests/                  Vitest unit tests (mirror src/ layout)
├── changelog/              Per-version changelogs (v3/ and v4/)
├── docs/                   In-depth rendering articles (WebGL, shaders, pixel art)
├── skills/                 AI agent skill packs (one per subsystem)
├── package.json
└── README.md

The skills/ directory — a notable asset

Phaser 4 ships a comprehensive set of AI agent skills in skills/ — one folder per subsystem (scenes, physics, input, animations, tilemaps, tweens, particles, cameras, filters, plus v3-to-v4-migration and v4-new-features). Each contains a SKILL.md with authoritative API references, idiomatic patterns, gotchas, and source-file maps. Point an AI coding agent at this folder and it gains deep, accurate knowledge of Phaser's architecture. This is a deliberate "AI-ready" design choice and is among the best in-repo agent documentation of any major framework.

The docs/ directory

Contains deep-dive rendering articles for Phaser 4: WebGL Compressed Textures, the Shader Guide, Pixel Art Guide, Internal Space Guide, Rendering Concepts, and the Compact Texture Atlas Format Specification. These are for engine-level understanding, not gameplay.


Security Analysis

Phaser is a client-side rendering library, not a server or a sandbox. Its security surface is correspondingly narrow, but there are real considerations for production deployment.

Threat model

Concern Assessment
Remote code execution No eval of game logic; the library doesn't execute arbitrary remote code by design. The loader's script/plugin filetypes do inject and run external JS — treat those URLs as trusted only.
Asset loading & CORS The Loader uses XHR with configurable crossOrigin. Cross-origin textures used in WebGL require proper CORS headers or they taint the context.
Data deserialization Tilemap/atlas/animation JSON is parsed with JSON.parse (safe); no eval-based parsing.
Network egress Phaser makes no telemetry calls. The only network activity is asset loading you initiate. The console banner links to phaser.io but makes no request.
Dependency surface Exactly one runtime dependency (eventemitter3), minimizing supply-chain exposure.

Hardening recommendations

Important

The Loader's this.load.script(), this.load.scripts(), this.load.plugin(), and this.load.scenePlugin() methods download and execute JavaScript. Only ever point them at first-party, integrity-controlled URLs. Loading a script filetype from user-supplied or third-party URLs is an XSS/RCE vector.

  • Content Security Policy: Phaser works under CSP, but WebGL shader compilation, blob URLs (default image load type is XHR→blob), and Web Audio may require worker-src, blob:, and connect-src allowances. Test your CSP with the actual asset pipeline.
  • CORS: Set loader.crossOrigin: 'anonymous' when loading cross-origin assets destined for WebGL, and ensure the asset host returns Access-Control-Allow-Origin.
  • Compressed textures: v4 expects GL-orientation (Y=0 at bottom). Re-compress assets accordingly — a mismatch is a correctness, not security, issue, but worth noting in a deployment checklist.
  • User-generated content: If your game loads player-supplied images/levels, validate dimensions and sizes before loading to avoid memory-exhaustion DoS on the client.
  • No secrets in the bundle: Anything in the client bundle (API keys, level data) is fully visible. Never embed secrets; gate sensitive logic behind a server.

Input handling

Phaser calls preventDefault on captured keys and (configurably) mouse/touch events. The disableContextMenu option suppresses the right-click menu. None of this is a security boundary — it's UX. The keyboard capture list is global across all scenes.


Performance & Operations

Production checklist

  • [ ] Use the minified build (phaser.min.js or a bundler production build).
  • [ ] Set physics.arcade.debug: false (debug rendering is expensive).
  • [ ] Use type: Phaser.AUTO for WebGL with Canvas fallback.
  • [ ] Provide multiple audio formats (OGG + MP3) for cross-browser coverage.
  • [ ] Set appropriate scale.mode for your target devices.
  • [ ] Consider autoMobileTextures: true (default) for mobile batch limits.
  • [ ] Remove the console banner in production if desired (banner: false).
  • [ ] Test under your Content Security Policy.

Batching and draw calls

WebGL performance is dominated by draw-call count. Batches break when:

  • The texture changes (group objects by texture/atlas; use multi-atlases).
  • The blend mode changes (group objects by blend mode).
  • Lighting is toggled (lighting changes the shader — group lit and unlit objects).
  • A filter is applied (filters force framebuffer indirection).

Tip

For very large object counts, prefer the v4 GPU game objects: SpriteGPULayer (millions of sprites, one draw call) and TilemapGPULayer (entire layer as a single quad, per-pixel cost). Populate their buffers once — updating a static GPU buffer is expensive.

Memory

  • Textures are the dominant memory cost. Remove unused textures with this.textures.remove(key) and audio with this.cache.audio.remove(key).
  • Use object pooling (Groups with maxSize + killAndHide) instead of create/destroy churn for bullets, particles, enemies.
  • game.destroy(true, true) releases internal references but precludes re-creating a Phaser instance on the page.

Physics tuning

  • fps (default 60) controls physics steps/sec; fixedStep: true decouples from render rate.
  • useTree: false for 5,000+ dynamic bodies (RTree rebuild cost).
  • timeScale for slow-motion/fast-forward; overlapBias/tileBias for separation tuning.

The console banner

DebugHeader.js prints a styled banner with version, renderer, and audio type. It uses CSS console styling (plain text fallback on IE). Disable with banner: false or hide just the Phaser line with banner: { hidePhaser: true }.


Code Quality & Conventions

Authoring conventions

  • ES5-compatible source, enforced by eslint-plugin-es5. No ES6+ syntax in src/ — classes are built via the custom Phaser.Class helper (src/utils/Class.js).
  • JSDoc on everything. Every class, method, property, and config option carries JSDoc that drives both the API docs and the generated TypeScript definitions. This is why source files are large.
  • One concept per file. Each function/class typically lives in its own file; folders aggregate via an index.js that re-exports.
  • Component mixins over inheritance. Game objects compose behavior from src/gameobjects/components/*.
  • Namespace assembly. Entry points build the Phaser object from subsystem requires; Extend merges constants in.

The Phaser.Class pattern

Because the source targets ES5, classes use a custom factory:

var Config = new Class({
    initialize: function Config (config) { /* constructor */ },
    someMethod: function () { /* ... */ }
});

Class.mixin composes component objects into the prototype. (Note: a known issue, #7286, is that Phaser.Class including Class.mixin is not exposed on the public ESM namespace, which constrains third-party plugins.)

Event-driven architecture

Nearly every subsystem extends eventemitter3. Managers, scenes, game objects, tweens, sounds, and cameras all emit typed events with string names and named constants (e.g. Phaser.Scenes.Events.CREATE). This decoupling is the backbone of cross-system and cross-scene communication.

Build-time feature flags

Conditional compilation via Webpack DefinePlugin: FEATURE_SOUND, WEBGL_RENDERER, CANVAS_RENDERER, WEBGL_DEBUG. Code guarded by if (typeof FEATURE_X) is included or dead-code-eliminated per build target — this is how the alternate builds (phaser-core, phaser-no-physics) are produced.


Testing Strategy

  • Framework: Vitest 4 (npm test / npm run test:watch), with jsdom simulating the DOM.
  • Layout: tests/ mirrors src/ (e.g. tests/math/, tests/geom/, tests/physics/, tests/tweens/). Coverage is strongest in the pure-logic subsystems — math, geometry, structs, actions, curves — where deterministic unit tests are tractable.
  • Type tests: npm run test-ts compiles a TypeScript test project (scripts/tsgen/test/) against the generated phaser.d.ts to catch definition regressions.
  • What's hard to unit-test: WebGL rendering output is not covered by automated tests in the standard suite — rendering correctness relies on the examples site and manual verification. This is inherent to GPU rendering and is the main coverage gap.

Note

When contributing, add tests under the mirrored tests/<subsystem>/ folder. Pure-logic changes (math, geometry, data structures) are expected to ship with unit tests; rendering changes are validated via the examples repository.


Known Issues & Tech Debt

Sourced from the project's open GitHub issues at the documented commit. These reflect real, community-confirmed items as of this analysis.

Confirmed bugs

Issue Summary
#7307 AnimationManager.toJSON() errors when stringified indirectly
#7303 Linting broken on master
#7289 (closed) filter.getPaddingCeil is not a function
#7267 (closed) MatterTileBody crashes on flipped tiles with collision shapes
#7309 Phaser.Math.Pow2.GetPowerOfTwo declared in phaser.d.ts but undefined at runtime
#7305 GetColor32() outputs negative values
#7296 Memory leak in TilemapLayerWebGLRenderer
#7295 Layer is not extended from the GameObject class
#7292 Blitter doesn't destroy its Bobs
#7193 Memory leak using MobilePipeline and SpinePlugin
#7306 Significant FPS drop with simple Container.addMask() in WebGL

TypeScript definition debt

Issue Summary
#7249 EventEmitter typings are unsatisfactory and unsafe
#7248 Event types should be literal types instead of string
#7260 TweenManager.create and similar take overly permissive object config types
#7257 Phaser.Math.RND.Pick / WeightedPick should use ArrayLike
#7298 Investigate alternatives to jsdoc for type generation

Architectural / API gaps

Issue Summary
#7286 Phaser.Class (incl. Class.mixin) not exposed on the public ESM namespace — forces third-party plugins into inadequate mixin fallbacks
#7294 No screen-reader accessibility guidance or framework-level a11y helpers
#7275 "First Game" example needs upgrading for Phaser 4

Warning

Two memory leaks are open against the WebGL renderer paths (#7296 TilemapLayer, #7193 Spine plugin) and one against Blitter cleanup (#7292). If your game uses tilemap layers or Spine heavily and runs for long sessions, monitor memory and consider explicit teardown/recreation of affected objects.

Accessibility

Phaser has no built-in screen-reader support or framework-level accessibility helpers (#7294 is an open request). Games requiring accessibility must implement it at the application layer (e.g. an off-screen DOM live region mirrored from game state).


Extending Phaser

Custom plugins

Register plugins via the config plugins object:

const config = {
    plugins: {
        global: [
            { key: 'SaveManager', plugin: SaveManagerPlugin, start: true, data: {} }
        ],
        scene: [
            { key: 'GridPlugin', plugin: GridPlugin, mapping: 'grid' }
        ]
    }
};

Global plugins are accessible game-wide; scene plugins are injected into each scene under their mapping name. Extend Phaser.Plugins.BasePlugin or ScenePlugin.

Custom render nodes

renderer.renderNodes.addNodeConstructor('MyNode', MyNodeClass);
renderer.renderNodes.addNode('MyNode', myNodeInstance);
// Register at boot via config.render.renderNodes, then assign per object:
gameObject.setRenderNodeRole('Submitter', 'MyNode');

Custom game objects

Build a class (via Phaser.Class or ES6 in your own project, where modern syntax is fine), mix in the components you need, and register a factory:

Phaser.GameObjects.GameObjectFactory.register('myThing', function (x, y) {
    return this.displayList.add(new MyThing(this.scene, x, y));
});
// Now usable as this.add.myThing(x, y)

The Extern game object

For direct WebGL access without desyncing Phaser's state wrapper, use an Extern game object — it resets WebGL state after rendering, making it safe to do raw gl calls inside it.


Contributing

  • Repository: github.com/phaserjs/phaser (single master/main branch).
  • License: MIT — contributions licensed accordingly.
  • Source style: ES5-compatible, JSDoc-annotated, one concept per file, lint clean (npm run lint). (Note #7303: linting is currently broken on master — verify the lint config before relying on it.)
  • Tests: Add Vitest tests under the mirrored tests/<subsystem>/ path for logic changes.
  • Changelog: Each version has its own changelog under changelog/v4/.
  • Types: If you change a public API, regenerate and type-check the definitions (npm run ts).
  • Community: Discord (discord.gg/phaser), the Phaser Discourse forum, and the examples repository for rendering validation.

Quick Reference

Renderer constants (src/const.js)

Constant Value
Phaser.VERSION '4.1.0'
Phaser.AUTO / CANVAS / WEBGL / HEADLESS 0 / 1 / 2 / 3
Phaser.FOREVER -1
Phaser.NONE / UP / DOWN / LEFT / RIGHT 4 / 5 / 6 / 7 / 8

Global game members (game.*)

config, renderer, canvas, context, events, anims, textures, cache, registry, input, scene, device, scale, sound, loop, plugins. State flags: isBooted, isRunning, isPaused, hasFocus.

Game control

game.pause();  game.resume();   // halt/continue the whole loop
game.destroy(removeCanvas, noReturn);   // async; listen for DESTROY

Build variants

Build Excludes
phaser.js nothing (full)
phaser-arcade-physics.js Matter.js
phaser-no-physics.js Arcade + Matter
phaser-core.js most game objects, Geom, Tilemaps

Key source files

Concern File
Game / boot src/core/Game.js
Config + all defaults src/core/Config.js
Game loop src/core/TimeStep.js
Renderer selection src/core/CreateRenderer.js
Scene base src/scene/Scene.js
Scene orchestration src/scene/SceneManager.js, ScenePlugin.js
Injection map src/scene/InjectionMap.js
Component list src/gameobjects/components/index.js
WebGL renderer src/renderer/webgl/WebGLRenderer.js
Build config config/webpack.config.js

Documentation & resources

  • API docs: https://docs.phaser.io
  • Source examples: https://phaser.io/examples
  • In-repo AI skills: skills/ (one per subsystem)
  • In-repo rendering deep-dives: docs/
  • Migration & gameplay basics: the companion User Guide