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
- Executive Summary
- System Architecture
- Requirements & Compatibility
- Configuration Mastery
- Build & Distribution
- Integrating Phaser into an App
- The Rendering Architecture
- Subsystem Reference
- Repository Structure
- Security Analysis
- Performance & Operations
- Code Quality & Conventions
- Testing Strategy
- Known Issues & Tech Debt
- Extending Phaser
- Contributing
- 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.tsdefinitions 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):
- Constructor parses config into
Config, instantiates every global manager. - Waits for
DOMContentLoaded, thenboot():config.preBoot(game)callback.scale.preBoot().CreateRenderer(game)— picks WebGL/Canvas/Headless, creates the canvas.CreateDOMContainer(game)(ifdom.createContainer).DebugHeader(game)— the styled console banner.AddToDOM(canvas, parent).- Emits
BOOT.
- When
TextureManageremitsREADY→texturesReady()emitsREADY, callsstart(). start()begins theTimeSteploop, installs theVisibilityHandler, runsconfig.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://andcapacitor://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
mipmapRegenerationis expensive (10+ microseconds per texture) and affects filters and DynamicTextures. Leave itfalseunless 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.jschecks forwindow.FORCE_WEBGLandwindow.FORCE_CANVASglobals at the end of parsing and overridestypeaccordingly. 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 forPhaser.Core.Events.DESTROYto know when cleanup completed. PassnoReturn: trueonly 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
gameinstance; reach scenes viagame.scene.getScene('Key')and call public methods, or push data intogame.registry. - Game → app: Emit events on
game.eventsor a scene'sevents, 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
glcalls in a v4 game. The internalWebGLGlobalWrappertracks state; rawglcalls desync it and cause unpredictable behavior. If you need direct WebGL access, use anExterngame 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:
customEnvironmentor a providedcanvaswithtype: AUTOthrows (must specify explicit type).AUTO→ WebGL ifFeatures.webGL, else Canvas.WEBGL→ throws if WebGL unavailable (no fallback).- The build's
WEBGL_RENDERER/CANVAS_RENDERERDefinePlugin 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(), andthis.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:, andconnect-srcallowances. 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 returnsAccess-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.jsor a bundler production build). - [ ] Set
physics.arcade.debug: false(debug rendering is expensive). - [ ] Use
type: Phaser.AUTOfor WebGL with Canvas fallback. - [ ] Provide multiple audio formats (OGG + MP3) for cross-browser coverage.
- [ ] Set appropriate
scale.modefor 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) andTilemapGPULayer(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 withthis.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: truedecouples from render rate.useTree: falsefor 5,000+ dynamic bodies (RTree rebuild cost).timeScalefor slow-motion/fast-forward;overlapBias/tileBiasfor 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 insrc/— classes are built via the customPhaser.Classhelper (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.jsthat re-exports. - Component mixins over inheritance. Game objects compose behavior from
src/gameobjects/components/*. - Namespace assembly. Entry points build the
Phaserobject from subsystem requires;Extendmerges 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/mirrorssrc/(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-tscompiles a TypeScript test project (scripts/tsgen/test/) against the generatedphaser.d.tsto 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(singlemaster/mainbranch). - 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