Library / SDK
playcanvas/react avatar
playcanvas/react

@playcanvas/react puts the PlayCanvas engine behind React components

Build 3D apps with React

544 stars60 forksTypeScriptMIT

At a glance

What is it?
@playcanvas/react is a thin wrapper around the PlayCanvas engine that lets you describe a 3D scene as a React tree. The engine still does the rendering, the physics and the asset pipeline, and the wrapper's job is to make the entity component system look like the component model React developers already use.
Who is it for?
Adopt @playcanvas/react if you want the PlayCanvas engine inside a React application and you prefer describing the scene as components to driving the engine imperatively. Skip it if you need a 3D product outside the web, since this targets the browser, and if you want a rendering engine of your own rather than a wrapper around someone else's.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 25 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A wrapper, deliberately

The package is described as a full-featured library for interactive 3D in React, and the first thing the documentation says about it is that it is a thin wrapper around PlayCanvas, which is called a battle-tested, real-time 3D engine.

The word thin is doing real work in that sentence, and it is the right way to think about the project. This is not a 3D engine written for React. It is the PlayCanvas engine, which has been running real-time 3D in browsers for years, with a React-flavoured API layer on top.

That architecture has an obvious benefit and an obvious cost, and the documentation is straightforward about the benefit. The package gets you building fast, without the usual mess of wiring together separate libraries for physics, input or asset management. It is a complete, batteries-included toolkit for interactive 3D experiences in React.

So the problem being solved is not that React lacks 3D. It is that a 3D application in the browser usually needs an engine, and an engine does not come with the things a React application expects. It does not have components, it does not suspend while it loads, it does not take pointer events as React props. The wrapper supplies those adapters.

The cost is that anything the wrapper has not adapted is still engine API, and you are writing React that imperatively drives a 3D engine rather than writing a 3D engine in React.

The package is MIT licensed, TypeScript, with the default branch main and the last push on 2026-09-28. The recent releases are @playcanvas/react at 0.11.7 and 0.11.6 and @playcanvas/blocks at 0.3.11, all from 2026-09-25, which tells you there are two published packages and that both are moving.

The scene API in one component tree

The code example is the clearest possible statement of what the wrapper does, and it is short.

jsx
import { Application, Entity } from '@playcanvas/react';
import { Camera, Render } from '@playcanvas/react/components';
import { OrbitControls } from '@playcanvas/react/scripts';

export function AssetViewer() {
  return (
    <Application>
      <Entity position={[0, 2, 0]}>
        <Camera />
        <OrbitControls />
      </Entity>
      <Render type="sphere"/>
    </Application>
  );
}

Three imports, and each one names a different layer. The Application and Entity come from the package root. The Camera and Render come from the components subpath. The OrbitControls comes from the scripts subpath.

That structure tells you the design. An Application is the root and it owns the engine instance, the canvas and the render loop. An Entity is a position in the scene graph, and its position prop is an array of coordinates rather than three separate props. A Camera is a component that creates a camera entity. And Render is a component that attaches a primitive or a model to its parent entity.

OrbitControls is in the scripts subpath, and that is the interesting one. It is not a component that renders anything; it is a script that gets attached to the camera entity and drives it. The engine has a script system for behaviour that is not a static part of the scene, and the wrapper exposes it as a component you nest.

So the underlying model is visible in the example. The PlayCanvas engine is an entity component system, and the wrapper's contribution is to let you express that tree as nested React elements, with props as the property values.

The example also shows the model viewer pattern, which is the shape most React 3D code takes: a camera entity with orbit controls inside it, and the rendered content beside it as a sibling. When the user drags, the camera moves and the model stays put, which is exactly what a model viewer should do.

Six features, and what they map onto

The feature list is six items, and reading them as engine concepts makes them clearer than reading them as marketing.

The simple scene API is the one the example demonstrates. The suspenseful asset loading is the one that matters most for React specifically: because React has a Suspense boundary and the engine has an asynchronous asset pipeline, the wrapper bridges the two so that a component can suspend while its model is loading rather than showing nothing or requiring manual loading state. The documentation has a dedicated guide on loading a 3D model.

