---
title: Drawing Objects
description: Create, restyle, and delete chart drawings you own — lines, boxes, labels, polylines, fills, and tables.
---

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.

```javascript
//@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.

```javascript title="scripts/probes/drawings/all_drawings.ks"
//@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.new` also accepts `fontWeight` (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).

```javascript title="scripts/probes/drawings/sticky_levels.ks"
//@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.
