Open-source project
Tencent/puerts avatar
Tencent/puerts

PuerTS: one ScriptEnv, three script languages, two game engines

PuerTS: write your UE or Unity game logic in TypeScript.

6,268 stars870 forksC++NOASSERTION

At a glance

What is it?
Tencent's PuerTS puts a script runtime inside Unity or Unreal so gameplay code can be written in JavaScript, TypeScript, Lua or Python. Unity 3.0 folds those languages behind one ScriptEnv and a swappable Backend, while Unreal stays on JavaScript and TypeScript.
Who is it for?
PuerTS fits Unity teams that want gameplay scripting in TypeScript, Lua or Python behind a single ScriptEnv, and it fits Unreal teams only for JavaScript and TypeScript. Before adopting it, check three things: the install steps in doc/unity/en/install.md, the contents of LICENSE, since the license field reads NOASSERTION, and whether the PESAPI_VERSION:11 binding shipped in Unity_v3.0.3 covers the host APIs your project already calls.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

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

From daily snapshots

Star history

  • Tencent/puerts6,268 ★ · +59
Star history
Repository2026-09-232026-11-12
Tencent/puerts6.2k6.3k

59 Stars in the last 50 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Swapping the Backend is the whole Unity 3.0 migration

PuerTS 3.0 collapses three separate scripting setups into one host type: `ScriptEnv`. You construct it with a backend, hand it source through `env.Eval`, and release it with `env.Dispose`. For JavaScript and TypeScript the choices are V8, QuickJS or Node, and Unity 3.0 puts `BackendLua` and `BackendPython` next to `BackendV8`. Nothing else in the host call changes when the language does, because the engine types arrive over the same bridge: `CS.UnityEngine.Vector3` from JavaScript, `require('csharp')` from Lua, and `import UnityEngine.Vector3` inside a Python `exec` string.

That is why the three samples read almost identically. Only the backend constructor and the script body change:

csharp
using Puerts;
using UnityEngine;

