Scripting engines
Vela™ ships no engine. Install the Pine addon or write your own against the port.
Vela™ ships no scripting engine. It defines the ScriptingEngine port, owns the bars,
and routes whatever an engine produces onto panes — but the language runtime itself is
always something you plug in. A bare chart draws candles, drawings and native indicators;
addIndicator() without a matching engine raises an actionable error.
There are two ways to get one:
- install an addon — Vela-PineTS today;
- write your own — the port is public and documented in adding an engine.
Vela-PineTS
Vela-PineTS is the official Pine addon: it runs indicator() and strategy() scripts through Vela™'s ScriptingEngine port on top of the PineTS runtime. The PineTS-side walkthrough is Using within charts.
npm install @luxalgo/vela-pinets pinetspinets is the Pine Script® runtime the addon executes; it is a peer dependency, so you
install it alongside.
import { Vela } from '@luxalgo/vela';
import { PineEngine } from '@luxalgo/vela-pinets';
const chart = new Vela('#chart', { symbol: 'BTCUSDT', timeframe: '60' });
chart.registerEngine('pine', new PineEngine());
chart.addIndicator(`//@version=5
indicator("EMA 20", overlay=true)
plot(ta.ema(close, 20), color=color.orange, linewidth=2)`);The addon exports two engines with identical Pine semantics:
| Export | Where scripts run | Use it when |
|---|---|---|
PineEngine | the main thread | simplest setup; light scripts |
PineWorkerEngine | a Web Worker | heavy scripts must never block the chart |
PineWorkerEngine needs no configuration: the worker source is inlined into the addon's
bundle at build time and spawned from a Blob URL. Under a Content-Security-Policy that
blocks blob:, host the worker file yourself and pass
new PineWorkerEngine({ workerUrl: '/vela-pine-worker.js' }).
Both engines report { streaming: true, visibleRange: true, inputs: true } — live
incremental re-execution, viewport-dependent scripts, and an inputs schema that drives
the settings dialog. request.security (HTF/LTF/cross-symbol, extended-ticker aware)
resolves through Vela™'s own cached data feed: the engine never fetches market data.
Importing Pine libraries
For your own chart integration, Vela-PineTS 0.3.1 with PineTS 0.13.1 supports scripts that use import user/library/version as alias. Pass an importResolver to PineEngine or PineWorkerEngine to supply each library's Pine Script® source. The engine does not download libraries automatically.
import { PineWorkerEngine, type PineLibraryResolver } from '@luxalgo/vela-pinets';
const importResolver: PineLibraryResolver = async (_path, { user, name, version }) => {
const url = `/pine-libraries/${encodeURIComponent(user)}/${encodeURIComponent(name)}/${version}.pine`;
const response = await fetch(url);
if (response.status === 404) return null;
if (!response.ok) throw new Error(`Library request failed: ${response.status}`);
return response.text();
};
chart.registerEngine('pine', new PineWorkerEngine({ importResolver }));Serve the library sources at the URLs your resolver uses. Return null for an unknown library; preparation then fails with an error naming the import. Imported libraries can have their own imports, which use the same resolver. The worker engine calls the resolver on the main thread during preparation, so it does not fetch libraries again for each bar.
If you use Vela Pro 0.4.33, its pineLibraryResolver helper supplies an HTTP resolver:
import { enablePineEngine, pineLibraryResolver } from '@luxalgo/vela-pro';
// Register before constructing your charts or workspace.
enablePineEngine({
importResolver: pineLibraryResolver({
url: '/pine-libraries/{user}/{name}/{version}.pine',
}),
});Without a resolver, scripts with imports cannot run. This is setup for developers embedding the chart libraries; it does not enable library imports in Vela's hosted Pine editor.
In the widget and the workspace
The shells take engine factories, keyed by language — one instance per chart, so a worker engine gets its own thread and dies with its chart:
import { VelaWorkspace } from '@luxalgo/vela/workspace';
import { PineWorkerEngine } from '@luxalgo/vela-pinets';
new VelaWorkspace('#app', {
layout: false, // one chart; any grid layout takes the same options
symbol: 'BTCUSDT',
engines: { pine: () => new PineWorkerEngine() },
indicators: [{ name: 'EMA 20', enabled: true, script: '…' }], // manifest entries default to `pine`
});The engines factories are instantiated once per chart (each workspace cell gets its
own — a worker engine per chart). See the workspace.
Registering it once, app-wide
registerDefaultEngine (from @luxalgo/vela/plugin) installs a language for every
chart built afterwards — widgets, workspace cells, everything — so feature packages can
wire Pine in once instead of threading an engines option through every call site:
import { registerDefaultEngine } from '@luxalgo/vela/plugin';
import { PineWorkerEngine } from '@luxalgo/vela-pinets';
registerDefaultEngine('pine', () => new PineWorkerEngine());An explicit engines entry still wins for its language.
Script tag / CDN
The addon publishes browser globals next to Vela™'s. Load order is the contract —
vela.global.js first, because the addon resolves @luxalgo/vela to the page's
window.Vela rather than bundling a second copy of the library:
<script src="vela.global.js"></script>
<script src="vela-pinets.global.js"></script>
<script>
const chart = new Vela.Vela('#chart', { data: bars, timeframe: '60' });
chart.registerEngine('pine', new VelaPinets.PineEngine());
</script>Licensing
pinets is AGPL-3.0, so @luxalgo/vela-pinets is AGPL-3.0 too. That is precisely
why it is a separate package: Vela™ itself stays Apache-2.0 and contains no Pine code.
Installing the addon brings the AGPL obligations into your deployment — decide that
deliberately. If you cannot take them on, write an engine for the language you need
against the port instead.
Writing your own engine
The port is small — prepare() (parse a script: inputs + declaration metadata, no market
data) and execute() (return a session handle; emit models through the handlers). Vela™
owns the bars and passes them in; the engine never fetches candles.
Adding an engine is the full contract, and
playground/demo-engine.ts in this repo is a ~300-line working engine you can read in
one sitting — the one the Vela™ playground itself runs.
Related
- Using within charts — PineTS + Vela-PineTS, from the PineTS side.
- Adding an engine — the port, in full.
- The plugin SDK —
registerDefaultEngineand the model vocabulary engines build with. - Native indicators — core-computed indicators (volume, VPVR) that need no engine at all.