Guide

Pine Script Label and Line Limits: Fixing the 500 Cap

Drawing objects are capped per script, and the default allowance is far below the maximum. When you exceed it the oldest drawings are deleted silently, so nothing looks broken.

11 min read

Your script draws labels or lines correctly on recent bars and nothing at all further back. There is no error, no red underline, and the code that draws the older ones is identical to the code that draws the visible ones. This is the drawing limit, and its defining characteristic is that it fails quietly: Pine deletes the oldest objects to make room for new ones rather than telling you it ran out. The Pine Editor on TradingView is deliberately strict, which is useful once you know what it is protecting you from, but not very comforting when you need one line fixed now.

This is why the limit is worth knowing about even before you hit it. A silent failure that only affects the parts of the chart you scroll back to is a genuinely difficult thing to notice, and it is easy to spend an hour debugging detection logic that was working perfectly the whole time. The objects were created. They were then removed to make space.

What this error actually means

Labels, lines, boxes, polylines, and table cells are drawing objects, and each type has a per-script cap. The important detail is that the default allowance is much lower than the ceiling you can request, so a script that has not touched the relevant declaration parameter is running with a modest budget rather than the maximum. When the budget is full and the script creates another object, the oldest one is deleted. That is defined behaviour rather than a bug, and it is the mechanism behind the disappearing drawings.

The reason it presents as "recent bars work, old bars do not" follows directly from the execution model. A script processes bars left to right, so the oldest drawings are created first and are therefore first in line to be deleted when the allowance is exhausted. By the time the script reaches the most recent bar, only the last N objects survive. Scroll left and you are looking at the region whose drawings were sacrificed.

The code pattern that causes it

The overwhelmingly common cause is creating a drawing object on every bar, or on a condition that turns out to be far more frequent than expected. A label placed on each bar where a fast moving average is above a slow one is not an occasional annotation; on a chart with twenty thousand bars it is a request for thousands of labels. The second cause is creating objects in a loop, where the count multiplies by the number of iterations as well as the number of bars.

pine
//@version=6
indicator("Drawing limit example", overlay = true)

// No max_labels_count, so this runs on the default allowance.
fast = ta.sma(close, 10)
slow = ta.sma(close, 30)

// A label on every bar where the condition holds. On a long chart this
// is thousands of labels, and only the most recent survive.
if fast > slow
    label.new(bar_index, high, "up", style = label.style_label_down)

The condition here is not an event, it is a state, and it is true for long stretches. Every bar in an uptrend gets its own label. The script compiles, runs, and looks correct near the right edge of the chart, which is precisely what makes it deceptive. Nothing in the code expresses an intent to draw thousands of objects, but that is what it does.

The smallest useful fix

pine
//@version=6
// Raise the allowance deliberately. 500 is the maximum for labels.
indicator("Drawing limit example", overlay = true, max_labels_count = 500)

fast = ta.sma(close, 10)
slow = ta.sma(close, 30)

// Draw on the event, not the state. A crossover happens rarely; the
// condition being true does not.
if ta.crossover(fast, slow)
    label.new(bar_index, high, "cross up", style = label.style_label_down)

// When you only need the most recent annotation, keep one object and
// move it instead of creating a new one on every update.
var label latest = na
if barstate.islast
    label.delete(latest)
    latest := label.new(bar_index, low, "last bar", style = label.style_label_up)

Three separate changes, and the second is the one that actually matters. Raising the allowance buys headroom. Switching from a state to an event reduces the demand by orders of magnitude, because a crossover occurs a handful of times where the condition being true occurs on thousands of bars. The third pattern, deleting and recreating a single object, is the right approach whenever you only ever want to see the latest annotation, and it uses exactly one label no matter how long the chart is.

Watch for objects created inside loops, because the arithmetic is unforgiving. A loop that draws five lines, running on every bar, exhausts any allowance almost immediately. The same applies to multi-timeframe scripts that draw levels for several timeframes at once. If you are drawing in a loop, the question to answer is not how to raise the cap but why the loop needs to run on more than the last bar.

