Skip to main content

Script Libraries#

Once you have written the same helper into three indicators, you want it in one place. A library is a script whose only job is to be imported by other scripts. You publish it once, and any indicator — yours or someone else's — pulls the functions it needs out of it by name.

This page covers importing a library, writing one, how versions and takedowns work, and the limits worth knowing before you build a deep stack. For the functions available inside a script, see the Scripting Reference.

Importing a library#

Add a named import at the top of your indicator:

// @indicator { name: "Keltner Channel", overlay: true }
import { keltner, widthPct } from "tracer:alice/band-utils@1";

The imported names are ordinary values in scope. There is no namespace object and no lib. prefix, because named imports are the only form that exists.

The specifier reads tracer:<username>/<slug>@<version>:

PartRule
usernameThe author's handle. 3–20 characters: lowercase letters, digits, underscore.
slugThe library's name in a URL. 3–64 characters: lowercase letters, digits, hyphens, starting and ending with a letter or digit.
versionA whole number, 1 or greater.

You can rename on the way in with as, which is how you resolve a collision between two libraries that both export ema:

import { ema as fastEma } from "tracer:alice/band-utils@1";
import { ema as slowEma } from "tracer:bob/smoothing@4";

Forms that are refused#

Only the named tracer: import is accepted. Everything else is rejected at save with a message naming the line, and all the problems in a script are reported together so you fix them in one pass.

You wroteWhy it is refused
import lib from "tracer:..."Default imports. Use a named import.
import * as ns from "tracer:..."Namespace imports. List the names you need.
import "tracer:..."Side-effect imports. Import the names you use.
import type { T } from "tracer:..."Libraries are inlined as runtime code, so there are no type-only imports.
import("tracer:...")Dynamic import. Use a static one.
import x = require("...")Not a form Tracer Script has.
export { x } from "tracer:..."A re-export is an import in disguise. See below.
import { x } from "./utils"Only tracer: imports exist. There are no relative imports and no npm.

The tracer: scheme is what keeps the grammar closed: without it, "./utils" and a library reference would be impossible to tell apart.

Writing a library#

A library is a script saved with the kind Library. It is never attached to a chart, never runs on its own, and does not appear in the community browse or install lists — other scripts reach it through an import instead.

export function keltner(candles, closes, length, mult, i) {
  const mid = ta.ema(closes, length, i);
  const range = ta.atr(
    candles.map(c => c.high),
    candles.map(c => c.low),
    closes,
    length,
    i,
  );
  if (mid === null || range === null) { return null; }
  return { mid: mid, upper: mid + mult * range, lower: mid - mult * range };
}
 
export function widthPct(band) {
  if (band === null || band.mid === 0) { return null; }
  return ((band.upper - band.lower) / band.mid) * 100;
}

The rules, all checked when you save:

  • Export at least one thing. A function, class or constant. A library that exports nothing cannot be imported by anyone.
  • No export default. Named exports only — a default export has no name to inline.
  • No re-exports. export { x } from "tracer:bob/thing@1" republishes someone else's binding under your name while contributing nothing of your own. Import what you need and export your own wrapper, so the binding is yours to maintain.
  • No onBar, compute or getSettings. The first two are chart entry points and a library is not chart-attachable; declaring one almost always means you meant to save an indicator. getSettings is refused because a library is configured through its function parameters, not a settings schema.
  • No TypeScript-only syntax. No type annotations, generics, as, satisfies, interface, type aliases, or enum. There is no compile step — your source is copied into the importing script exactly as written, so there is nothing to erase a type annotation.
  • Libraries are JavaScript. A tracer: import only resolves a JavaScript module, so a Python or Rust script cannot be a library.

Everything else about the sandbox is unchanged. A library's body ends up running with the importer's privileges, so it goes through the same static analysis as an indicator: the same blocked globals, the same brace-every-loop rule, the same compute budget.

Publishing#

To publish a library you need both halves of the specifier other people will type: a slug on the library, and a username on your account. If a slug cannot be derived cleanly from the name, Tracer asks you for one rather than inventing library-a7f3 — a slug is half of a public reference and is effectively permanent once anything imports it.

Slugs are unique per account, so you can own @you/band-utils even if somebody else already has one.

Versions, takedowns and re-linking#

The one idea that explains everything below: a library is inlined by value, not referenced.

When you save a script that imports a library, Tracer copies that library's source into your script's executable code. Your script then runs those exact bytes, permanently. This is what makes an indicator you published two months ago keep behaving the way you tested it.

Every import is pinned. Version rows never change, which is the only thing that makes a pin mean anything. The editor lets you type @latest as a convenience, but on save Tracer resolves it to a concrete number, rewrites your source, and shows you what was actually stored. There is no unpinned state — a floating reference would let the code behind an already-reviewed script change underneath it.

The pin is an id, not a handle. @alice/band-utils is display text; the dependency is recorded against the library's row. Usernames can be released and taken by someone else, so if the pinned row no longer answers to the handle you wrote — different owner, or a changed slug — your next save is refused rather than silently rebound to a stranger's code. You re-point the import deliberately.

Moving a pin is always something you do. Nothing upgrades you automatically. Re-linking re-runs the whole save path: resolve, inline, analyze, and mint a new version of your script.

When a library is taken down#

If a library is suspended or removed by a moderator, Tracer stops linking it into new saves, so no new copy is created. Copies that already exist keep running — the bytes are inside published versions and inside indicators already installed on people's charts, and nothing can reach in and change them.

What happens instead is that every script that inlined it is marked, so you are told that a library you depend on was taken down and can review and re-link. There is deliberately no automatic re-resolution: that would be a silent code change to your published script — new code, which you never reviewed, running under your name.

Limits#

A library may import another library. The graph is bounded by four numbers:

LimitValue
Link depth3 levels
Linked modules16
Linked size120,000 characters after everything is inlined
Your own source50,000 characters

Import cycles are refused: a library cannot import, directly or indirectly, a script that imports it.

Depth is measured from the script at the root of the graph — which is not always you. A library is the root when you save it, but only a rung when somebody imports it, so the same chain is 3 deep for you and 4 deep for your first user. A library can therefore save cleanly and still be impossible to import.

Rather than let that land on an importer — who would see a depth error about your library, in a chain they cannot see and cannot flatten — Tracer warns you at save:

  • If your library's own imports nest 2 levels, it uses the entire budget an importer has. An indicator can still import it, but a library that imports it becomes un-importable.
  • If they nest 3 or more, nothing can import your library as it stands. It still saves, because building bottom-up is legitimate, but the warning says so plainly.

The fix in both cases is to flatten the chain by one level.

Learn more#