Skip to main content

Read Text (OCR)

Reads dynamic on-screen values so later nodes can branch on them, type them back, or click by them.

Purpose

Reads text out of one or more rectangles — coordinates, gold, a timer, a status word — and writes it into variables.

You tell it what the area contains (the What to read type) and it tunes the OCR engine and produces correctly typed variables automatically. You do not configure the engine; you describe the content.

When to use it

  • Reading a number you need to compare — gold, load, slot counts.
  • Extracting coordinates so a later node can act on them.
  • Reading a status word to branch on.
  • Reading a counter like 3/5 or a timer like 04:20.

When not to use it

  • To find a button or icon — use Find Image. OCR is slower and less reliable for graphics.
  • To detect a state with no text — use Check Region.
  • To check whether text exists at all — read it, then use an If with Is Empty.

Requirements

Two things, or the node is Fatal:

  1. An OCR engine and a language must be available.
  2. The flow's ocrEnabled feature gate must be on.
OCR is gated per flow

If a Read Text node fails immediately with no output, check features.ocrEnabled in flow settings before debugging the region. This is the most common cause.

How it runs

Settings

SettingTypeRangeDefaultWhat it does
Regionrectanglepercentage of screenThe area to read
What to read (Type)choiceauto, text, number, counter, time, percentage, customautoTunes OCR and decides the output variables
Read multiple regionsbooleanfalseRead several areas in one node
Regionslist≤ 20Multi-region entries, each with its own variable, type and cleaning
Outputslist≤ 8 per regionNamed output → variable, with an optional Default
Patternstring≤ 500 charsCustom type: the named-group regex
Auto convertbooleanInfer int/float/bool per capture group
Character whiteliststring≤ 200 charsThe engine only recognises these characters
Post-processlist≤ 16 stepsOrdered cleaning: raw, digits, whitelist, regexFilter
Languagestring≤ 32 charsAutoOCR language; empty = the configured default
Timeoutinteger1–120000 ms5000How long to keep trying for fresh text
Poll intervalinteger50–120000 ms250How often to re-capture

The content types

This is the setting that matters most. Picking the right type is better than any amount of manual tuning.

TypeProducesUse for
autoRaw textWhen you genuinely don't know
textA stringStatus words, names
numberA numberGold, load, any value you will compare
counter<name>_current and <name>_max3/5 style counters
time<name>_hours and <name>_minutes04:20 timers
percentageA number85%
customOne variable per named regex groupStructured text like coordinates
Pick the specific type, not Auto

Auto returns raw text. A raw string "1167" is not a number, so {{gold}} > 100 silently fails. Choosing Number makes the comparison work.

Custom regex extraction

The custom type takes a regex with named groups and produces one variable per group:

Pattern: X:(?P<x>\d+)\s+Y:(?P<y>\d+)
Screen: X:1167 Y:1120
Result: x = 1167 y = 1120 (as numbers)

The pattern must contain at least one named group (?P<name>…). An invalid or group-less pattern faults the node as Fatal before any capture — it does not silently extract nothing.

Unparseable reads stay empty

A Number or Counter that cannot be parsed writes empty, not 0.

This is deliberate: a fabricated 0 would flow silently into your comparisons and produce wrong decisions. An empty value stays visible. Supply a per-output Default if you want a specific fallback.

Because of this, declare OCR-bound variables as nullablenumber?, not number.

Multi-region reads

One node, several rectangles, each writing its own variable. Use it when you need two or three related values from one screen — reading them in one node means one screenshot, not three.

Each region carries its own type, cleaning and default, so a digits-only price can sit next to a text label.

Flat variables only

A structured type writes flat names (slot_current, not slot.current). This is because {{name}} references resolve by flat name — a nested object would work inside an If but fail as a Click coordinate.

Ports

PortTaken whenRequired
SuccessThe read completed — including an empty region. No text is not a failureNo
FailureNo fresh frame within the timeout, or a Custom pattern matched nothingNo
CancelledThe run was stopped mid-readNo — never followed
FatalOCR engine unavailable, no language, invalid region, invalid Custom regex or post-process configNo — never followed
Failure does not mean "no text"

