
Pixijs Scene Core Concepts
- 3k installs
- 293 repo stars
- Updated June 4, 2026
- pixijs/pixijs-skills
pixijs-scene-core-concepts is a PixiJS v8 skill explaining scene graph containers, leaves, transforms, masking, and render order.
About
PixiJS Scene Core Concepts is the shared mental model for PixiJS v8 display lists covering containers, leaves, transforms, render order, culling, render groups, masking, and destroy semantics. Every display object is a Container subclass; leaves such as Sprite, Graphics, Text, Mesh, and GifSprite must not hold children, while grouping belongs in Container or RenderLayer nodes. The renderer walks the tree each frame, composes local and world transforms, culls offscreen nodes, and draws siblings in array order with optional sortableChildren zIndex or RenderLayer decoupling. Render groups with isRenderGroup true move transform work to the GPU for large stable subtrees. Masking supports Graphics stencil, Sprite alpha, and ColorMask types. Visibility versus renderable flags split skip-render from keep-transform-update behavior for hit testing. Destroy with children true recursively tears down branches and optional texture cleanup. The skill maps each leaf type to dedicated pixijs-scene leaf skills and points to scene-management and masking references for deeper patterns.
- Container versus leaf distinction with no children on leaves.
- World transform composition and render order by sibling index.
- Render groups for GPU transform on large stable subtrees.
- Masking types including stencil, alpha, scissor, and color.
- Destroy semantics with children and texture resource cleanup.
Pixijs Scene Core Concepts by the numbers
- 3,033 all-time installs (skills.sh)
- +214 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #164 of 2,277 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
pixijs-scene-core-concepts capabilities & compatibility
- Capabilities
- container versus leaf role guidance · local and world transform composition · sibling render order and zindex sorting · render group gpu transform optimization · masking type selection and setup · destroy and hierarchy lifecycle events
- Use cases
- frontend
- Pricing
- Free
What pixijs-scene-core-concepts says it does
In PixiJS v8, leaves must not have children.
Children render in array order: index 0 first, last index last.
npx skills add https://github.com/pixijs/pixijs-skills --skill pixijs-scene-core-conceptsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3k |
|---|---|
| repo stars | ★ 293 |
| Security audit | 3 / 3 scanners passed |
| Last updated | June 4, 2026 |
| Repository | pixijs/pixijs-skills ↗ |
How does the PixiJS v8 scene graph organize containers, leaves, transforms, and draw order?
Reason about PixiJS v8 scene graph structure, container versus leaf roles, transforms, render order, and masking.
Who is it for?
Frontend developers structuring PixiJS games, data viz, or canvas apps with v8 display lists.
Skip if: Skip for non-Pixi canvas APIs, backend services, or deep single-leaf API tutorials without graph context.
When should I use this skill?
User asks about PixiJS scene graph, Container, masking, render groups, or world transforms.
What you get
Correct container versus leaf usage, transform reasoning, and render order choices for a PixiJS scene.
- Constructor-option scene node snippets
- Refactored PixiJS initialization patterns
Files
This skill is the shared mental model referenced by all pixijs-scene-* leaves. It explains what the scene graph is in PixiJS v8, how a Container differs from a leaf, and where each concept lives. It does not go deep on any single API; it frames the pieces and points to the skill or reference file that does.
Quick Start
const world = new Container({ isRenderGroup: true });
app.stage.addChild(world);
const hero = new Container({ label: "hero" });
hero.addChild(new Sprite(bodyTexture));
hero.addChild(new Sprite(faceTexture));
world.addChild(hero);
const mask = new Graphics().rect(0, 0, 800, 600).fill(0xffffff);
world.mask = mask;
world.addChild(mask);
hero.position.set(world.width / 2, world.height / 2);Related skills: pixijs-scene-container (Container API in detail), the leaf skills (pixijs-scene-sprite, pixijs-scene-graphics, pixijs-scene-text, pixijs-scene-mesh, pixijs-scene-particle-container, pixijs-scene-dom-container, pixijs-scene-gif), pixijs-events (hit testing traverses the scene graph), pixijs-performance (cache, culling, render groups), pixijs-math (Matrix, toGlobal/toLocal detail).
Core Concepts
What the scene graph is
The PixiJS scene graph is a tree of display objects rooted at app.stage. Each node has a parent, a transform (position, scale, rotation, pivot, skew) relative to its parent, and optional visual state (alpha, tint, blendMode, visibility). Each frame the renderer walks the tree, composes transforms and visual state down to world-space, culls what's offscreen, and emits draw calls. The scene graph is both the layout model and the render order: earlier siblings draw behind later siblings.
Every display object in v8 is a Container subclass. DisplayObject from earlier versions was removed.
Container vs leaf (CRITICAL)
There are two roles in the tree:
- Containers: nodes that hold children. Use a
Container(orRenderLayer) for any node that groups, positions, or transforms other nodes. - Leaves: nodes that draw something and have no children. Use
Sprite,Graphics,Text,Mesh,ParticleContainer'sParticle,DOMContainer, orGifSpriteas leaves.
In PixiJS v8, leaves must not have children. Adding children to a Sprite / Graphics / Text / Mesh logs a deprecation warning and is scheduled to become a hard error. The rule is: use `Container` for any node that needs children; do not nest children inside leaf scene objects. If you need to group a leaf with other leaves, wrap them in a Container.
This distinction is why the pixijs-scene-* skills are split the way they are: pixijs-scene-container covers the grouping node, and each leaf gets its own skill focused on its draw behavior.
Transforms and coordinate spaces
Every container composes a localTransform (a Matrix) from its position, scale, rotation, pivot, and skew. The renderer multiplies parents' local transforms together to produce the worldTransform (and groupTransform if a render group is in the chain), which maps local points to scene-root space. Use toGlobal(point) and toLocal(point, from?) to convert between spaces, and getGlobalPosition() for this object's world position. Full Matrix detail lives in pixijs-math; transform setters and toLocal/toGlobal live in pixijs-scene-container.
Render order and explicit z-ordering
Children render in array order: index 0 first, last index last. For explicit z-ordering on a single container, set sortableChildren = true and assign zIndex values to children. For render order that is decoupled from the logical hierarchy (e.g., a character's parent is a game world but its drawing happens on a UI layer), use RenderLayer. Deep detail, including when to prefer sortable children vs RenderLayer, is in references/scene-management.md.
Render groups
Flagging a container with isRenderGroup: true (or calling container.enableRenderGroup()) tells PixiJS to apply its transform on the GPU as a single matrix instead of recomputing every descendant's world transform on the CPU each frame. Use render groups on large, stable sub-trees such as worlds, UI layers, or parallax strips. Deep detail in references/scene-management.md.
Culling
cullable = true + a cullArea: Rectangle tells the CullerPlugin (or any culling pass) to skip rendering objects that fall outside the visible area. cullableChildren = false short-circuits recursive culling for a sub-tree whose children are always on screen. Culling is a performance topic; pixijs-performance and references/scene-management.md cover the trade-offs.
Masking
Set container.mask to another display object to clip its rendering. PixiJS picks the mask type automatically: a Graphics or Container mask uses a stencil buffer, a Sprite mask uses an alpha filter, and a number selects a ColorMask. All four mask types (AlphaMask, StencilMask, ScissorMask, ColorMask) are covered in references/masking.md.
Visibility, alpha, tint, and blend mode
visible = false skips rendering and transform updates; renderable = false skips rendering but still updates transforms (use when hit-testing or bounds queries need to stay live). alpha and tint multiply down through the sub-tree; blendMode controls how this container's draw instructions composite against what is already on the target. See pixijs-blend-modes for the full blend-mode list and pixijs-scene-container for per-node state.
Destroy semantics
container.destroy() unlinks one node. container.destroy({ children: true }) recursively destroys the whole sub-tree; always use this for killing a branch. texture: true and textureSource: true additionally tear down GPU resources owned by leaves. If cacheAsTexture is on, disable it before destroying. pixijs-scene-container documents the full signature.
Lifecycle events
Containers emit events for hierarchy and visibility changes: childAdded / childRemoved on the parent, added / removed on the child, plus visibleChanged and destroyed on the container itself. Useful for wiring reactive UI updates or resource bookkeeping. Full details in references/container-hierarchy.md.
Leaf comparison: which skill covers which object
| Leaf | Primary use | Skill |
|---|---|---|
Sprite | Draw a single texture at a position (with variants NineSliceSprite for resizable UI panels and TilingSprite for repeating backgrounds). | pixijs-scene-sprite |
Text / BitmapText / HTMLText / SplitText / SplitBitmapText | Render text. Canvas-based Text for general use, BitmapText for high-volume cheap text, HTMLText for rich HTML/CSS layout, split variants for per-character animation. | pixijs-scene-text |
Graphics | Vector drawing: shapes, lines, paths, fills, strokes. Backed by a GraphicsContext. | pixijs-scene-graphics |
Mesh / MeshSimple / MeshPlane / MeshRope / PerspectiveMesh | Custom geometry with a shader or texture. Use MeshRope for textured path-following ribbons and PerspectiveMesh for 2D perspective. | pixijs-scene-mesh |
ParticleContainer + Particle | Thousands of lightweight sprites with a restricted transform set, for high-throughput particle effects. | pixijs-scene-particle-container |
DOMContainer | Render an HTML element positioned inside the scene graph (useful for inputs, iframes, accessibility overlays). | pixijs-scene-dom-container |
GifSprite | Animated GIF playback as a display object. Requires pixi.js/gif. | pixijs-scene-gif |
Container itself is covered in pixijs-scene-container and is the node every leaf lives inside.
When to use what (quick decisions)
- "I want to group and transform some display objects" →
Container, seepixijs-scene-container. - "I want to draw a texture" →
Sprite, seepixijs-scene-sprite. - "I want to draw vector shapes or paths" →
Graphics, seepixijs-scene-graphics. - "I want to draw text" →
Text/BitmapText/HTMLText, seepixijs-scene-text. - "I want thousands of cheap sprites" →
ParticleContainer, seepixijs-scene-particle-container. - "I want a custom-geometry mesh or a deformed sprite" →
Meshor one of its variants, seepixijs-scene-mesh. - "I want to clip a sub-tree" → set
.mask, seereferences/masking.md. - "I want a decoupled render order" →
RenderLayer, seereferences/scene-management.md. - "I want GPU-level transforms for a big stable sub-tree" →
isRenderGroup: true, seereferences/scene-management.md. - "I want to skip offscreen rendering" →
cullable = true+CullerPlugin, seepixijs-performance.
References
- references/constructor-options.md: the ~30 fields inherited by every
Container-derived node (transform, display, hierarchy, sorting, layout, effects, callbacks), with defaults, types, and when line-by-line assignment is appropriate. Shared reference for all leaf skills. - references/container-hierarchy.md: add/remove/swap children, reparenting with transform preservation, label navigation, destroy sub-trees.
- references/transforms.md: position, scale, rotation, pivot, origin, skew, toGlobal/toLocal, the three matrices (local/group/world), bounds.
- references/masking.md: AlphaMask, StencilMask, ScissorMask, ColorMask, inverse masking, cost comparison.
- references/layers.md:
RenderLayer, attach/detach, sorted layers, layer + logical parent split. - references/render-groups.md:
isRenderGroup, GPU-level transforms, when to use, render-groups vscacheAsTexture. - references/scene-management.md: combined view; render groups,
RenderLayer, culling, zIndex sorting,boundsArea.
Common Mistakes
[CRITICAL] Adding children to a leaf display object
Wrong:
const sprite = new Sprite(texture);
sprite.addChild(new Graphics().rect(0, 0, 10, 10).fill(0xff0000));Correct:
const group = new Container();
group.addChild(new Sprite(texture));
group.addChild(new Graphics().rect(0, 0, 10, 10).fill(0xff0000));In v8 leaves (Sprite, Graphics, Text, Mesh, ParticleContainer, DOMContainer, GifSprite) technically extend Container but should not hold children. Adding children to a leaf produces undefined rendering behavior. Wrap the leaf in a Container when you need grouping.
[CRITICAL] Referencing DisplayObject
Wrong:
import { DisplayObject } from "pixi.js"; // no such export in v8
function moveNode(node: DisplayObject) {
node.x += 1;
}Correct:
import { Container } from "pixi.js";
function moveNode(node: Container) {
node.x += 1;
}DisplayObject was removed in v8. Every display object — including Sprite, Graphics, Text, Mesh — is a Container subclass now. Use Container as the base type.
[HIGH] Forgetting isRenderGroup on large static subtrees
Wrong:
const world = new Container();
for (let i = 0; i < 5000; i++) {
world.addChild(new Sprite(texture));
}
app.stage.addChild(world);Correct:
const world = new Container({ isRenderGroup: true });
for (let i = 0; i < 5000; i++) {
world.addChild(new Sprite(texture));
}
app.stage.addChild(world);Without isRenderGroup: true, the renderer recomposes every child's transform against its parents every frame. Marking the subtree as a render group caches transforms and draw state until a child changes, which is essential for large or mostly-static trees.
[HIGH] Treating child.x as world space
Wrong:
const enemy = new Container();
enemy.x = 500;
world.addChild(enemy);
world.x = 200;
console.log(enemy.x); // 500 (local), not 700 (world)Correct:
const worldPos = enemy.toGlobal({ x: 0, y: 0 });
console.log(worldPos.x); // 700Container.x/y/scale/rotation are LOCAL to the parent. Use toGlobal(point) to compute world-space coordinates, or getGlobalPosition() for the container's origin in world space. The world transform is not exposed as a simple x/y pair.
[MEDIUM] sortableChildren without zIndex
Wrong:
const layer = new Container();
layer.sortableChildren = true;
layer.addChild(bg); // no zIndex
layer.addChild(mid); // no zIndex
layer.addChild(fg); // no zIndex
// order is unchanged — all zIndex default to 0Correct:
const layer = new Container();
layer.sortableChildren = true;
bg.zIndex = 0;
mid.zIndex = 10;
fg.zIndex = 20;
layer.addChild(bg, mid, fg);sortableChildren re-sorts children by zIndex before rendering, but only takes effect when children actually have distinct zIndex values. Setting only the parent flag has no visible effect.
Tooling
The PixiJS Devtools Chrome extension lets you inspect and manipulate a running scene graph in real time. Install it for any non-trivial layout or render-order debugging.
API Reference
Constructor options (Container-inherited)
Prefer new X({ ... }) over line-by-line property assignment when constructing Container-derived scene nodes.
Before / after
Line-by-line:
const hero = new Sprite(texture);
hero.x = 100;
hero.y = 200;
hero.anchor.set(0.5);
hero.scale.set(2);
hero.rotation = Math.PI / 4;
hero.alpha = 0.8;
hero.tint = 0xff8800;
hero.label = "hero";
hero.zIndex = 10;Options object:
const hero = new Sprite({
texture,
x: 100,
y: 200,
anchor: 0.5,
scale: 2,
rotation: Math.PI / 4,
alpha: 0.8,
tint: 0xff8800,
label: "hero",
zIndex: 10,
});Exceptions (line-by-line is fine here)
- Values calculated after construction — e.g.,
sprite.position.set(app.screen.width / 2, app.screen.height / 2)needsappto exist first. - Properties that change at runtime and the change is the point of the example — e.g.,
sprite.tint = damageColorin a damage-flash demo. - Objects received from elsewhere — you can't reconstruct them through a constructor options bag.
point.set(x, y)/scale.set(sx, sy)for multi-coordinate batches where a single call reads better than two options keys.
For v7 to v8 migration of constructor patterns, see pixijs-migration-v8.
Worked example
const group = new Container({
// Transform
x: 100,
y: 200,
scale: { x: 2, y: 2 },
rotation: Math.PI / 4,
pivot: { x: 50, y: 50 },
skew: { x: 0, y: 0 },
// Display
alpha: 0.8,
tint: 0xff8800,
blendMode: "add",
visible: true,
renderable: true,
// Hierarchy
label: "world",
children: [background, player],
// Sorting & grouping
isRenderGroup: true,
sortableChildren: true,
zIndex: 10,
// Layout & bounds
boundsArea: new Rectangle(0, 0, 800, 600),
// Effects
filters: [new BlurFilter(2)],
mask: maskGraphics,
// Callbacks
onRender: (renderer) => {
/* per-frame logic */
},
});Transform
| Option | Type | Default | Description |
|---|---|---|---|
alpha | number | 1 | Opacity multiplied with parent alpha; 0 is fully transparent, 1 fully opaque. |
angle | number | 0 | Rotation in degrees. Alias for rotation in radians. |
origin | `PointData \ | number` | new Point(0, 0) |
pivot | `PointData \ | number` | new Point(0, 0) |
position | PointData | new Point(0, 0) | Position in parent-local coordinates. |
rotation | number | 0 | Rotation in radians. |
scale | `PointData \ | number` | new Point(1, 1) |
skew | PointData | new Point(0, 0) | Skew factor in radians along each axis. |
x | number | 0 | Alias for position.x. |
y | number | 0 | Alias for position.y. |
Display
| Option | Type | Default | Description |
|---|---|---|---|
blendMode | BLEND_MODES | 'normal' | How this node composites against its target. |
renderable | boolean | true | When false, skips rendering but still updates transforms. |
tint | ColorSource | 0xFFFFFF | Color multiplied into this node's output; 0xFFFFFF is no tint. |
visible | boolean | true | When false, skips both rendering and transform updates for this subtree. |
Hierarchy
| Option | Type | Default | Description |
|---|---|---|---|
children | C[] | [] | Array of children to addChild after construction. |
label | string | null | Instance label used by getChildByLabel / getChildrenByLabel. |
parent | Container | null | Parent container; the new node is added as a child during construction. |
Sorting & grouping
| Option | Type | Default | Description |
|---|---|---|---|
isRenderGroup | boolean | false | Marks this container as a render group; GPU-level transform for the whole subtree. |
sortableChildren | boolean | false | Before rendering, re-sorts children by zIndex. |
zIndex | number | 0 | Sort key used when the parent has sortableChildren enabled. |
Layout & bounds
| Option | Type | Default | Description |
|---|---|---|---|
boundsArea | Rectangle | undefined | Override bounds rectangle; skips recursive child measurement. |
height | number | 0 | Requested height in pixels; internally assigns scale.y. |
width | number | 0 | Requested width in pixels; internally assigns scale.x. |
Effects
| Option | Type | Default | Description |
|---|---|---|---|
filters | `Filter \ | readonly Filter[]` | null |
mask | `Container \ | number \ | null` |
Callbacks
| Option | Type | Default | Description |
|---|---|---|---|
onRender | `((renderer: Renderer) => void) \ | null` | null |
Advanced options and aliases
autoGarbageCollect(@advanced, fromViewContainerOptions): leave at default unless you know why you need otherwise.cacheAsTexture: typed as a constructor option viaCacheAsTextureMixinConstructorbut is ACTUALLY A METHOD on the instance —container.cacheAsTexture(true). Passing it as an options key is accepted but semantically wrong. Prefer the method.setMask: typed onEffectsMixinConstructorbut is also a method, not data. Usemask: ...in options or callsetMask(...)later.interactive: convenience boolean that maps toeventMode;truesetseventMode = 'static',falsesetseventMode = 'passive'. SeeeventModefor the full set of modes.effects: theContainerconstructor explicitly passeseffects: truetoassignWithIgnore, so passing this option is silently dropped. Not useful.
See also
Events (eventMode, hitArea, cursor, interactive*, on* handlers), accessibility (accessible*, tabIndex, accessibleTitle, etc.), and culling (cullable, cullArea, cullableChildren) are ALSO valid constructor options via mixin extension on ContainerOptions. They are documented in their own skills: pixijs-events, pixijs-accessibility, pixijs-performance.
Container Hierarchy
The PixiJS scene graph is a tree of Container subclasses rooted at app.stage. Every node has a single parent, zero or more children, and a local transform. Use this reference for mental model and fine-grained parent/child management; for full Container API (constructor options, lifecycle methods), see the top-level pixijs-scene-container skill.
Quick Start
const world = new Container({ label: "world" });
app.stage.addChild(world);
const hero = new Container({ label: "hero" });
hero.addChild(new Sprite(bodyTexture));
hero.addChild(new Sprite(weaponTexture));
world.addChild(hero);
const enemies = new Container({ label: "enemies" });
world.addChild(enemies);
enemies.addChild(new Sprite(enemyTexture), new Sprite(enemyTexture));Every display object in v8 is a Container subclass. Leaves (Sprite, Graphics, Text, Mesh, ParticleContainer, DOMContainer, GifSprite) have allowChildren = false and must not have children. Use a plain Container to group leaves.
Core Patterns
Add, remove, swap
// Add (variadic, returns the first added child)
parent.addChild(child1, child2, child3);
// Insert at index
parent.addChildAt(child, 0);
// Remove
parent.removeChild(child);
parent.removeChildAt(0);
parent.removeChildren();
parent.removeChildren(0, 3); // first three
// Swap
parent.swapChildren(childA, childB);addChild with an existing parent first removes the child from its old parent (no explicit removeChild needed). Returned value is the first added child for chaining.
When addChildAt moves a child that is already in the same container, the move is silent: no added / removed / childAdded / childRemoved events fire, because the parent-child relationship hasn't changed.
Replacing a child
parent.replaceChild(oldChild, newChild);replaceChild swaps oldChild for newChild at the same index and copies the old child's local transform (position, rotation, scale, etc.) onto the replacement. Use this when you want a drop-in replacement to inherit the old child's placement without copying fields by hand.
Child queries
const count = parent.children.length;
const first = parent.getChildAt(0);
const index = parent.getChildIndex(child);
const hero = parent.getChildByLabel("hero");
const heroDeep = parent.getChildByLabel("weapon", true); // recursive
const enemies = parent.getChildrenByLabel("enemy");
const waves = parent.getChildrenByLabel(/^wave-\d+/);
const buttonsDeep = parent.getChildrenByLabel("button", true);label can be a string or a RegExp (e.g., parent.getChildByLabel(/^hero-/)). getChildByLabel returns the first match; getChildrenByLabel returns every match. Both walk the immediate children and recurse when the second argument is true. For more complex queries, iterate parent.children manually.
Iterating children
for (const child of parent.children) {
child.alpha = 0.5;
}
parent.children.forEach((child, i) => {
child.y = i * 32;
});children is a plain array. It's safe to iterate, but mutating it during iteration (via addChild / removeChild) is not; snapshot first with [...parent.children] if you need to modify the list.
Reparenting with transform preservation
newParent.reparentChild(child);
newParent.reparentChildAt(child, 0);
newParent.reparentChild(childA, childB, childC);reparentChild and reparentChildAt move children to a new parent while preserving their world transform, so the visuals don't jump. reparentChild appends to the end and accepts multiple children; reparentChildAt inserts at a specific index and accepts one child.
If you must do this manually (for example, to batch with other transform work), convert through getGlobalPosition and toLocal:
const globalPos = child.getGlobalPosition();
newParent.addChild(child);
child.position = newParent.toLocal(globalPos);Plain addChild keeps the local transform, which means the visual position changes; reparentChild is almost always what you want.
Destroying sub-trees
branch.destroy({ children: true });destroy() unlinks a single node. destroy({ children: true }) recursively tears down the entire sub-tree. Always use { children: true } when removing a branch; otherwise you leak the child nodes and their textures.
Label-based tree navigation
const panel = new Container({ label: "panel" });
panel.addChild(new Container({ label: "header" }));
panel.addChild(new Container({ label: "body" }));
const header = panel.getChildByLabel("header");Use label for debug tooling and light-weight tree navigation. Don't use it for hot-path code; the getChildByLabel walk is O(n) per call.
Lifecycle events
Containers emit events for hierarchy changes, visibility changes, and destruction.
Parent-side events fire on the container whose children changed:
group.on("childAdded", (child, parent, index) => {
/* ... */
});
group.on("childRemoved", (child, parent, index) => {
/* ... */
});Child-side events fire on the child itself when its parent changes:
sprite.on("added", (parent) => {
/* ... */
});
sprite.on("removed", (oldParent) => {
/* ... */
});Property and lifecycle events:
container.on("visibleChanged", (visible) => {
/* ... */
});
container.on("destroyed", (container) => {
/* ... */
});visibleChanged fires whenever container.visible flips. destroyed fires inside destroy() after internal cleanup but before listeners are removed. By the time destroyed runs, position, scale, pivot, origin, skew, and parent have already been nulled, and children has been emptied (length 0, but the array reference itself is not nulled). Capture any container state you need before calling destroy(), not inside the event handler.
Common Mistakes
[CRITICAL] Adding children to a leaf
Wrong:
sprite.addChild(otherSprite);Correct:
const group = new Container();
group.addChild(sprite, otherSprite);Sprite, Graphics, Text, Mesh, ParticleContainer, DOMContainer, and GifSprite all set allowChildren = false. Adding children logs a deprecation warning and will become a hard error. Use a plain Container to group.
[HIGH] Destroying the parent without children: true
Wrong:
levelContainer.destroy();Correct:
levelContainer.destroy({ children: true });Plain destroy() only removes the parent. Its children become orphans; still in memory, still referencing textures. For a clean teardown, always pass { children: true }, and include texture: true / textureSource: true when you also want to release GPU memory.
[MEDIUM] Mutating children during iteration
Wrong:
parent.children.forEach((child) => {
if (shouldRemove(child)) parent.removeChild(child);
});Correct:
for (const child of [...parent.children]) {
if (shouldRemove(child)) parent.removeChild(child);
}removeChild splices the array, shifting indices. Iterating the live array misses elements or processes some twice. Snapshot before iterating.
API Reference
RenderLayer
RenderLayer decouples render order from the scene graph hierarchy. Objects keep their logical parent (for transforms) but render at the layer's position in the scene. Use layers to pull specific objects to a specific z-depth without reparenting; the classic "character is parented to the world, but draws on the UI overlay" problem.
Quick Start
const bgLayer = new RenderLayer();
const entityLayer = new RenderLayer();
const uiLayer = new RenderLayer();
app.stage.addChild(bgLayer, entityLayer, uiLayer);
const world = new Container();
app.stage.addChild(world);
const player = new Sprite(texture);
world.addChild(player); // logical parent for transforms
entityLayer.attach(player); // render positionThe player lives in world for transform purposes (so moving the world moves the player), but the renderer draws it in the order determined by entityLayer's position in the stage.
Core Patterns
attach / detach
layer.attach(sprite);
layer.attach(sprite1, sprite2, sprite3);
layer.detach(sprite);
layer.detachAll();Use attach() / detach() for layer membership; not addChild / removeChild. RenderLayer overrides the scene graph methods to throw errors.
Sorted layers
const sortedLayer = new RenderLayer({
sortableChildren: true,
sortFunction: (a, b) => a.position.y - b.position.y,
});
sortedLayer.attach(sprite1, sprite2, sprite3);Set sortableChildren: true on the layer (not the object) and optionally pass a custom sortFunction. The default sort is by zIndex. Common use: y-sort for 2D top-down games so objects draw in depth order based on world y-position.
Manual sort
const layer = new RenderLayer({ sortableChildren: false });
layer.attach(sprite1, sprite2, sprite3);
layer.sortRenderLayerChildren();If you turn off automatic sorting, call sortRenderLayerChildren() manually when you want the sort to happen. Useful when the sort order changes less often than every frame.
Layer + logical parent
const world = new Container();
const hud = new RenderLayer();
app.stage.addChild(world, hud);
const player = new Sprite(playerTexture);
world.addChild(player);
hud.attach(player);
world.x = 100; // player still moves with worldThe player's transform chain goes through world; moving world.x moves the player visually. But the renderer places the player's draw call at hud's position in the scene graph, so the player renders on top of everything else.
Removing from scene graph auto-detaches
world.removeChild(player);
// player is automatically detached from entityLayerWhen you remove an object from its scene graph parent (via removeChild), the render layer attachment is cleared automatically. Re-adding the object to a scene graph parent does NOT re-attach it; you must call layer.attach(player) again.
Common Mistakes
[CRITICAL] Using addChild on a RenderLayer
Wrong:
const layer = new RenderLayer();
layer.addChild(sprite);Correct:
const layer = new RenderLayer();
container.addChild(sprite);
layer.attach(sprite);RenderLayer throws on addChild. The object still needs a real scene graph parent (via addChild on a Container) for transforms; use layer.attach() to control render order.
[HIGH] Layer and attached children in different render groups
Wrong:
const renderGroup = new Container({ isRenderGroup: true });
const layer = new RenderLayer();
renderGroup.addChild(sprite);
app.stage.addChild(layer);
layer.attach(sprite);Correct:
const renderGroup = new Container({ isRenderGroup: true });
const layer = new RenderLayer();
renderGroup.addChild(sprite);
renderGroup.addChild(layer);
layer.attach(sprite);A layer and the objects it attaches must be in the same render group. Otherwise the attached child renders without its correct transform composition.
[MEDIUM] Expecting parent filters to apply to layer children
Filters on an ancestor Container are applied by capturing the container's children into a texture via push/pop. Layer-attached children skip this capture; they're collected separately and drawn at the layer's position. If you need a filter on a layer-attached object, apply the filter directly to the object (not the ancestor).
API Reference
Masking
Clip display objects with PixiJS masks. PixiJS supports four mask types: AlphaMask (sprite-based), StencilMask (Graphics/Container-based via the stencil buffer), ScissorMask (axis-aligned rectangle), and ColorMask (bit-mask on channels).
Quick Start
const photoGroup = new Container();
photoGroup.addChild(new Sprite(await Assets.load("photo.png")));
const mask = new Graphics().circle(100, 100, 80).fill(0xffffff);
photoGroup.mask = mask;
photoGroup.addChild(mask);
app.stage.addChild(photoGroup);A mask should live in the scene graph of the masked object's parent (typically as a child of the masked Container) so its transform tracks the masked subtree. Sprites, Text, and other leaves cannot hold the mask as a child (allowChildren = false); wrap them in a Container as shown above.
Set container.mask to a display object. PixiJS automatically selects the mask type based on what you assign.
Core Patterns
Mask type selection
PixiJS picks the mask type automatically based on the mask object:
| Mask object | Type used | Cost | Notes |
|---|---|---|---|
| Graphics or Container | StencilMask | Medium | Uses stencil buffer |
| Sprite | AlphaMask | Expensive | Uses filter pipeline internally |
Number (e.g., 0xF) | ColorMask | Cheapest | Bitmask on RGBA channels |
Performance hierarchy: ColorMask (cheapest) < StencilMask < AlphaMask (most expensive). Choose the simplest mask that achieves the visual result.
Note: ScissorMask exists in the codebase but is not auto-selected by the mask system. Only AlphaMask, StencilMask, and ColorMask are registered as mask effects.
Stencil mask (Graphics-based)
const container = new Container();
const mask = new Graphics().roundRect(0, 0, 200, 150, 20).fill(0xffffff);
container.mask = mask;
container.addChild(mask);The mask Graphics should be a child of the masked container (or share the same coordinate space). The fill color doesn't matter; only the shape is used.
Alpha mask (Sprite-based)
const maskTexture = await Assets.load("gradient-mask.png");
const maskSprite = new Sprite(maskTexture);
const photoGroup = new Container();
photoGroup.addChild(new Sprite(await Assets.load("photo.png")));
photoGroup.mask = maskSprite;
photoGroup.addChild(maskSprite);Alpha masks use the red channel of the sprite texture by default to control visibility. High red value = fully visible, zero red = hidden. This is the most expensive mask type because it uses the filter pipeline internally.
Mask channel selection
By default, sprite (alpha) masks read the red channel. If your mask texture uses transparency instead (e.g., a PNG with an alpha gradient), switch to the alpha channel via setMask:
const maskSprite = new Sprite(await Assets.load("alpha-gradient.png"));
const photoGroup = new Container();
photoGroup.addChild(new Sprite(await Assets.load("photo.png")));
photoGroup.setMask({ mask: maskSprite, channel: "alpha" });
photoGroup.addChild(maskSprite);Available channels: 'red' (default), 'alpha'. This is useful when a single mask texture encodes different shapes in different channels.
Inverse masking
Use setMask with inverse: true to show everything outside the mask shape:
const holeMask = new Graphics().circle(100, 100, 80).fill(0xffffff);
const container = new Container();
container.setMask({ mask: holeMask, inverse: true });
container.addChild(holeMask);
const maskSprite = new Sprite(await Assets.load("mask.png"));
const photoGroup = new Container();
photoGroup.addChild(new Sprite(await Assets.load("photo.png")));
photoGroup.setMask({ mask: maskSprite, inverse: true });
photoGroup.addChild(maskSprite);Both alpha and stencil masks support inverse on WebGL and WebGPU. Canvas2D does not support inverse stencil masks (it logs a warning and ignores the flag).
Removing a mask
container.mask = null;
container.mask = null;
mask.destroy();Always use container.mask = null to clear a mask. setMask({ mask: null }) does not work due to an internal falsy check. Always remove the mask reference before destroying either the mask or the masked object.
Common Mistakes
[HIGH] Using cacheAsTexture with masks
The combination of cacheAsTexture() and masks is fragile. In Firefox, it can require a timeout between setting the mask and enabling caching. In other cases it fails silently. Avoid combining them when possible, or test thoroughly across browsers if needed.
[MEDIUM] Using too many sprite masks
Sprite masks (AlphaMask) use the filter pipeline internally, making them the most expensive mask type. Using many of them simultaneously degrades performance significantly. Prefer stencil masks (Graphics shapes) or scissor masks (axis-aligned rectangles) when the visual result allows it.
Performance cost: Scissor (near zero) < Stencil (one extra draw) < Alpha (full filter pass)
API Reference
Render Groups
A render group is a Container whose transform (position, scale, rotation, alpha, tint) is applied on the GPU as a single operation rather than being recalculated per-child on the CPU every frame. Use render groups for large stable sub-trees such as a game world, a HUD, or a parallax strip. Each render group owns its own instruction set and cannot batch with other groups.
Quick Start
const world = new Container({ isRenderGroup: true });
const hud = new Container({ isRenderGroup: true });
app.stage.addChild(world, hud);
for (let i = 0; i < 5000; i++) {
const bunny = new Sprite(bunnyTexture);
bunny.x = Math.random() * 2000;
bunny.y = Math.random() * 2000;
world.addChild(bunny);
}
world.x = 100; // applied once on the GPU, not 5000 times on the CPUFlag a container with isRenderGroup: true at construction, or call enableRenderGroup() at runtime.
Core Patterns
Construction options
const world = new Container({ isRenderGroup: true });
const hud = new Container();
hud.enableRenderGroup();Both forms work. Pre-declare via the options object for static setups; call enableRenderGroup() when you want to flip the flag at runtime.
The three transform levels
When a render group is in the chain, PixiJS composes three matrices:
1. localTransform; from the container's own position, scale, rotation, pivot, skew. 2. groupTransform; the child's position relative to the render group it belongs to. 3. worldTransform; the scene-root-space matrix.
For children inside a render group, the CPU only maintains localTransform and groupTransform. The GPU applies the group's own worldTransform once, then draws all the children. This is the performance win: the per-child CPU matrix math is cut from "one full walk per frame" to "one walk when children move."
When to use render groups
Good fits:
- Game world with thousands of static entities.
- UI layer that mostly stays put.
- Parallax background strips.
- A menu or HUD overlay that scales or translates as a whole.
Bad fits:
- Small sub-trees (a dozen children). Overhead outweighs the save.
- Constantly-changing structure (children being added / removed per frame).
- Sub-trees that need to batch with other objects outside the group.
Profile before adding
// Start without explicit render groups
const world = new Container();
// Measure frames, identify bottleneck
// Add if CPU transform updates dominate
world.enableRenderGroup();Most scenes don't need explicit render groups beyond the auto-created root. The scene root container passed to renderer.render() is automatically a render group. Add explicit groups only when profiling shows CPU transform costs dominating.
Render groups vs cacheAsTexture
const hud = new Container();
hud.cacheAsTexture({ antialias: true });
const world = new Container({ isRenderGroup: true });cacheAsTexture()rasterizes the sub-tree once to a texture and draws that texture each frame. Best for things that don't change visually.isRenderGroupkeeps normal drawing but moves the group transform work to the GPU. Best for sub-trees whose children animate freely but whose structure is stable.
Render groups are lighter than cacheAsTexture and preserve dynamic children. Use cacheAsTexture only when the sub-tree is genuinely static.
Common Mistakes
[MEDIUM] Overusing render groups
Wrong:
for (const enemy of enemies) {
enemy.enableRenderGroup();
}Each render group has its own instruction set and can't batch with other groups. A scene with many small render groups generates many separate draw buckets, which is slower than a single batched draw. Apply render groups at a coarse level (world, HUD), not per-entity.
[HIGH] Nesting many render groups
Wrong:
const world = new Container({ isRenderGroup: true });
const section = new Container({ isRenderGroup: true });
const tile = new Container({ isRenderGroup: true });
world.addChild(section);
section.addChild(tile);Correct:
const world = new Container({ isRenderGroup: true });
const section = new Container();
const tile = new Container();
world.addChild(section);
section.addChild(tile);Deep nesting of render groups multiplies the instruction set overhead. Pick a single level (usually one per "subsystem") and let the children be normal containers.
[MEDIUM] Constantly adding/removing from a render group
If the sub-tree structure changes every frame (not just child transforms), the render group's instruction set is rebuilt each time, negating the performance benefit. Render groups help stable structures with animated children; not dynamic structures.
API Reference
Scene Management: Render Groups, Layers, and Culling
Organize PixiJS scenes with render groups (GPU-level transforms), RenderLayer (decoupled render order), culling (offscreen optimization), zIndex sorting, and boundsArea optimization.
Quick Start
const world = new Container({ isRenderGroup: true });
const hud = new Container({ isRenderGroup: true });
app.stage.addChild(world, hud);
const bgLayer = new RenderLayer();
const entityLayer = new RenderLayer();
const uiLayer = new RenderLayer();
app.stage.addChild(bgLayer, entityLayer, uiLayer);
const player = new Sprite(texture);
world.addChild(player);
entityLayer.attach(player);Core Patterns
Render groups for GPU-level transforms
A render group is a container whose transform (position, scale, rotation, alpha, tint) is applied on the GPU as a single operation rather than being recalculated per-child on the CPU. The root container passed to renderer.render() is automatically a render group.
const gameWorld = new Container({ isRenderGroup: true });
const uiLayer = new Container({ isRenderGroup: true });
scene.addChild(gameWorld, uiLayer);
gameWorld.x += 10;
const panel = new Container();
panel.enableRenderGroup();Use render groups for large stable sub-trees (the structure stays the same even though children move/rotate). Children inside can still animate freely; "stable" means children are not being constantly added/removed.
The scene has three matrix levels:
1. localTransform - based on the container's own position/scale/rotation 2. groupTransform - relative to the render group it belongs to 3. worldTransform - relative to the scene root
RenderLayer for decoupled render order
RenderLayer separates render order from scene graph hierarchy. Objects keep their logical parent for transforms but render at the layer's position in the scene.
const bgLayer = new RenderLayer();
const entityLayer = new RenderLayer();
const uiLayer = new RenderLayer();
app.stage.addChild(bgLayer, entityLayer, uiLayer);
const player = new Sprite(texture);
const world = new Container();
world.addChild(player);
entityLayer.attach(player);
entityLayer.detach(player);
entityLayer.detachAll();
const sortedLayer = new RenderLayer({
sortableChildren: true,
sortFunction: (a, b) => a.position.y - b.position.y,
});Key constraints:
addChild()/removeChild()on RenderLayer throws an error. Useattach()/detach().- Objects removed from their scene graph parent via
removeChild()are automatically detached from their layer. - Re-adding an object to the scene graph does NOT automatically re-attach it to the layer. You must call
attach()again. - Layers and their children must belong to the same render group.
Culling
Culling skips rendering objects outside the visible area. In v8, culling is manual; it does not happen automatically during render.
const stage = new Container();
for (let i = 0; i < 1000; i++) {
const sprite = Sprite.from("bunny.png");
sprite.x = Math.random() * 5000;
sprite.y = Math.random() * 5000;
sprite.cullable = true;
stage.addChild(sprite);
}
const view = { x: 0, y: 0, width: 800, height: 600 };
Culler.shared.cull(stage, view);
renderer.render(stage);For automatic culling with Application, register CullerPlugin:
import { extensions, CullerPlugin } from "pixi.js";
extensions.add(CullerPlugin);
const app = new Application();
await app.init({ width: 800, height: 600 });Set cullArea on a container to define a custom cull region instead of computing bounds:
container.cullArea = new Rectangle(0, 0, 1000, 1000);
container.cullable = true;zIndex sorting
const parent = new Container({ sortableChildren: true });
const bg = new Sprite(bgTexture);
bg.zIndex = 0;
const player = new Sprite(playerTexture);
player.zIndex = 10;
const fg = new Sprite(fgTexture);
fg.zIndex = 20;
parent.addChild(fg, bg, player);Setting zIndex on a child automatically enables sortableChildren on its parent, so the explicit constructor option is only needed when you want sorting enabled before any child sets a zIndex.
boundsArea optimization
Setting boundsArea on a container prevents recursive bounds measurement of all children. Useful for containers with many children whose aggregate bounds are known.
const particles = new Container();
particles.boundsArea = new Rectangle(0, 0, 800, 600);Common Mistakes
MEDIUM: Overusing render groups
Each render group has its own instruction set and cannot batch with other groups. Use render groups at a broad level (game world, HUD), not per-child. Profile before adding them; most scenes do not need any explicit render groups beyond the auto-created root.
HIGH: Expecting automatic culling
v8 culling is explicit. Setting cullable=true only marks the container as eligible. You must call Culler.shared.cull() before rendering, or register CullerPlugin for automatic culling with Application.
MEDIUM: Expecting filters on ancestors to apply to layer children
RenderLayer children are rendered outside their parent's filter scope. Filters capture children into a texture via push/pop, but layer-attached children skip their parent's collection and render at the layer's position instead. Apply filters directly to the child when using RenderLayer.
HIGH: Using addChild on RenderLayer
RenderLayer overrides addChild, removeChild, and related methods to throw errors. Use attach() and detach() for layer membership. The object still needs a scene graph parent via addChild on a regular Container for transforms.
API Reference
Transforms and Coordinate Spaces
Every Container has a transform: position, scale, rotation, pivot, skew, and origin. These combine into a localTransform matrix, which compounds with ancestors to produce the worldTransform. Use this reference for coordinate-space conversion and the details of setting vs. animating transforms. For matrix math, see pixijs-math.
Quick Start
const hero = new Container();
hero.x = 100;
hero.y = 200;
hero.scale.set(2);
hero.rotation = Math.PI / 4;
const worldPoint = hero.toGlobal({ x: 0, y: 0 });
const localPoint = hero.toLocal({ x: 300, y: 300 });Transforms are local to the parent. toGlobal and toLocal convert between coordinate spaces.
Core Patterns
Position, scale, rotation
container.x = 100;
container.y = 200;
container.position.set(100, 200);
container.scale.set(2); // uniform
container.scale.set(1.5, 0.8); // non-uniform
container.scale.x = 2;
container.rotation = Math.PI / 4; // radians
container.angle = 45; // degrees (convenience)position, scale, and pivot are ObservablePoint instances; direct assignment via x / y works, or call set(x, y?). Setting them triggers the parent's transform update flag.
Pivot
sprite.pivot.set(sprite.width / 2, sprite.height / 2);
sprite.x = 200;
sprite.y = 200;
sprite.rotation += 0.01;pivot is the point around which rotation and scale apply; in the container's own local pixel space. A pivot of (w/2, h/2) rotates around the center.
For Sprite, prefer anchor (normalized 0–1) which only shifts the texture draw origin without offsetting the position. Pivot shifts the position too.
Origin (new in v8)
container.origin.set(100, 50);origin is an alternative transform origin that shifts the rotation/scale pivot without offsetting the visual position. Think of it as "pivot, but without the position shift." Useful when you want to spin a large container around an off-center point while keeping it visually anchored to its x / y.
Skew
container.skew.set(0.1, 0);Rarely used. Skew shears the coordinate space by x / y radians. Useful for fake perspective and isometric effects.
Coordinate conversion
const globalPoint = container.toGlobal({ x: 10, y: 20 });
const localPoint = container.toLocal({ x: 400, y: 300 });
const localFromSprite = container.toLocal({ x: 0, y: 0 }, otherSprite);
const cached = container.getGlobalPosition();toGlobal(localPoint): convert from this container's local space to global (scene root) space.toLocal(point, from?): convert from global (or another container's) space to this container's local space.getGlobalPosition(): shortcut fortoGlobal({ x: 0, y: 0 }); this container's origin in global space.
All three accept an optional point argument to write into (reduces allocation in hot paths) and a skipUpdate flag that uses the cached transform (faster but can be stale).
The three matrices
container.localTransform; // from this node's own position/scale/rotation/pivot/skew
container.groupTransform; // relative to the render group it belongs to
container.worldTransform; // relative to the scene rootEach frame, PixiJS composes these three levels:
1. localTransform; derived from the container's own transform setters. 2. groupTransform; if an ancestor is a render group, this is the transform relative to that group. 3. worldTransform; relative to the scene root; what the renderer consumes.
For normal code, set x / y / scale / rotation and let the renderer compose the rest. Reading worldTransform directly is useful for hit testing, custom rendering, or integrating with physics.
Sizing via width, height, setSize
container.width = 100;
container.height = 200;
container.setSize(100, 200);
const { width, height } = container.getSize();Setting width and height scales the container to fit the requested dimensions; internally they assign scale.x / scale.y based on the measured bounds. Assigning the two setters independently re-measures the subtree twice, which is wasteful. Use setSize(width, height?) to assign both in a single pass, and getSize() to read them back as { width, height }.
Bounds in different spaces
const localBounds = container.getLocalBounds();
const globalBounds = container.getBounds();
const reusable = new Bounds();
container.getBounds(true, reusable); // skipUpdate + in-place
const rect = container.getBounds().rectangle;
rect.contains(pointerX, pointerY);getLocalBounds(): rectangle in the container's own local space.getBounds(skipUpdate?, bounds?): rectangle in world space (accounts for all ancestor transforms). PasstrueforskipUpdateto reuse the cached transform instead of forcing a fresh pass, and pass an existingBoundsinstance as the second argument to avoid allocating a new one.
Both methods return a Bounds instance, not a Rectangle. Bounds exposes x, y, width, and height getters plus a .rectangle accessor for APIs like Rectangle.contains().
Bounds are recalculated lazily. Call them sparingly; each call walks the container's sub-tree. For containers with many cheap children (particles, tile layers), set container.boundsArea = new Rectangle(0, 0, w, h) so getBounds() returns that rectangle directly without recursing.
Global, local, and screen coordinates
PixiJS uses three coordinate spaces:
- Local: relative to the object's parent. This is what
container.x/container.yandposition.set()always operate on. - Global (a.k.a. "world"): relative to the scene root.
toGlobal(),toLocal(), andgetGlobalPosition()convert between local and global space. - Screen (a.k.a. "viewport"): relative to the top-left of the canvas element PixiJS is rendering into. DOM events and native mouse clicks use screen space.
Global and screen usually match, but they diverge when the canvas is CSS-scaled or rendered at a non-1 resolution. PixiJS's event system handles this conversion automatically for pointer events, so onPointerDown handlers always receive global coordinates. When you need raw screen coordinates, read renderer.events.pointer; use toLocal / toGlobal for the world side.
Cumulative alpha (getGlobalAlpha)
container.alpha is local to the container; the cumulative alpha (parent × this × ancestors) is exposed via container.getGlobalAlpha(skipUpdate?). It's computed during render, so reading it before the first frame returns 1 on a freshly-added subtree. Useful for custom render hooks that need to know how transparent the renderer will actually draw this node.
Common Mistakes
[HIGH] Setting scale to 0
Wrong:
container.scale.set(0);Correct:
container.scale.set(0.01); // or use visible = false
container.visible = false;Scale of exactly 0 collapses the transform matrix to zero, and any subsequent toLocal / toGlobal / inverse calculations divide by zero. Use visible = false to hide, or scale to a tiny non-zero value if you need an animation.
[MEDIUM] Reading world transform before first render
Wrong:
const pos = container.getGlobalPosition();
console.log(pos); // { x: 0, y: 0 } even though container.x is setCorrect:
app.stage.addChild(container);
await new Promise((r) => requestAnimationFrame(r));
const pos = container.getGlobalPosition();worldTransform is computed during render. Before the first render, it's identity. If you need a live position before rendering, use container.toGlobal({x:0, y:0}) which walks the transforms explicitly.
[MEDIUM] Confusing anchor and pivot
On Sprite, anchor is normalized (0–1) and shifts only the draw origin. pivot is in pixel space and shifts both the transform origin AND the visual position. For centering a sprite, use anchor. For off-center rotation of a Container (which has no anchor), use pivot.
API Reference
Related skills
How it compares
Pick pixijs-scene-core-concepts when refactoring PixiJS initialization style; use broader PixiJS architecture skills when designing full scene systems or render pipelines.
FAQ
Can a Sprite have children in v8?
No. Leaves must not hold children; wrap groups in a Container instead.
When use RenderLayer?
When draw order must decouple from logical hierarchy, such as UI over a game world.
What does isRenderGroup do?
It applies the subtree transform on the GPU instead of recomputing every descendant each frame.
Is Pixijs Scene Core Concepts safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.