void Start() {
    var env = new ScriptEnv(new BackendLua());
    env.Eval(@"
        local CS = require('csharp')
        local Vector3 = CS.UnityEngine.Vector3
        local Debug = CS.UnityEngine.Debug
        local pos = Vector3(1, 2, 3)
        Debug.Log('Hello from Lua! pos = ' .. pos:ToString())
    ");
    env.Dispose();
}

One detail in that sample is worth copying carefully: the call `pos:ToString()` uses Lua's colon syntax inside a block tagged `csharp`, so the fence names the host language rather than the script language held in the string. The Lua sample also reaches the same `Debug.Log` the JavaScript one does, which is the concrete form of the one API surface claim.

Unreal keeps the JavaScript only path that Unity left behind

The multi-language story is a Unity story. The language list carries a parenthetical limit: Unreal currently supports JavaScript and TypeScript only. `BackendLua` and `BackendPython` have no Unreal counterpart, and Puerts.AI, the agent layer described further down, is marked Unity only too. A studio shipping one Unreal title and one Unity title over a shared scripting layer cannot share the language half of that layer.

Unreal also runs on its own release line and its own document set. The README points at a release tag named `Unreal_v1.0.9`, at `doc/unreal/en/install.md`, `doc/unreal/en/faq.md`, `doc/unreal/en/changelog.md` and `doc/unreal/en/bugs.md`. Unity gets parallel files under `doc/unity/en/`, with one exception: the Unity changelog is not in `doc/` at all but sits inside the package at `unity/Assets/core/upm/changelog.md`. Two engines, two changelogs, two known-issue lists, and no single page that lines the two up against each other.

Five NuGet packages, and none of them reach Unreal

The .NET distribution is five packages on NuGet, one per runtime: `Puerts.V8.Complete`, `Puerts.NodeJS.Complete`, `Puerts.QuickJS.Complete`, `Puerts.Lua.Complete` and `Puerts.Python.Complete`. They differ only in the runtime named in the middle, and all five carry the same `Complete` suffix.

Their scope is the limit worth noticing. The README lists the NuGet packages separately from the Unreal and Unity release tags, and none of the five package names refers to Unreal.

The README hands installation to two documents and gives no command

Finding the install steps takes a detour. The top level README links to `doc/unreal/en/install.md` and `doc/unity/en/install.md`, and to a documentation site at `puerts.github.io/en`. The Unity changelog path, `unity/Assets/core/upm/changelog.md`, says where the Unity side lives, but the file that would give the add step, the prerequisites and a supported editor version is not in the repository root.

What you can copy today is the first call, which is the entire hello world in each language: build a `ScriptEnv`, evaluate a snippet that creates a `Vector3` and logs it, then dispose the environment.

csharp
using Puerts;
using UnityEngine;

void Start() {
    var env = new ScriptEnv(new BackendV8());
    env.Eval(@"
        const Vector3 = CS.UnityEngine.Vector3;
        const Debug = CS.UnityEngine.Debug;
        let pos = new Vector3(1, 2, 3);
        Debug.Log('Hello from JS! pos = ' + pos);
    ");
    env.Dispose();
}

What you should notice in the sample: it disposes the environment on the next line with no guard around `Eval`, so a script that throws during evaluation leaves cleanup to the caller. It also creates the environment inside `Start` and disposes it in the same call, which teaches nothing about keeping a `ScriptEnv` alive across frames.

Puerts.AI exposes one MCP tool and loads builtins when asked

Puerts.AI is a Unity only layer built on PuerTS that works in the editor and, by its own description, at game runtime. Its editor assistant comes in two shapes. The built-in agent version chats inside a Unity Editor window with no network round trip. The MCP version connects from Cursor, Windsurf, Claude Desktop or any MCP compatible client over HTTP/SSE, which is the version aimed at editing code and then checking the result in the running editor.

Three differences from other Unity MCP setups are named: the server runs inside the Unity process instead of a separate service, it registers a single tool and loads builtin modules on demand, and new capabilities arrive by dropping files into a resource directory. That directory has three parts:

text
Resources/my-agent/
├── system-prompt.md.txt      # Who the AI is and how it behaves
├── skills/                   # Domain knowledge documents (loaded on demand)
└── builtins/                 # Executable helper modules

The framework underneath is Puerts.Agent, and agents are not confined to the editor. A Smart NPC demo gives each character its own agent instance plus a personality driven system prompt, so dialogue follows player behaviour and conversation memory. A Maze Runner demo navigates a 3D maze by taking screenshots, reasoning about paths, issuing movement commands and verifying the result, using two builtin modules and a system prompt. Both demos live in separate repositories, so neither ships inside this one.

Two routes for speed, and neither one comes with numbers

Speed is promised along two separate routes. The general route is reflection: calls cross into the full engine with no hand written glue, across languages and platforms. The targeted route is static wrapper generation for the performance critical paths, offered across all supported languages. TypeScript declaration generation sits beside them, giving typed access to host engine APIs.

The WebGL claim is the sentence to read carefully. PuerTS is described as a large advantage in performance and development efficiency there, and as faster than pure C# in some cases. No benchmark, engine version or scene description comes with that sentence, so it is a claim to test in your own project rather than a number to plan around.

The same silence covers the wrappers. Nothing in the top level entries says which APIs receive a generated static wrapper, how a wrapper is requested for your own class, or what the reflection path costs per call. TypeScript users get the most from that gap being filled, since a declaration that does not exist is a compile error rather than a runtime surprise.

Three Unity 3.0 releases in 2026, all on PESAPI_VERSION 11

The release feed is the clearest signal available. Unity_v3.0.1 shipped 2026-03-04, Unity_v3.0.2 on 2026-04-01 and Unity_v3.0.3 on 2026-09-05, and all three carry `PESAPI_VERSION:11`. The binding API version held still while the package version moved twice, so on that measure a 3.0.1 to 3.0.3 upgrade does not cross an API boundary. The README also links older tags, Unity_v2.2.3 and Unreal_v1.0.9, which places the 2.x line below the 3.0 rework of `ScriptEnv`.

The repository is not archived, the default branch is `master`, and the last push landed on 2026-10-30. Changes are tracked per engine in changelog files rather than in release notes on the page: `doc/unreal/en/changelog.md` for Unreal and `unity/Assets/core/upm/changelog.md` for Unity. The practical cost of an upgrade is therefore split across two engines and two changelogs, and `PESAPI_VERSION` is the only compatibility number visible without opening either file.

A LICENSE file at the root, and a license field reading NOASSERTION

The repository root holds a LICENSE file, while the license metadata for the project reads NOASSERTION, a value that identifies no license by itself. The README links that file through its license badge. If redistribution terms matter to your studio, read `LICENSE` directly before shipping a build, because nothing on the project page settles the question.

Two other root entries shape expectations. `.clang-format` sits next to `doc/`, `unity/` and `unreal/`, which fits a codebase whose primary language is C++ even though the pitch is TypeScript first. A `.codebuddy/` directory also sits at the top level beside `.github/`, with no explanation of its contents anywhere in the README. The single external documentation link given is `puerts.github.io/en`, and the FAQ, install, changelog and known issue files for each engine are all reached by relative path from the root.

Editorial conclusion

PuerTS fits Unity teams that want gameplay scripting in TypeScript, Lua or Python behind a single ScriptEnv, and it fits Unreal teams only for JavaScript and TypeScript. Before adopting it, check three things: the install steps in doc/unity/en/install.md, the contents of LICENSE, since the license field reads NOASSERTION, and whether the PESAPI_VERSION:11 binding shipped in Unity_v3.0.3 covers the host APIs your project already calls.

Frequently asked questions

Which scripting languages can PuerTS run inside Unreal?

JavaScript and TypeScript only. The language list marks Unreal as JavaScript and TypeScript only, and the Lua and Python backends, `BackendLua` and `BackendPython`, belong to the Unity 3.0 `ScriptEnv`.

How do I install PuerTS into a Unity project?

The top level README points to `doc/unity/en/install.md`, to the documentation site `puerts.github.io/en`, and to the package changelog at `unity/Assets/core/upm/changelog.md`.

What does PESAPI_VERSION mean when I upgrade a Unity release?

Unity_v3.0.1, Unity_v3.0.2 and Unity_v3.0.3 all carry PESAPI_VERSION:11, so that binding version did not move across the three 2026 releases. The repository is not archived and its last push was on 2026-10-30.

Can a PuerTS agent run inside a game rather than only in the editor?

In Unity, yes. Puerts.AI works in the editor and at game runtime, the Smart NPC demo gives each character its own agent instance, and the Maze Runner demo drives a 3D maze with two builtin modules.

Official sources

  1. Issues
  2. README
  3. Releases
  4. Tencent/puerts on GitHub
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/tencent-puerts.svg)](https://hysenlabs.com/projects/tencent-puerts)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/tencent-puerts.svg)](https://hysenlabs.com/projects/tencent-puerts)

Was this useful?