◆ New — Strategy Validator, Code Fixer & Prop Compliance Checker are live. Try one free →
Home / Learn / Pine Script Libraries: How to Create, Publish, and Import Reusable Code
Pine Script

Pine Script Libraries: How to Create, Publish, and Import Reusable Code

How Pine Script v6 libraries work end to end: declaring a library, the export rules and type constraints for exported functions, publishing and the auto-incrementing version number, and importing with the import User/Name/Version syntax. Includes why exact-version pinning is quietly one of the best reproducibility features in Pine — and the gotchas that bite first-time library authors. Educational material only, not financial advice; shared code does not make a strategy profitable, and trading carries a real risk of loss.

How do I create and import a library in Pine Script?

A Pine Script library is a script declared with library() instead of indicator() or strategy(), containing functions marked with the export keyword. You publish it on TradingView (publicly or privately), and other scripts pull it in with import Username/LibraryName/Version. That is the complete loop: declare, export, publish, import.

Here is a minimal but real library:

//@version=6
// Educational only — validate before trading; not financial advice.
library("NormUtils")

// Exported function: every parameter needs an explicit type.
export normalize(float src, simple int len) =>
    lo = ta.lowest(src, len)
    hi = ta.highest(src, len)
    hi == lo ? 0.0 : (src - lo) / (hi - lo)

And a consumer script:

//@version=6
// Educational only — validate before trading; not financial advice.
indicator("Normalized close", overlay = false)

import YourUserName/NormUtils/1 as nu

plot(nu.normalize(close, 50), "Normalized close (0 to 1)")

The import path has three mandatory parts: the publishing account's username, the library title exactly as declared in library(), and an integer version. The as nu alias is optional — without it, the library's own title becomes the namespace — but short aliases keep call sites readable.

Why bother with libraries at all? Because copy-pasting the same ATR helper or position-sizing function into thirty scripts guarantees they drift apart, and drift in shared trading logic is a correctness problem, not a style problem. A library gives one canonical, separately reviewable implementation. For a quality-assurance mindset, that is the entire pitch: fewer copies of the truth means fewer places for a bug to hide. Everything here is educational only — a well-factored library is about correctness and maintenance, and implies nothing about profitability.

Export rules: what you can and cannot export

The export keyword is where libraries stop feeling like ordinary scripts, because exported code lives under stricter rules than script-local code.

Parameter types are mandatory. In a normal script, f(x) => x * 2 compiles and Pine infers the type. In an exported function every parameter must be explicitly typed: export f(float x) => x * 2. Untyped exported parameters are a compile error.

Qualifiers matter, and simple is the sharp edge. A parameter declared int len is treated as a series int — it may change bar to bar. But several built-ins demand arguments that are fixed once at the start, qualified simple. ta.ema() is the classic example: its length must be a simple int. If your exported wrapper declares int len and passes it to ta.ema(), the library will not compile. Declaring simple int len fixes it — and also constrains callers, who now cannot pass a bar-varying length. Choosing qualifiers deliberately is most of the craft of library API design.

Exported functions must be self-contained. They cannot read or mutate the library's mutable global variables; state must live in parameters, return values, or local var declarations inside the function. This is a feature: it makes exported functions individually reviewable, with no hidden coupling.

What can carry the export keyword: functions, methods (export method), user-defined types (export type), and enums. Plain variables cannot be exported — a library exposes behaviour and shapes, not shared mutable state.

// Inside a library: exporting a user-defined type.
export type Band
    float upper
    float lower

Consumers can then declare nu.Band fields and receive them from your exported functions — the cleanest way to return structured results without parallel arrays.

Publishing and versioning: how the version number actually works

A library only becomes importable once it is published on TradingView — saving it in the Pine Editor is not enough, and you cannot import from your unpublished drafts. Publication comes in public and private flavours, and the difference matters for review culture: public libraries must be open-source. Anyone importing your public library can read every line, and you can read every line of any public library you depend on. For code that sits inside trading logic, that auditability is not a nicety; importing a black box you cannot inspect would defeat the purpose of factoring logic out for correctness. Private publication keeps the library usable by your own account without exposing it.

