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/5or a timer like04: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:
- An OCR engine and a language must be available.
- The flow's
ocrEnabledfeature gate must be on.
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
| Setting | Type | Range | Default | What it does |
|---|---|---|---|---|
| Region | rectangle | percentage of screen | — | The area to read |
| What to read (Type) | choice | auto, text, number, counter, time, percentage, custom | auto | Tunes OCR and decides the output variables |
| Read multiple regions | boolean | — | false | Read several areas in one node |
| Regions | list | ≤ 20 | — | Multi-region entries, each with its own variable, type and cleaning |
| Outputs | list | ≤ 8 per region | — | Named output → variable, with an optional Default |
| Pattern | string | ≤ 500 chars | — | Custom type: the named-group regex |
| Auto convert | boolean | — | — | Infer int/float/bool per capture group |
| Character whitelist | string | ≤ 200 chars | — | The engine only recognises these characters |
| Post-process | list | ≤ 16 steps | — | Ordered cleaning: raw, digits, whitelist, regexFilter |
| Language | string | ≤ 32 chars | Auto | OCR language; empty = the configured default |
| Timeout | integer | 1–120000 ms | 5000 | How long to keep trying for fresh text |
| Poll interval | integer | 50–120000 ms | 250 | How 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.
| Type | Produces | Use for |
|---|---|---|
auto | Raw text | When you genuinely don't know |
text | A string | Status words, names |
number | A number | Gold, load, any value you will compare |
counter | <name>_current and <name>_max | 3/5 style counters |
time | <name>_hours and <name>_minutes | 04:20 timers |
percentage | A number | 85% |
custom | One variable per named regex group | Structured text like coordinates |
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 nullable — number?, 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
| Port | Taken when | Required |
|---|---|---|
| Success | The read completed — including an empty region. No text is not a failure | No |
| Failure | No fresh frame within the timeout, or a Custom pattern matched nothing | No |
| Cancelled | The run was stopped mid-read | No — never followed |
| Fatal | OCR engine unavailable, no language, invalid region, invalid Custom regex or post-process config | No — never followed |
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 → producesslot_1_current,slot_1_max - Region 2 →
slot_2, Type Counter → producesslot_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
| Mistake | What happens | Fix |
|---|---|---|
| Using Auto then comparing numerically | Comparison silently fails on a string | Use the Number type |
| Region includes surrounding UI | Poor accuracy | Tighten the region |
| Expecting Failure when there is no text | A plain read succeeds | Use If → Is Empty |
| Custom regex with no named group | Fatal before capture | Add (?P<name>…) |
| Non-nullable variable bound to OCR | Run rejected with TYPE_MISMATCH | Declare number? / string? |
Forgetting ocrEnabled | Node is Fatal immediately | Enable it in flow settings |
| Reading tiny text without zooming | Unreliable reads | Zoom 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.
Related nodes
- Find Image — locate graphics
- Check Region — state with no text
- If — branch on what was read
- Variable — store and compute
- Zoom — enlarge small text first
Related pages
- Variables and Memory — nullable types and defaults
- Flow Anatomy — the OCR feature gate