Pointer events are the second React-shaped feature. The engine has its own input system, and the wrapper maps pointer interaction onto React's synthetic event model so that handlers attach to entities the way they attach to DOM elements. The interaction guide covers it, and the feature list for the library notes interactivity as a first-class topic rather than an afterthought.

Physics out of the box is the engine's physics system exposed without you having to wire it up, and there is a physics example in the documentation. The script component is the mechanism the OrbitControls in the example uses, so custom behaviour attaches the same way the built-in ones do.

And the last item, the entity component system, is listed as a feature rather than hidden, which is honest: the wrapper does not hide the engine's model, it exposes it through a React-shaped syntax.

The examples listed at the end of the documentation show the coverage in practice. Building a scene, loading a 3D model, interaction, the model viewer, motion, physics and splats, which has its own API page.

That last one is worth a note. The repository topics include gaussian and splats, and the GSplat API page is linked separately from the others. Gaussian splats are a way of representing a scene as a set of anisotropic gaussians rather than as triangles, which is how you show a photogrammetry capture in a browser. The PlayCanvas engine supports them, and the wrapper gives them a React-facing API.

Getting started, and the AI rules for your editor

There are three routes in, and they differ in how much setup you take on.

The fastest is the StackBlitz starter, which is what the getting started section points at first, and there is a tick tock clock example built in it. A browser-based starter means you can see the wrapper working in about a minute without installing anything, which is the right first step for a library whose whole appeal is how quickly you get something on screen.

Then there is the package install:

bash
npm install @playcanvas/react playcanvas

Note that it is two packages, @playcanvas/react and playcanvas. The engine is a peer dependency in practice, which is the right arrangement: the wrapper and the engine version together determine whether the adapter still matches.

And there is a starter template you can clone:

bash
git clone https://github.com/marklundin/playcanvas-react-template.git

The repository also ships a set of editor rules for Cursor, which is a newer idea and worth describing precisely. To get your IDE up to speed, install the PlayCanvas React rules, and the command is:

bash
mkdir -p .cursor/rules && curl -s https://raw.githubusercontent.com/playcanvas/react/main/packages/lib/.playcanvas-react.mdc -o .cursor/rules/playcanvas-react.mdc

That creates a .cursor/rules directory and downloads a .playcanvas-react.mdc file into it. The file lives in the repository at packages/lib/.playcanvas-react.mdc, so it is a versioned artefact that changes with the library rather than a static document somebody wrote once.

That matters more than it might appear. A wrapper around an engine has a large surface of correct usage that is not discoverable from the type signatures alone, particularly around when to use a component and when to reach for the engine API directly. Handing that knowledge to an editor as a rules file is a pragmatic way to close the gap, and versioning it alongside the code means it does not go stale.

The rest of the documentation is organised into a getting started guide, a set of example pages and a full API reference, all hosted on the PlayCanvas developer site.

Two packages, and a modern toolchain

The contributing section explains the repository layout in one sentence. The monorepo is split into two packages: @playcanvas/react, which is the main React library and lives in packages/lib, and @playcanvas/blocks, which holds high-level React components for common 3D use cases and lives in packages/blocks.

That split is the right one for this kind of project. The main library is the adapter between React and the engine, and it is small and stable. The blocks package is a set of ready-made components for the things people keep rebuilding, and it is where new user-facing surface area lands. A user who only needs the adapter does not take on the blocks.