Versioning is deliberately simple. The first publication is version 1. Every time you publish an update to the same library, TradingView increments the version — 2, 3, 4 — and, crucially, the old versions remain frozen and importable. Publishing version 5 does not overwrite version 4; it adds a new immutable snapshot alongside it.

This has two practical consequences for library authors. First, you cannot hot-fix a version in place. If version 3 has a bug, version 3 has that bug forever; the fix ships as version 4, and every consumer must edit their import line to receive it. Second, semantic discipline is on you: Pine's version integers carry no major/minor meaning, so a breaking signature change and a comment tweak both look like +1. The kind thing to do for your consumers (including your future self) is to note breaking changes prominently in the release description, and to prefer adding new exported functions over changing the signatures of existing ones.

Before publishing, test on-chart: code outside your exported functions runs when the library itself is added to a chart, so a few plot() calls exercising your exports make a cheap built-in test harness.

Why pinned versions matter for reproducible backtests

The import statement's most underrated property: import YourUserName/NormUtils/1 pins exactly version 1, forever. When the author publishes version 2, nothing happens to your script. There is no auto-upgrade, no floating "latest" tag, no semver range resolution. Your script's behaviour cannot change unless you edit that line yourself.

Anyone who has debugged a JavaScript project where a transitive dependency shipped a breaking patch overnight will recognise what Pine quietly avoided here. For trading code, the stakes are specific: a backtest is only meaningful if it can be re-run and produce the same result. If library code could update underneath a strategy, last month's tester report and today's tester report could differ with zero visible change in your own source — and you would have no way to know whether a performance shift came from new market data or from someone else's edit. Exact-version pinning removes that entire failure class.

That protection creates a small bookkeeping duty in exchange. Because upgrades are manual, treat a version bump as a real change to your strategy, not housekeeping:

  • Record the full import line — user, library, version — alongside any backtest result you intend to compare against later. It is part of the experiment's configuration, as much as commission or slippage settings.
  • Re-run your baseline after any bump. If results move, the library changed behaviour, and you want to know that before trusting new numbers — diff the two published versions, which is possible precisely because public libraries are open-source.
  • Bump deliberately, not reflexively. "Newer" is not "better" for a dependency inside measurement code; upgrade when the changelog gives you a reason.

None of this makes results good — it makes them comparable, which is the property a backtest cannot do without. Reproducibility is a correctness feature, not a performance one.

Gotchas that bite first-time library authors

A short field guide to the mistakes that surface after the first publish.

Built-ins inside exported functions still follow every-bar execution rules. Functions like ta.lowest() or ta.ema() maintain internal history, and they need to execute on every bar to stay consistent. Wrapping them in a library does not exempt them: if a consumer calls your exported function only inside an if branch, the same inconsistent-history warnings and subtle bugs apply as with any conditionally-called ta.* code. Document whether your exports contain stateful built-ins, and prefer computing on every bar and gating only the use of the value.

The qualifier you export is a contract. Declare a parameter simple int and consumers cannot pass a series value — sometimes exactly right, sometimes needlessly restrictive. Loosening it later (simple to series) is usually safe for callers; tightening it (series to simple) breaks them. Think one version ahead when choosing.

Namespace collisions are on the importer. Two libraries can happily export functions with the same name; the alias disambiguates. Pick meaningful aliases (as norm, as risk) rather than single letters when a script imports several libraries — six months later, n.f(x) is a puzzle.

The library title is part of your public API. It appears verbatim in every consumer's import line, so renaming is effectively republishing under a new identity, with every consumer needing a manual edit. Choose a title you can live with.

Fixes do not propagate. Worth repeating, because it is the inverse of what most package ecosystems train you to expect: shipping version 4 with a bug fix helps nobody until each consumer edits their import. If you maintain several strategies on a shared library, keep a checklist of which script imports which version.

Educational material only, not financial advice — clean library hygiene improves correctness and review-ability, never returns, and trading always carries a real risk of loss.

Key takeaways

Educational only — not financial advice. Trading involves substantial risk of loss.

Catch the bug that compiles.Run auditGet Pro