How to diagnose it without guessing

Confirm the diagnosis before changing anything. Scroll to the left edge of the chart: if drawings are missing there and present on the right, this is the limit and not your detection logic. To count what you are creating, replace the drawing call with a counter and plot it, or use log.info() to print each creation. Seeing the actual number is usually the moment the problem becomes obvious, because the count is typically far larger than expected.

Adopt two habits. First, draw on transitions rather than states, which means reaching for ta.crossover() and ta.crossunder() or an explicit comparison against the previous bar rather than a plain condition. Second, delete objects you no longer need, especially when you are only ever interested in the most recent one. Both reduce demand rather than raising supply, and demand is the side of the equation you control.

The counts, and why the default is low

The maximum you can request for labels, lines, and boxes is 500 each, and the default in force when you do not specify anything is considerably lower. That gap is deliberate. Drawing objects are relatively expensive to maintain compared with a plotted series, and a conservative default keeps a script that creates them carelessly from consuming a large budget by accident. It also means the first thing to check when drawings vanish is whether the declaration mentions the relevant parameter at all.

It is worth understanding why plots do not have this problem, because it points at the right tool for many cases. A plotted series is one declaration that produces a value per bar, handled efficiently by the platform. A label is a discrete object with text, position, style, and colour, stored individually. If what you actually want is a continuous visual, a plot or a plotshape is both cheaper and unlimited. Reach for a label when you genuinely need text at a specific point, not as a general-purpose way to mark bars.

Reducing demand instead of raising the cap

The single highest-leverage change is drawing on events rather than states, and it is worth being precise about the distinction. "The fast average is above the slow one" is a state that persists for many bars. "The fast average crossed above the slow one" is an event that happens once per transition. These sound similar in English and differ by a factor of hundreds in how many objects they create. Most drawing-limit problems are this confusion.

The second is to delete deliberately. Every drawing object has a corresponding delete function, and a script that maintains a fixed number of annotations by deleting the old one before creating the new one has a constant footprint regardless of chart length. Keeping a handle in a var variable is what makes this possible, since you need a reference to the object to delete it. This pattern is the right answer for "show me the current level" style indicators, which is a large share of them.

The third is to restrict when drawing happens at all. Many annotations only need to exist on the most recent bar, and wrapping the creation in a barstate.islast check reduces thousands of objects to one. Others only need to cover the visible range rather than all history. Ask what the drawing is for: if it is there to tell you about the current situation, historical copies of it are pure waste, and deleting that waste is better than paying for a larger allowance to store it.

Designing a script that draws sustainably

Start by separating detection from presentation. Compute the condition you care about as a plain boolean or series in the global scope, then decide separately how to show it. This separation matters because it lets you change the presentation without touching the logic, and it makes the drawing decision explicit rather than incidental. A great many drawing-limit problems come from code where the detection and the label creation are the same line, so nobody ever made a decision about how many objects to create.

Then choose the presentation to match the intent. If you want to see where a condition held across all of history, that is a plot or a plotshape, and it has no cap. If you want text with a value in it at a specific moment, that is a label, and you should expect to manage the count. If you want a fixed panel of current readings, that is a table, which is created once and updated rather than recreated. Matching the tool to the intent removes the problem at the source rather than mitigating it.

For zone-style drawings, boxes and lines that mark levels, the sustainable pattern is a bounded collection. Keep an array of the last few objects, and when you add one beyond your chosen limit, delete the oldest yourself. This gives you control over which objects survive, which is a meaningful improvement over letting the platform delete the oldest arbitrarily, because you can decide that the most significant levels persist rather than merely the most recent ones.

Be careful with multi-timeframe drawings specifically. A script that draws higher-timeframe levels can look modest in the code and create a great deal of work, because a single higher-timeframe bar spans many chart bars and naive code redraws on each of them. Draw when the higher-timeframe bar changes rather than on every chart bar, and the count drops to something proportionate to what you are actually displaying.