The build instructions are pnpm-based. You run pnpm install from the root, then build each package with pnpm run build:lib or pnpm run build:blocks, and run the tests with pnpm test. The root package is private and named playcanvas-react-monorepo, the workspaces field points at packages/*, and the package manager is pinned to pnpm 12.6.0.

The scripts show the toolchain. Formatting is prettier, with a check script and a write script. Linting is eslint run across every workspace package. Testing is vitest run across every workspace package. And the build scripts use pnpm filters so each package is built by the package that owns it rather than by the root.

The dependency versions are worth noting because they show how current the project is. TypeScript is at 6.0.3, React types at 19.3.0, eslint at 9.39.5, prettier at 3.9.9 and vitest at 5.0.2. The eslint configuration is the flat config format, in an eslint.config.mjs file, and there is a playwright and vite plugin for react alongside it.

Two other dependencies point at specific practices. The eslint plugin for the React compiler is in the dev dependencies, so the code is linted against the rules of the React compiler. And pkg-pr-new is there, which matches the badge in the documentation header: it publishes a preview of every pull request as an installable package, so a reviewer can try a change without building it locally.

The rest of the repository is the standard set for this kind of project. There is a .changeset/ directory, so releases are driven by changeset files rather than by hand-edited changelogs. There is renovate.json for dependency updates. There is a pnpm-lock.yaml and a pnpm-workspace.yaml. And there is a vitest.config.ts and a tsconfig.json at the root.

Who is using it, and where it fits

The documentation names two users in production and invites you to add yours by opening a pull request.

The first is Snap AI, described as real-time 3D interfaces inside Snap's next-generation tools. That is a demanding use: a consumer messaging product embedding interactive 3D, where the framework has to behave like a normal part of the app rather than something bolted on. It is also the case where the wrapper earns its keep, because a chat interface that has to render a 3D avatar in the flow of a conversation needs to coexist with a React tree it does not control.

The second is GSplat Share, for sharing your splats with optional time-limited and password-protected links. That is a small, complete product built on the wrapper: the interesting part is the link sharing, and the 3D is the payload.

The comparison with other approaches in the browser is worth making directly. The alternative is to use the PlayCanvas engine directly, with no React layer, which is entirely reasonable and gives you the smallest possible overhead. The cost is that the scene graph is not a React tree, so you lose components, you lose suspense, and you lose the ability to put a 3D canvas inside a React application without an imperative bridge.

The other alternative is a React 3D library built on three.js or WebGL directly, where the scene is React-native. The difference is that those libraries are the engine, and they are usually thinner on the features an application needs beyond rendering, such as a physics system and a production asset pipeline. The PlayCanvas engine has those, and the wrapper is the price of admission.

So the trade is: React-shaped API and React interop, in exchange for one layer of indirection and a dependency on the engine's feature set. For a product where 3D is one component among many, that is usually the right side of the trade. For a 3D application with no meaningful React around it, the engine alone is simpler.

One thing to verify early is the version pairing. The wrapper and the engine are published separately and the adapter depends on the engine's API, so a mismatch between the two versions in your dependency tree is the first thing to check if something behaves oddly.

Editorial conclusion

Adopt @playcanvas/react if you want the PlayCanvas engine inside a React application and you prefer describing the scene as components to driving the engine imperatively. Skip it if you need a 3D product outside the web, since this targets the browser, and if you want a rendering engine of your own rather than a wrapper around someone else's. Verify first which of the two packages you need, since the high-level components live in @playcanvas/blocks rather than the main library, that the engine version you install matches what the wrapper expects, and how much of the engine you actually need, since the wrapper covers a specific set of features rather than the whole engine surface.

Frequently asked questions

What is @playcanvas/react and what does it add to the PlayCanvas engine?

It is a thin wrapper around the PlayCanvas engine that lets you describe a 3D scene as a React tree. The engine still does the rendering, physics and asset pipeline, and the wrapper supplies what a React application expects: a simple scene API, suspenseful asset loading, pointer events as React props, physics out of the box, a script component and the entity component system exposed through components.

How do I install and start a project with @playcanvas/react?

The fastest route is the StackBlitz starter, which has a tick tock clock example built in. Otherwise install with `npm install @playcanvas/react playcanvas`, which pulls in the engine alongside the wrapper, or clone the starter template repository. The documentation then has a getting started guide, example pages and a full API reference on the PlayCanvas developer site.

What is the difference between @playcanvas/react and @playcanvas/blocks?

The repository is a monorepo split into two packages. @playcanvas/react in packages/lib is the main React library and the adapter between React and the engine, while @playcanvas/blocks in packages/blocks holds high-level React components for common 3D use cases. To work on them, run pnpm install from the root, then pnpm run build:lib or pnpm run build:blocks, and pnpm test to run the tests.

Official sources

  1. License: MIT
  2. playcanvas/react on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/playcanvas-react.svg)](https://hysenlabs.com/projects/playcanvas-react)

Was this useful?