PixiJS is built as a set of swappable parts called extensions. Renderers, asset loaders, event systems, and application plugins are all extensions. You can add your own, replace built-in ones, or remove ones you don't need. This is how PixiJS stays lightweight while supporting a wide range of features.
Register an extension using the global extensions object:
import { extensions, ExtensionType } from 'pixi.js';
const spriteSheetLoader = {
extension: {
type: ExtensionType.LoadParser,
name: 'sprite-sheet-loader',
priority: 100,
},
id: 'spriteSheet', // Unique name; the renderer/loader uses this to look up the extension
test(url: string) {
return url.endsWith('.json');
},
async load(url: string) {
const response = await fetch(url);
return response.json();
},
};
extensions.add(spriteSheetLoader);
// Single extension
extensions.add(spriteSheetLoader);
// Multiple extensions
extensions.add(spriteSheetLoader, customSystem, customPipe);
// Single extension
extensions.remove(spriteSheetLoader);
// Multiple extensions
extensions.remove(spriteSheetLoader, customSystem);
Extensions can specify a priority to control execution order. Higher priority numbers run first. The default priority is -1 when unspecified.
const highPriorityLoader = {
extension: {
type: ExtensionType.LoadParser,
name: 'priority-loader',
priority: 100, // runs first
},
};
const lowPriorityLoader = {
extension: {
type: ExtensionType.LoadParser,
name: 'fallback-loader',
priority: -1, // runs last
},
};
Asset extensions handle loading, resolving, caching, and detecting assets. You can combine them into a single Asset group extension using the loader, resolver, cache, and detection properties:
const spriteAsset = {
extension: ExtensionType.Asset,
loader: {
id: 'spriteLoader',
test(url: string) { return url.endsWith('.sprite'); },
async load(url: string) { /* load logic */ },
},
resolver: {
test(url: string) { return url.endsWith('.sprite'); },
parse(url: string) { /* resolve logic */ },
},
cache: {
test(asset: any) { return asset.isSprite; },
getCacheableAssets(keys: string[], asset: any) { /* cache logic */ },
},
detection: {
test: async () => true,
add: async (formats: string[]) => [...formats, 'sprite'],
remove: async (formats: string[]) => formats.filter((f) => f !== 'sprite'),
},
};
Rendering extensions define how objects are drawn. Systems manage specific rendering aspects (textures, shaders, buffers), while pipes handle rendering for specific display object types.
Extensions can target WebGL, WebGPU, or both:
// Renderer system targeting both WebGL and WebGPU
class CustomSystem {
static extension = {
type: [ExtensionType.WebGLSystem, ExtensionType.WebGPUSystem],
name: 'custom-system',
};
init() { /* setup */ }
destroy() { /* cleanup */ }
}
// Render pipe targeting all renderers
const customPipe = {
extension: {
type: [
ExtensionType.WebGLPipes,
ExtensionType.WebGPUPipes,
ExtensionType.CanvasPipes,
],
name: 'custom-pipe',
},
render(object) { /* draw logic */ },
};
A renderer loader is an async hook the renderer awaits while it initialises, after the environment extensions load and before it creates its systems and pipes. Use one to import() the systems and pipes that only one backend needs, so the other backends' code stays out of the main bundle. The imported module registers them with extensions.add, and the renderer picks them up once every loader for its backend has resolved.
// Each backend gets its own loader; only the loaders for the renderer in use run
extensions.add(
{
extension: { type: ExtensionType.WebGLLoader, name: 'my-plugin' },
load: () => import('./gl/register'), // registers WebGL systems and pipes on import
},
{
extension: { type: ExtensionType.WebGPULoader, name: 'my-plugin' },
load: () => import('./gpu/register'),
},
);
Loader names are unique per backend; a second loader registered under the same name is ignored. Loaders run concurrently, so don't rely on one plugin's module evaluating before another's; the systems and pipes they register are ordered by their own priority. Loaders are skipped when the renderer is created with skipExtensionImports: true, so custom builds must import the backend modules themselves.
Application plugins extend the core Application class. Plugins use static init and destroy methods. During init, this refers to the Application instance:
class MyPlugin {
static extension = ExtensionType.Application;
static init(options) {
// `this` is the Application instance
Object.defineProperty(this, 'myFeature', {
value: () => { /* feature logic */ },
});
}
static destroy() {
// cleanup
}
}
Plugins initialize in registration order and destroy in reverse order.
ExtensionType.Application: Application pluginsExtensionType.Environment: Environment configurationExtensionType.Asset: Combined asset handlingExtensionType.LoadParser: Resource loadingExtensionType.ResolveParser: URL resolutionExtensionType.CacheParser: Cache managementExtensionType.DetectionParser: Format detectionExtensionType.WebGLSystem: WebGL systemsExtensionType.WebGLPipes: WebGL render pipesExtensionType.WebGLPipesAdaptor: WebGL render pipe adaptorsExtensionType.WebGLLoader: Async hooks awaited while a WebGL renderer initialisesExtensionType.WebGPUSystem: WebGPU systemsExtensionType.WebGPUPipes: WebGPU render pipesExtensionType.WebGPUPipesAdaptor: WebGPU render pipe adaptorsExtensionType.WebGPULoader: Async hooks awaited while a WebGPU renderer initialisesExtensionType.CanvasSystem: Canvas systemsExtensionType.CanvasPipes: Canvas render pipesExtensionType.CanvasPipesAdaptor: Canvas render pipe adaptorsExtensionType.CanvasLoader: Async hooks awaited while a Canvas renderer initialisesThese extension types are for specialized use cases. Most applications won't need them.
ExtensionType.MaskEffect: Custom masking strategiesExtensionType.BlendMode: Custom blend mode implementationsExtensionType.TextureSource: Auto-detect and create texture sources from raw dataExtensionType.ShapeBuilder: Add new shape types to the Graphics APIExtensionType.Batcher: Custom batch grouping for draw call optimization