A plain read succeeds with no text found. Failure means no frame was captured, or a Custom pattern matched nothing. To branch on empty text, read it and then use an If with Is Empty.

Basic example

Read a gold counter:

  • Region: tight rectangle around the number
  • What to read: Number
  • Variable: gold

Then {{gold}} > 1000 works as a numeric comparison.

Realistic example — read coordinates, then decide

One OCR read turns an on-screen label into two numeric variables that drive a real decision.

Advanced example — multi-region capacity check

Read two slot counters in one node and stop when the queue is full:

  • Read multiple regions: on
  • Region 1 → slot_1, Type Counter → produces slot_1_current, slot_1_max
  • Region 2 → slot_2, Type Counter → produces slot_2_current, slot_2_max

Then a single If in expression mode:

{{slot_1_current}} == {{slot_1_max}} || {{slot_2_current}} == {{slot_2_max}}

routing to a Stop: success with the message "All slots full".

One node, one screenshot, four typed variables, one decision.

Best practices

  • Draw the region tightly around just the text. Surrounding UI is the main cause of bad reads.
  • Always pick a specific Type rather than Auto.
  • Zoom in first for small text. OCR accuracy depends heavily on glyph size — pair with Zoom.
  • Declare OCR variables nullable (number?) so an unparseable read does not reject the run.
  • Use Test Node to preview the read and the extracted variables before running the flow.
  • Use multi-region rather than several Read Text nodes reading the same screen.
  • Use digits-only cleaning for numeric fields with stray characters.

Performance notes

  • OCR is substantially more expensive than template matching. Prefer Find Image when you only need to know whether something is present.
  • Cost scales with region area. A tight region is both faster and more accurate.
  • Multi-region reads share one screenshot, so two regions in one node are far cheaper than two nodes.
  • A character whitelist speeds the engine up as well as improving accuracy, because it constrains the search space.
  • Default timeout is 5000 ms with a 250 ms poll — roughly 20 attempts in the worst case. Lower the timeout for values that are either there immediately or not at all.

Memory notes

  • Each poll captures a fresh frame and runs the OCR pipeline over the crop. Frames are transient but the pipeline allocates per pass.
  • Multi-region reads process each region against the same captured frame, so memory scales with region count, not with capture count.
  • Extracted variables are ordinary flow variables and live for the run.

Common mistakes

MistakeWhat happensFix
Using Auto then comparing numericallyComparison silently fails on a stringUse the Number type
Region includes surrounding UIPoor accuracyTighten the region
Expecting Failure when there is no textA plain read succeedsUse If → Is Empty
Custom regex with no named groupFatal before captureAdd (?P<name>…)
Non-nullable variable bound to OCRRun rejected with TYPE_MISMATCHDeclare number? / string?
Forgetting ocrEnabledNode is Fatal immediatelyEnable it in flow settings
Reading tiny text without zoomingUnreliable readsZoom in first

Troubleshooting

The node is Fatal straight away. Check features.ocrEnabled in flow settings, then the Custom regex if you use one.

Numbers come back empty. The read could not be parsed as a number. An empty value is deliberate — check the region and consider digits-only cleaning or a Default.

Comparisons like {{gold}} > 100 never fire. The value is text, not a number. Switch the type to Number.

The read is inconsistent between runs. The region is probably capturing a changing background edge. Tighten it, or add a character whitelist.

The Custom pattern takes Failure sometimes. The pattern matched nothing on that frame. Widen the pattern, or wire Failure to a retry path.

FAQ

Does an empty region mean Failure? No — Success. Only a missing frame or an unmatched Custom pattern routes Failure.

How many regions can one node read? Up to 20.

Why is my counter variable called slot_current? Structured types write flat names so {{name}} references resolve everywhere.

Can I set a fallback when the read fails? Yes — a per-output Default.

Which is faster, OCR or Find Image? Find Image, by a wide margin. Use OCR only when you need the value.