Drawings are objects you create and own. line.new(), box.new(), label.new(), polyline.new(), linefill.new(), and table.new() each return a handle, and that handle is the whole point: you can restyle it later or remove it with .delete().
Ownership is what makes self-managing annotations possible. Draw a supply zone the moment it forms, then delete its box the instant price mitigates it. Tag a swing high with a label, then move the label as the swing extends. Deleting one drawing leaves every other drawing untouched, so each annotation has its own lifecycle.
This is different from the plot* family. A plot redraws from your series every bar and you never hold a reference to it. A drawing is created once, lives until you remove it, and is addressed by the handle you stored.
The handles
Each constructor returns a handle of the matching kind. Store it in a var so it survives across bars.
| Constructor | Creates | Key arguments |
|---|---|---|
line.new(x1, y1, x2, y2, opts?) | A line between two time/price points | {color, width} |
box.new(x1, y1, x2, y2, opts?) | A filled rectangle between two corners | {bgcolor, border_color, opacity} |
label.new(x, y, text, opts?) | A text label anchored to a time/price | {color, bgcolor, size} |
polyline.new(points, opts?) | A multi-segment path; points is [[t, price], …] | {color, width} |
linefill.new(lineA, lineB, opts?) | A fill between two existing line handles | {color, opacity} |
table.new(position, rows, cols, opts?) | A fixed-position grid | {bgcolor, border_color} |
X coordinates are timestamps in milliseconds (use time() for the current bar). Y coordinates are price levels. linefill.new takes two line handles, not coordinates, and fills the band between them.
Working with a handle
Once you hold a handle you can restyle or update it, then remove it. The setters mutate the drawing in place.
//@version=2
// Draw a level once, on the last bar, && keep its handle.
if (isLastBar) {
var hi = line.new(time() - 20 * currentInterval, high, time(), high, {color: "#2563eb", width: 2})
hi.set_tooltip("session high") // restyle / annotate via the handle
}Common handle methods: .set_tooltip(text) on most drawings, .set_text(text) on labels, .set_opacity(n) on lines and fills, and .setCell(row, col, value, opts?) to write a table cell. Every kind supports .delete().
Full example
This draws a complete annotation set on the last bar: a high and low line, a zone box between them, a label, a three-point path, a fill tying the two lines together, and a small table. It also creates a throwaway label and immediately deletes it, to show that removal is surgical: the temporary label disappears and nothing else moves.
//@version=2
define(title="Verified Drawing Objects", position="onchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
if (isLastBar) {
var t0 = time()
var t1 = t0 + 3600000
var t2 = t0 + 7200000
var topLine = line.new(t0, trade.high, t2, trade.high + 1, {color: "#2563eb", width: 2})
var bottomLine = line.new(t0, trade.low, t2, trade.low - 1, {color: "#dc2626", width: 2})
var zone = box.new(t0, trade.high, t2, trade.low, {bgcolor: "#dbeafe", border_color: "#2563eb", opacity: 0.35})
var note = label.new(t1, trade.close, "last", {color: "#111827", bgcolor: "#f8fafc", size: "small"})
var path = polyline.new([[t0, trade.low], [t1, trade.close], [t2, trade.high]], {color: "#f97316", width: 2})
var fill = linefill.new(topLine, bottomLine, {color: "#93c5fd", opacity: 0.25})
var grid = table.new("top_right", 2, 2, {bgcolor: "#ffffff", border_color: "#94a3b8"})
var temp = label.new(t0, trade.open, "delete", {color: "#64748b"})
topLine.set_tooltip("upper line")
bottomLine.set_tooltip("lower line")
zone.set_tooltip("box zone")
note.set_text("last bar")
path.set_tooltip("three point path")
fill.set_opacity(0.2)
grid.setCell(0, 0, "kind", {text_color: "#111827"})
grid.setCell(0, 1, "value", {text_color: "#111827"})
grid.setCell(1, 0, "close", {text_color: "#111827"})
grid.setCell(1, 1, tostring(trade.close, "0.00"), {text_color: "#2563eb"})
temp.delete()
}
plotLine(value=trade.close, colors=["#2563eb"], width=2, label=["Close"], desc=["close anchor for drawing probe"])After this runs you see two lines, one box, one label, one polyline, one linefill, and one table. The temp label is gone, which is the takeaway: .delete() removes exactly one drawing and never disturbs the rest.
Sticky level tags
Horizontal level lines (prior-day high, VWAP, session levels) read best when the line and its price tag stay pinned beside the y-axis as you pan, instead of scrolling off with the bar they are anchored to. Two opts handle this:
line.new(..., {stickyRight: true})pins the line's right end to the right plot edge. Pair it with{extend: "left"}so the line is a left-extending ray whose right end meets its tag at the axis rather than running past it.label.new(..., {stickyRight: true})pins the label box to the right plot edge.label.newalso acceptsfontWeight(e.g.450) to make the tag lighter or heavier.
The engine records both on the drawing's props; the chart renderer clamps them to the plot edge, so this needs a chart build with sticky-drawing support (verified against engine 3.0.26).
//@version=2
define(title="Sticky Level Tags", position="onchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
// A horizontal level line extended LEFT across history, with its RIGHT end pinned to
// the plot edge (stickyRight), and a price tag pinned to the same edge. As you pan,
// the line + tag stay beside the y-axis instead of scrolling off.
if (isLastBar) {
var lvl = line.new(time() - 50 * currentInterval, trade.high, time(), trade.high, {color: "#2563eb", width: 1, extend: "left", stickyRight: true})
var tag = label.new(time(), trade.high, "PDH", {color: "#ffffff", bgcolor: "#1e222d", size: "small", fontWeight: 450, stickyRight: true})
}
plotLine(value=trade.close, colors=["#2563eb"], width=2, label=["Close"], desc=["close anchor for sticky-level probe"])Limits
There is a cap per drawing kind and a cap on total drawings. The per-kind limit is 500 of any single kind. Going over it stops the script with an error like Drawing per-kind limit exceeded: kind=line, count=501, max=500.
This is why you delete drawings you no longer need. A script that creates a line every bar without ever removing stale ones will hit the cap. Reuse a stored handle, or delete the old drawing before making a new one, so your live count stays bounded.