Jint 4.15.1
dotnet add package Jint --version 4.15.1
NuGet\Install-Package Jint -Version 4.15.1
<PackageReference Include="Jint" Version="4.15.1" />
<PackageVersion Include="Jint" Version="4.15.1" />
<PackageReference Include="Jint" />
paket add Jint --version 4.15.1
#r "nuget: Jint, 4.15.1"
#:package Jint@4.15.1
#addin nuget:?package=Jint&version=4.15.1
#tool nuget:?package=Jint&version=4.15.1
Jint
Jint is a Javascript interpreter for .NET which can run on any modern .NET platform as it supports .NET Standard 2.0 and .NET 4.6.2 targets (and later).
Use cases and users
- Run JavaScript inside your .NET application in a safe sand-boxed environment
- Expose native .NET objects and functions to your JavaScript code (get database query results as JSON, call .NET methods, etc.)
- Support scripting in your .NET application, allowing users to customize your application using JavaScript (like Unity games)
Some users of Jint include RavenDB, EventStore, OrchardCore, ELSA Workflows, docfx, JavaScript Engine Switcher, and many more.
Supported features
ECMAScript 2015 (ES6)
- ✔ ArrayBuffer
- ✔ Arrow function expression
- ✔ Binary and octal literals
- ✔ Class support
- ✔ DataView
- ✔ Destructuring
- ✔ Default, rest and spread
- ✔ Enhanced object literals
- ✔
for...of - ✔ Generators
- ✔ Template strings
- ✔ Lexical scoping of variables (let and const)
- ✔ Map and Set
- ✔ Modules and module loaders
- ✔ Promises (Experimental, API is unstable)
- ✔ Reflect
- ✔ Proxies
- ✔ Symbols
- ❌ Tail calls
- ✔ Typed arrays
- ✔ Unicode
- ✔ Weakmap and Weakset
ECMAScript 2016
- ✔
Array.prototype.includes - ✔
await,async - ✔ Block-scoping of variables and functions
- ✔ Exponentiation operator
** - ✔ Destructuring patterns (of variables)
ECMAScript 2017
- ✔
Object.values,Object.entriesandObject.getOwnPropertyDescriptors - ✔ Shared memory and atomics
ECMAScript 2018
- ✔ Asynchronous iteration
- ✔
Promise.prototype.finally - ✔ RegExp named capture groups
- ✔ Rest/spread operators for object literals (
...identifier) - ✔ SharedArrayBuffer
ECMAScript 2019
- ✔
Array.prototype.flat,Array.prototype.flatMap - ✔
String.prototype.trimStart,String.prototype.trimEnd - ✔
Object.fromEntries - ✔
Symbol.description - ✔ Optional catch binding
ECMAScript 2020
- ✔
BigInt - ✔
export * as ns from - ✔
for-inenhancements - ✔
globalThisobject - ✔
import - ✔
import.meta - ✔ Nullish coalescing operator (
??) - ✔ Optional chaining
- ✔
Promise.allSettled - ✔
String.prototype.matchAll
ECMAScript 2021
- ✔ Logical Assignment Operators (
&&=||=??=) - ✔ Numeric Separators (
1_000) - ✔
AggregateError - ✔
Promise.any - ✔
String.prototype.replaceAll - ✔
WeakRef - ✔
FinalizationRegistry
ECMAScript 2022
- ✔ Class Fields
- ✔ RegExp Match Indices
- ✔ Top-level await
- ✔ Ergonomic brand checks for Private Fields
- ✔
.at() - ✔ Accessible
Object.prototype.hasOwnProperty(Object.hasOwn) - ✔ Class Static Block
- ✔ Error Cause
ECMAScript 2023
- ✔ Array find from last
- ✔ Change Array by copy
- ✔ Hashbang Grammar
- ✔ Symbols as WeakMap keys
ECMAScript 2024
- ✔ ArrayBuffer enhancements -
ArrayBuffer.prototype.resizeandArrayBuffer.prototype.transfer - ✔
Atomics.waitAsync - ✔ Ensuring that strings are well-formed -
String.prototype.ensureWellFormedandString.prototype.isWellFormed - ✔ Grouping synchronous iterables -
Object.groupByandMap.groupBy - ✔
Promise.withResolvers - ✔ Regular expression flag
/v
ECMAScript 2025
- ✔ 16-bit floating point numbers (float16), Requires NET 8 or higher,
Float16Array,Math.f16round() - ✔ Array.fromAsync
- ✔ Import attributes
- ✔ Iterator helper methods
- ✔ JSON modules
- ✔
Promise.try - ✔
RegExp.escape() - ✔ Regular expression pattern modifiers (inline flags)
- ✔ Duplicate named capture groups
- ✔ Set methods (
intersection,union,difference,symmetricDifference,isSubsetOf,isSupersetOf,isDisjointFrom)
ECMAScript proposals (no version yet)
- ✔ Await Dictionary (
Promise.allKeyed,Promise.allSettledKeyed) - ✔ Decorators (
@decoratorsyntax for classes, methods, fields, and accessors) - ✔
Error.isError - ✔
Error.prototype.stackaccessor (error-stack-accessor) - ✔ Explicit Resource Management (
usingandawait using) - ✔ Immutable Arraybuffers
- ✔ Import Bytes (
import x from './file' with { type: 'bytes' }) - ✔ Iterator Sequencing
- ✔ Joint Iteration
- ✔ JSON.parse source text access
- ✔
Math.sumPrecise - ✔
ShadowRealm - ✔
Temporal - ✔
Uint8Arrayto/from base64 - ✔
Upsert
Other
- Further refined .NET CLR interop capabilities
- Constraints for execution (recursion, memory usage, duration)
Performance
- Because Jint neither generates any .NET bytecode nor uses the DLR it runs relatively small scripts really fast
- If you repeatedly run the same script, you should prepare it for execution using
Engine.PrepareScriptorEngine.PrepareModule, cache the returnedPrepared<...>object and feed it to Jint instead of the content string - You should prefer running engine in strict mode, it improves performance
You can check out the engine comparison results, bear in mind that every use case is different and benchmarks might not reflect your real-world usage.
Embedding performance
Notes for hosts that project their own objects into script, pool engines, or bound execution. Each of these is a cost model rather than a rule; the XML documentation on the named APIs has the detail.
Projecting host data. Subclassing ObjectInstance is the most expensive way to expose data. Such a
receiver gets no own-property inline caching — every own read reaches your GetOwnProperty and allocates the
PropertyDescriptor it returns. Cheaper options, in order of preference:
- For fixed-shape records, do not subclass at all:
JsObject.Create(engine, layout, values)andJsObject.CreateFromEntriesbuild straight into the hidden-class representation, so every object sharing aJsObjectLayoutshares one hidden class and a script reading a batch of them keeps a monomorphic inline cache. A record with expensive members most items never have read — a body that must be parsed, a field that must be decoded — declares them withJsObjectLayout.CreateBuilder().AddLazy(name, factory)and passes the raw payload as thelazySlotStateargument ofJsObject.Create: the factory runs on the first read that observes that member's value and the result is memoized on the object, while enumerating keys,inandhasOwnPropertynever run it. The object stays a hidden-class object throughout. - For CLR objects,
engine.SetValue(name, obj)wraps them inObjectWrapper, whose member resolution and compiled accessors are cached process-wide on theTypeResolver. - For the prototypes those objects sit behind, declare the members once per process with
JsObjectShapeand create one object per engine withInstantiate: members materialize only when a script touches them, and because the engine stores and versions the whole member set itself, a shaped prototype can serve the prototype-method inline cache — which anObjectInstancesubclass used as a prototype can never do. - For a live indexed collection — a DOM
NodeList, a result window, any list computed on demand — derive fromArrayLikeObjectrather than assembling the property model yourself. You implement two members,uint Lengthandbool TryGetIndex(uint index, out JsValue value); the base class derives everything else (index andlengthdescriptors, enumeration order, the existence and value hooks, WebIDL-shapeddelete/definePropertyrefusals) and the engine keys two lanes on the type, solist[i],Array.prototypegenerics,for-of/ spread /Array.from/ destructuring andJSON.stringifyeach cost oneTryGetIndexper element with no descriptor and no key allocation. It is array-like, not an array:Array.isArraystaysfalseby design, the same answer a browser gives for aNodeList. If your backing store can test containment more cheaply than it can produce an element, also overrideprotected virtual bool HasIndex(uint index), andin/hasOwnProperty/Object.keys/deletestop projecting elements they only ever discard. Reach for it when the collection is live; when it is a snapshot, copying into aJsArrayonce is cheaper still — and give it aJsObjectShapeprototype, per the bullet above, for the collection's own methods. - If you must subclass, override
TryGetOwnPropertyValueso an own read hands the value over with no descriptor at all, andProbeOwnPropertyso existence and enumerability questions (in,Object.keys, spread,JSON.stringify) are answered without materializing one either. Both carry an obligation to agree withGetOwnProperty; Debug builds verify that on every read, so running your integration suite once against a Debug build of Jint is a free checker.
Lazy values. PropertyFlag.CustomJsValue is the supported hook for a property whose value is computed on
first read: a PropertyDescriptor subclass overriding CustomValue keeps working under the read inline
caches, because every caching lane re-reads the flag on each hit and caches the descriptor reference rather
than a value snapshot. For a whole global that may never be touched, Options.AddLazyGlobal defers building
the value until script reads the name.
Sparse data. Hosts that read deep chains off optional data — input.Address.City.length, where any link
may be absent — usually install an IReferenceResolver so a nullish base yields a value instead of throwing.
Register NullPropagatingReferenceResolver.Instance rather than writing that class yourself: the engine
recognizes the singleton and serves the propagation inline, with no interface call and no pooled Reference
per nullish read, which an equivalent hand-written resolver cannot get. Pass
ReferenceResolverInterests.NullishPropertyBase alongside it so every unrelated read lane stays armed. The
boundary is that reads propagate: a call on a nullish base still throws, and a host that needs callable
substitution or unresolvable-identifier handling writes its own resolver and forgoes the inline lane.
Prepared scripts and engine reuse. Engine.PrepareScript / PrepareModule return an object that is
reusable and thread-safe: prepare once at startup and feed the same Prepared<T> to as many engines, on as
many threads, as you like. The engine's own per-node caches are a separate matter — they are engine-owned and
engage only on the second evaluation of a given script on a given engine, so a host that builds a fresh
engine per operation never reaches them by design. Note the mirror image if you pool engines instead: a
warmed call site holds a reference to the last receiver it served until it caches a different one, so pooled
engines can keep host objects alive between runs.
Registering only what a script uses. When the ambient API is large and scripts touch little of it,
prepare with ScriptPreparationOptions.CollectReferencedGlobals and read Prepared<T>.ReferencedGlobals:
the free identifiers the program actually references, resolved per binding site, as an immutable set you can
intersect with your registry — including the CLR-side context you would otherwise build speculatively, which
lazy globals cannot defer. Honor HasDirectEvalCall: a program with direct eval can reference anything,
so install everything for those.
Reusing a configured engine. If you build a fresh engine per evaluation only because you need a clean
global, engine.Advanced.CaptureGlobalSnapshot() and RestoreGlobalSnapshot(snapshot) are the cheaper route:
capture once after your SetValue calls and module setup, then restore between evaluations. Restore reverts
the global object's own properties, its prototype and extensibility, and the top-level let/const/class
declarations (which nothing else can clear, so a script with a top-level let can otherwise only be run once
per engine); it also clears the RegExp.$1-style legacy statics and resets the interop wrapper caches. The
per-node caches above are deliberately kept, so the next run starts warm. Keep the snapshot in a field beside
the engine and put the restore in a finally — a script that throws still declared its globals, so restoring
only on the success path hands them to the next caller.
Choosing between this and AddLazyGlobal is a question of engine lifetime: a fresh-engine-per-evaluation
host wants lazy globals (nothing to restore — the win is never building what the script does not read); a
pooled host wants the snapshot. They compose: restore returns a global that was still lazy at capture to its
unmaterialized state, so a pooled engine keeps both benefits.
Restore also ends the previous cycle on the event loop. Queued jobs are discarded, and — because discarding
cannot reach work that has not been enqueued yet — any promise registered before the restore is dropped when
it settles instead of resuming its continuation, so a fire-and-forget async function suspended on a host
Task never wakes up against the restored globals. Register a promise that is meant to outlive a restore
after it. Restore refuses (InvalidOperationException) while an evaluation is in progress, including an
EvaluateAsync/ExecuteAsync/InvokeAsync whose Task you still hold. What it cannot fence is you calling
back in: invoking a function a previous evaluation handed you runs it against the restored surface.
It is a configuration-reuse primitive, not an isolation boundary — mutations of Object.prototype and
other intrinsics, of object graphs behind restored bindings, of host CLR state, plus Symbol.for
registrations and registered modules, all survive a restore, so mutually distrusting scripts still need
separate engines. A snapshot also keeps its engine and every captured value strongly reachable, so do not
cache one past that engine's lifetime.
Constraints and options. An Options instance is meant to be shared across engines, including concurrent
ones — the built-in constraint helpers register a factory, so each engine gets its own counter and its own
deadline. (Sharing it is not required: building an Options per scope is fine when your globals depend on
scoped state.) Watch for the sentinel trap: MaxStatements(int.MaxValue), LimitMemory(long.MaxValue) and
TimeoutInterval(TimeSpan.MaxValue) register no constraint at all and remove any previously registered one
of that kind, so spelling "effectively unlimited" that way leaves you with no limit rather than a large one.
Values do not cross engines. A JsValue that is an ObjectInstance holds a hard reference to the engine
and realm that created it, and passing one to a different engine is not supported — it is neither validated
nor made safe. Prepared<Script> / Prepared<Module> and ModuleBuilder are the supported ways to share
work between engines; convert to CLR values (ToObject()) to move data.
Discussion
Join the chat on Gitter or post your questions with the jint tag on stackoverflow.
Video
Here is a short video of how Jint works and some sample usage
https://docs.microsoft.com/shows/code-conversations/sebastien-ros-on-jint-javascript-interpreter-net
Thread-safety
Engine instances are not thread-safe and they should not accessed from multiple threads simultaneously.
Examples
This example defines a new value named log pointing to Console.WriteLine, then runs
a script calling log('Hello World!').
var engine = new Engine()
.SetValue("log", new Action<object>(Console.WriteLine));
engine.Execute(@"
function hello() {
log('Hello World');
};
hello();
");
Here, the variable x is set to 3 and x * x is evaluated in JavaScript. The result is returned to .NET directly, in this case as a double value 9.
var square = new Engine()
.SetValue("x", 3) // define a new variable
.Evaluate("x * x") // evaluate a statement
.ToObject(); // converts the value to .NET
You can also directly pass POCOs or anonymous objects and use them from JavaScript. In this example for instance a new Person instance is manipulated from JavaScript.
var p = new Person {
Name = "Mickey Mouse"
};
var engine = new Engine()
.SetValue("p", p)
.Execute("p.Name = 'Minnie'");
Assert.AreEqual("Minnie", p.Name);
You can invoke JavaScript function reference
var result = new Engine()
.Execute("function add(a, b) { return a + b; }")
.Invoke("add",1, 2); // -> 3
or directly by name
var engine = new Engine()
.Execute("function add(a, b) { return a + b; }");
engine.Invoke("add", 1, 2); // -> 3
Accessing .NET assemblies and classes
You can allow an engine to access any .NET class by configuring the engine instance like this:
var engine = new Engine(cfg => cfg.AllowClr());
Then you have access to the System namespace as a global value. Here is how it's used in the context on the command line utility:
jint> var file = new System.IO.StreamWriter('log.txt');
jint> file.WriteLine('Hello World !');
jint> file.Dispose();
And even create shortcuts to common .NET methods
jint> var log = System.Console.WriteLine;
jint> log('Hello World !');
=> "Hello World !"
When allowing the CLR, you can optionally pass custom assemblies to load types from.
var engine = new Engine(cfg => cfg
.AllowClr(typeof(Bar).Assembly)
);
and then to assign local namespaces the same way System does it for you, use importNamespace
jint> var Foo = importNamespace('Foo');
jint> var bar = new Foo.Bar();
jint> log(bar.ToString());
adding a specific CLR type reference can be done like this
engine.SetValue("TheType", TypeReference.CreateTypeReference<TheType>(engine));
and used this way
jint> var o = new TheType();
Generic types are also supported. Here is how to declare, instantiate and use a List<string>:
jint> var ListOfString = System.Collections.Generic.List(System.String);
jint> var list = new ListOfString();
jint> list.Add('foo');
jint> list.Add(1); // automatically converted to String
jint> list.Count; // 2
Intercepting access to .NET objects
ECMAScript Proxy traps can be implemented in .NET by deriving from ProxyHandler and creating the proxy via engine.Advanced.CreateProxy (or CreateRevocableProxy). A trap returning null forwards the operation to the target, exactly like an absent trap on a JavaScript handler object, and all proxy invariants are enforced on trap results. Combine with SetWrapObjectHandler to transparently intercept every wrapped .NET object. Note that proxy.method() fires the Get trap (then calls the returned value) — the Apply trap only fires when the proxy itself is invoked, so intercepting method calls means returning a wrapping function from Get. new Proxy(target, handlerObject) from script remains the JavaScript-side equivalent.
class LoggingHandler : ProxyHandler
{
public override JsValue? Get(ObjectInstance target, JsValue property, JsValue receiver)
{
Console.WriteLine($"get {property}");
return null; // forward to the target
}
public override bool? Has(ObjectInstance target, JsValue property)
{
Console.WriteLine($"has {property}");
return null;
}
}
var engine = new Engine(options =>
{
// wrap every interop object in a logging proxy
options.SetWrapObjectHandler((e, obj, type) =>
e.Advanced.CreateProxy(ObjectWrapper.Create(e, obj, type), new LoggingHandler()));
});
Internationalization
You can enforce what Time Zone or Culture the engine should use when locale JavaScript methods are used if you don't want to use the computer's default values.
This example forces the Time Zone to Pacific Standard Time.
var PST = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var engine = new Engine(cfg => cfg.LocalTimeZone(PST));
engine.Execute("new Date().toString()"); // Wed Dec 31 1969 16:00:00 GMT-08:00
This example is using French as the default culture.
var FR = CultureInfo.GetCultureInfo("fr-FR");
var engine = new Engine(cfg => cfg.Culture(FR));
engine.Execute("new Number(1.23).toString()"); // 1.23
engine.Execute("new Number(1.23).toLocaleString()"); // 1,23
Extending Temporal and Intl with custom providers
Jint ships English-only / ISO-only defaults to keep the binary small. Non-English CLDR data and full IANA timezone DST history are reachable via two pluggable providers:
| Extension point | Default | What it covers | Reusable example |
|---|---|---|---|
Options.Temporal.TimeZoneProvider (ITimeZoneProvider) |
DefaultTimeZoneProvider (BCL TimeZoneInfo + Windows↔IANA mapping) |
UTC offsets, DST transitions, IANA canonicalization | NodaTimeZoneProvider.cs — uses NodaTime TZDB for full historical accuracy |
Options.Intl.CldrProvider (ICldrProvider) |
DefaultCldrProvider (English + .NET CultureInfo) |
locale names, currencies, units, plural rules, calendar month/era names | IcuCldrProvider.cs — uses ICU4N for full CLDR coverage |
The provider files in Jint.Tests.Test262/ are MIT-licensed copy-and-modify templates,
not a stable API contract. They are also exactly how the test suite reaches its current
test262 conformance numbers.
To wire them into your project, add the same NuGet packages (NodaTime, ICU4N), copy
the provider files in, and register them on Engine options:
var engine = new Engine(options =>
{
options.Temporal.TimeZoneProvider = new NodaTimeZoneProvider();
options.Intl.CldrProvider = new IcuCldrProvider();
});
A separate ICalendarProvider for non-ISO calendar arithmetic (Islamic UmAlQura, Persian
astronomical, Chinese/Dangi at extreme dates) is on the roadmap; until then non-ISO
calendars use System.Globalization.Calendar subclasses, which constrains some date
ranges (see Jint/Native/Temporal/NonIsoCalendars.cs).
Execution Constraints
Execution constraints are used during script execution to ensure that requirements around resource consumption are met, for example:
- Scripts should not use more than X memory.
- Scripts should only run for a maximum amount of time.
You can configure them via the options:
var engine = new Engine(options => {
// Limit memory allocations to 4 MB
options.LimitMemory(4_000_000);
// Set a timeout to 4 seconds.
options.TimeoutInterval(TimeSpan.FromSeconds(4));
// Set limit of 1000 executed statements.
options.MaxStatements(1000);
// Use a cancellation token.
options.CancellationToken(cancellationToken);
}
You can also write a custom constraint by deriving from the Constraint base class:
public abstract class Constraint
{
/// Called before script is run and useful when you use an engine object for multiple executions.
public abstract void Reset();
// Called before each statement to check if your requirements are met; if not - throws an exception.
public abstract void Check();
}
For example we can write a constraint that stops scripts when the CPU usage gets too high:
class MyCPUConstraint : Constraint
{
public override void Reset()
{
}
public override void Check()
{
var cpuUsage = GetCPUUsage();
if (cpuUsage > 0.8) // 80%
{
throw new OperationCancelledException();
}
}
}
var engine = new Engine(options =>
{
options.Constraint(new MyCPUConstraint());
});
When you reuse the engine and want to use cancellation tokens you have to reset the token before each call of Execute:
var engine = new Engine(options =>
{
options.CancellationToken(new CancellationToken(true));
});
var constraint = engine.Constraints.Find<CancellationConstraint>();
for (var i = 0; i < 10; i++)
{
using (var tcs = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
{
constraint.Reset(tcs.Token);
engine.SetValue("a", 1);
engine.Execute("a++");
}
}
Using Modules
You can use modules to import and export variables from multiple script files:
var engine = new Engine(options =>
{
options.EnableModules(@"C:\Scripts");
});
var ns = engine.Modules.Import("./my-module.js");
var value = ns.Get("value").AsString();
By default, the module resolution algorithm will be restricted to the base path specified in EnableModules, and there is no package support. However you can provide your own packages in two ways.
Defining modules using JavaScript source code:
engine.Modules.Add("user", "export const name = 'John';");
var ns = engine.Modules.Import("user");
var name = ns.Get("name").AsString();
Defining modules using the module builder, which allows you to export CLR classes and values from .NET:
// Create the module 'lib' with the class MyClass and the variable version
engine.Modules.Add("lib", builder => builder
.ExportType<MyClass>()
.ExportValue("version", 15)
);
// Create a user-defined module and do something with 'lib'
engine.Modules.Add("custom", @"
import { MyClass, version } from 'lib';
const x = new MyClass();
export const result = x.doSomething();
");
// Import the user-defined module; this will execute the import chain
var ns = engine.Modules.Import("custom");
// The result contains "live" bindings to the module
var id = ns.Get("result").AsInteger();
Note that you don't need to EnableModules if you only use modules created using Engine.Modules.Add.
Asynchronous Execution
Jint supports non-blocking execution of JavaScript that involves async/await and Promises. This is important in ASP.NET Core and other environments where blocking a thread while waiting for I/O can cause thread-pool exhaustion.
EvaluateAsync / ExecuteAsync / InvokeAsync
Use EvaluateAsync when you want to evaluate JavaScript code that may return a Promise (e.g., an async function call). The method awaits Promise settlement without blocking any thread — the calling thread is released back to the pool while I/O is in flight:
var engine = new Engine();
// Expose an async .NET method to JavaScript
engine.SetValue("fetchData", new Func<string, Task<string>>(async url =>
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}));
// EvaluateAsync properly awaits the Promise returned by the async IIFE
var result = await engine.EvaluateAsync("""
(async () => {
const data = await fetchData('https://example.com/api');
return data;
})()
""");
Console.WriteLine(result.AsString());
ExecuteAsync and InvokeAsync follow the same pattern:
// Execute a script that may produce a Promise and await its completion
await engine.ExecuteAsync("async function init() { ... } init()");
// Invoke a named async function and await its result
var value = await engine.InvokeAsync("myAsyncFunction", arg1, arg2);
By default, async execution respects Options.Constraints.PromiseTimeout. You can also pass a CancellationToken:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var result = await engine.EvaluateAsync("(async () => await fetchData(url))()", cancellationToken: cts.Token);
UnwrapIfPromiseAsync
When you already hold a JsValue that may be a Promise — for example returned from engine.Invoke(...), value.Call(...), or a property access — use UnwrapIfPromiseAsync to await it without blocking:
// Obtain a JsValue that may be a Promise
var jsValue = engine.Invoke("computeAsync", someArg);
// Await it asynchronously (non-blocking)
var result = await jsValue.UnwrapIfPromiseAsync();
// With cancellation support
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var result = await jsValue.UnwrapIfPromiseAsync(cts.Token);
If jsValue is not a Promise it is returned immediately. If it is a rejected Promise a PromiseRejectedException is thrown.
The synchronous UnwrapIfPromise is still available for scenarios where blocking is acceptable (e.g., CPU-bound scripts with no I/O), but UnwrapIfPromiseAsync should be preferred in any async call chain.
Task/ValueTask to Promise Interop (Experimental)
When the TaskInterop experimental feature is enabled, .NET Task and ValueTask return values are automatically converted to JavaScript Promises. This allows JavaScript code to await or .then() the results of .NET async methods without any manual wrapping:
var engine = new Engine(options =>
{
options.ExperimentalFeatures = ExperimentalFeature.TaskInterop;
});
engine.SetValue("fetchData", new Func<string, Task<string>>(async url =>
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}));
// .NET Task is automatically converted to a JavaScript Promise
var result = engine.Evaluate("fetchData('https://example.com/api').then(data => data)");
result = result.UnwrapIfPromise();
Without TaskInterop, .NET Tasks passed to JavaScript are exposed as opaque CLR objects. With it enabled, they become native Promises that support await, .then(), and .catch().
You can configure the timeout for promise resolution via Options.Constraints.PromiseTimeout:
var engine = new Engine(options =>
{
options.ExperimentalFeatures = ExperimentalFeature.TaskInterop;
options.Constraints.PromiseTimeout = TimeSpan.FromSeconds(10);
});
.NET Interoperability
- Manipulate CLR objects from JavaScript, including:
- Single values
- Objects
- Properties
- Methods
- Delegates
- Anonymous objects
- Convert JavaScript values to CLR objects
- Primitive values
- Object → expando objects (
IDictionary<string, object>and dynamic) - Array → object[]
- Date → DateTime
- number → double
- string → string
- boolean → bool
- RegExp → Regex
- Function → Delegate
- Extensions methods
Security
The following features provide you with a secure, sand-boxed environment to run user scripts.
- Define memory limits, to prevent allocations from depleting the memory.
- Enable/disable usage of BCL to prevent scripts from invoking .NET code.
- Limit number of statements to prevent infinite loops.
- Limit depth of calls to prevent deep recursion calls.
- Define a timeout, to prevent scripts from taking too long to finish.
Branches and releases
- The recommended branch is main, any PR should target this branch
- The main branch is automatically built and published on MyGet. Add this feed to your NuGet sources to use it: https://www.myget.org/F/jint/api/v3/index.json
- The main branch is occasionally published on NuGet
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
NuGet packages (236)
Showing the top 5 NuGet packages that depend on Jint:
| Package | Downloads |
|---|---|
|
Elsa.Scripting.JavaScript
Elsa is a set of workflow libraries and tools that enable lean and mean workflowing capabilities in any .NET Core application. This package provides a JavaScript expression evaluator based on Jint. |
|
|
JavaScriptEngineSwitcher.Jint
JavaScriptEngineSwitcher.Jint contains a `JintJsEngine` adapter (wrapper for the Jint). |
|
|
OrchardCore.ContentFields
Orchard Core CMS is a Web Content Management System (CMS) built on top of the Orchard Core Framework. Provides features to add content fields and indexing them for a content type. |
|
|
Microi.Core
开源低代码平台-Microi吾码,核心库。此类库开源,官网:https://microi.net |
|
|
AngleSharp.Js
Integrates a JavaScript engine to AngleSharp. |
GitHub repositories (61)
Showing the top 20 popular GitHub repositories that depend on Jint:
| Repository | Stars |
|---|---|
|
OrchardCMS/OrchardCore
Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
|
|
|
elsa-workflows/elsa-core
The Workflow Engine for .NET
|
|
|
kurrent-io/KurrentDB
KurrentDB is a database that's engineered for modern software applications and event-driven architectures. Its event-native design simplifies data modeling and preserves data integrity while the integrated streaming engine solves distributed messaging challenges and ensures data consistency.
|
|
|
BililiveRecorder/BililiveRecorder
录播姬 | mikufans 生放送录制
|
|
|
dotnet/docfx
Static site generator for .NET API documentation.
|
|
|
ravendb/ravendb
ACID Document Database
|
|
|
rnwood/smtp4dev
smtp4dev - the fake smtp email server for development and testing
|
|
|
microsurging/surging
Surging is a micro-service engine that provides a lightweight, high-performance, modular RPC request pipeline. support Event-based Asynchronous Pattern and reactive programming.
|
|
|
SciSharp/BotSharp
AI Multi-Agent Framework in .NET
|
|
|
ErsatzTV/legacy
Open-source platform that transforms your personal media library into live, custom TV channels.
|
|
|
Squidex/squidex
Headless CMS and Content Managment Hub
|
|
|
openbullet/OpenBullet2
OpenBullet reinvented
|
|
|
TEdit/Terraria-Map-Editor
TEdit - Terraria Map Editor - TEdit is a stand alone, open source map editor for Terraria. It lets you edit maps just like (almost) paint! It also lets you change world settings (time, bosses downed etc), edit chests and change sign, make epic dungeons, castles, cities, and add rewards for your adventurers!
|
|
|
openbullet/openbullet
The OpenBullet web testing application.
|
|
|
Apps2Samsung/Apps2Samsung
One-click app installer for Samsung TVs, projectors and smart monitors (Tizen) — Jellyfin, Moonlight, and the whole community catalog.
|
|
|
ape-byte/DouyinBarrageGrab
基于系统代理的抖音弹幕wss抓取程序,能够获取所有数据来源,包括chrome,抖音直播伴侣等,可进行进程过滤
|
|
|
KoalaBear84/OpenDirectoryDownloader
Indexes open directories
|
|
|
bitfoundation/bitplatform
Build all of your apps using what you already know and love ❤️
|
|
|
IoTSharp/IoTSharp
IoTSharp is an open-source IoT platform for data collection, processing, visualization, and device management.
|
|
|
dotnetcore/SmartSql
SmartSql = MyBatis in C# + .NET Core+ Cache(Memory | Redis) + R/W Splitting + PropertyChangedTrack +Dynamic Repository + InvokeSync + Diagnostics
|
| Version | Downloads | Last Updated |
|---|---|---|
| 4.15.1 | 0 | 7/28/2026 |
| 4.15.0 | 0 | 7/28/2026 |
| 4.14.0 | 7,716 | 7/22/2026 |
| 4.13.0 | 16,583 | 7/15/2026 |
| 4.12.0 | 15,234 | 7/12/2026 |
| 4.11.0 | 21,597 | 7/5/2026 |
| 4.10.1 | 43,016 | 6/23/2026 |
| 4.10.0 | 31,834 | 6/15/2026 |
| 4.9.3 | 53,508 | 6/2/2026 |
| 4.9.2 | 81,563 | 5/17/2026 |
| 4.9.1 | 17,450 | 5/14/2026 |
| 4.9.0 | 24,063 | 5/10/2026 |
| 4.8.0 | 259,614 | 4/11/2026 |
| 4.7.1 | 33,608 | 4/3/2026 |
| 4.7.0 | 60,442 | 3/26/2026 |
| 4.6.4 | 51,960 | 3/22/2026 |
| 4.6.3 | 47,891 | 3/11/2026 |
| 4.6.2 | 22,317 | 3/6/2026 |
| 4.6.1 | 25,578 | 3/1/2026 |
| 4.6.0 | 103,783 | 2/15/2026 |