It is also worth setting the allowance explicitly even when you are comfortably inside it, with a brief comment saying what the script draws. This is not about the limit; it is about the next person, or you in six months, being able to see at a glance that the drawing budget was considered. A declaration with an explicit count and a one-line comment communicates intent in a way that silence does not.

Finally, test on a long chart before concluding the script works. A script developed on a few hundred bars will never show this problem. Scroll back through several years, or switch to a lower timeframe on a liquid symbol, and look at whether the oldest annotations are still there. That check takes ten seconds and catches a failure that is otherwise invisible until someone else reports it.

Drawing on events rather than states is the fix that scales

Why the obvious fix is usually the wrong one

Asked about disappearing labels, a general-purpose chat assistant will nearly always suggest raising max_labels_count, because that is the parameter whose name matches the problem. It is not wrong so much as insufficient. Setting the count to its maximum takes a script that wanted thousands of labels and gives it five hundred, which means drawings still vanish, just slightly further back. The demand side is untouched.

PineScripter is the product we build, and what helps here is that it works against the retrieved Pine Script manual and edits in place, so a proposal can change the condition from a state to an event and show you that change as a diff rather than swapping one number in a declaration. The judgement about what the script should draw is still yours, and the chart is where you confirm it: scroll left and check the oldest annotations survived.

For the underlying language rule, consult TradingView documentation on labels, lines, and drawing objects. Related reading: the broader compile-error guide, why a script runs slowly, other runtime errors, how the execution model works.

A practical checklist before you paste again

Scroll to the left of the chart to confirm the drawings are missing there rather than never created. Count what you create with a plotted counter or log.info(). Change any state condition into an event with ta.crossover() or a comparison against the previous bar. Delete objects you no longer need, and wrap current-state annotations in barstate.islast. Only then raise max_labels_count, max_lines_count, or max_boxes_count, and say in a comment why.

Frequently asked questions

Why do my Pine Script labels disappear on older bars?

You have exhausted the drawing allowance. Labels, lines, and boxes are capped per script, and when the cap is reached Pine deletes the oldest objects to make room for new ones. Because a script processes bars left to right, the oldest drawings are created first and are therefore deleted first, which is why recent bars look fine and older ones are empty.

What is the maximum number of labels in Pine Script?

The maximum you can request is 500 each for labels, lines, and boxes, set with max_labels_count, max_lines_count, and max_boxes_count on the indicator or strategy declaration. The default in force when you do not specify anything is considerably lower, so a script that has never touched those parameters is running on a modest budget.

Does raising max_labels_count fix the problem?

Only if you were close to the limit. It buys headroom, but a script that draws on every bar will exhaust any allowance on a long chart, so the drawings simply vanish slightly further back. The change that actually scales is drawing on events rather than states, using ta.crossover() or a comparison against the previous bar instead of a plain condition.

How do I show only the most recent annotation?

Keep a handle to the object in a var variable, delete it, and create a new one when the value changes. This uses exactly one object no matter how long the chart is. Wrapping the creation in a barstate.islast check achieves the same thing when you only ever want the annotation on the current bar.

Why do plots not have this limit?

A plotted series is one declaration that produces a value per bar and is handled efficiently by the platform. A label is a discrete object with its own text, position, style, and colour, stored individually. If what you want is a continuous visual rather than text at a specific point, a plot or plotshape is both cheaper and uncapped.

How do I tell this apart from broken detection logic?

Scroll to the left edge of the chart. If drawings are present on the right and absent on the left, the detection is working and the allowance is exhausted. To count what you are creating, replace the drawing call with a counter and plot it, or print each creation with log.info(); the number is usually far larger than expected.

The drawing limit is one of the few Pine Script problems that never announces itself, so the habit that protects you is checking the old bars rather than the recent ones. Reduce how many objects you create before you raise how many you are allowed, because an allowance is finite and a per-bar drawing loop is not.

Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.