The blocker to migrating a decade of Excel workbooks usually isn't the data. It's the formulas. The single-value lookup set (XLOOKUP, XMATCH, INDEX and MATCH) is now built and hardened to match Excel, so an Excel-heavy team can move those formulas across 1:1.
What 'parity' means here, concretely: exact match (case-insensitive, like Excel), directional approximate match (exact-or-next-smaller / exact-or-next-larger), wildcard match (* ? with ~ escaping), and forward/reverse search. A lookup miss returns a real #N/A error value, not a blank or a string, so IFNA(XLOOKUP(…)) and IFERROR wrap it the way they do in Excel, and INDEX(range, MATCH(…)) surfaces a clean #N/A when nothing matches.
It's gated on a real Excel-compatibility test suite. Every case asserts the exact value Excel returns. A couple of behaviors are deliberately, and documentedly, lenient (we treat the number 5 as matching the text "5", which Excel doesn't) because our engine is uniformly lenient and it helps people mid-migration. Those are written down, not silent. A multi-column XLOOKUP return collapses to the first column for now. See the dynamic-arrays card for the spill work.
Activity
- Ryan K. opened this entryMay 27, 2026
- moved from Under review to PlannedMay 30, 2026
- moved from Planned to In progressJun 5, 2026
- ShippedJun 5, 2026
Shipped: XLOOKUP, XMATCH, INDEX and MATCH, hardened to Excel and pinned by an Excel-compatibility suite (exact values, not smoke tests). The headline fix was making #N/A a real error value. Before, a lookup miss returned the plain string "#N/A", so IFNA/IFERROR couldn't catch it. Now they do, just like Excel.
Two honest limits, written down rather than hidden: the engine is intentionally lenient on type (number 5 matches text "5", which Excel won't), and a multi-column XLOOKUP return gives you the first column rather than spilling a whole row. The spill work (FILTER, SORT, UNIQUE and multi-column returns) is its own card so we don't claim it before it's real.
LET and LAMBDA need lazy evaluation of bound names, which the current parser doesn't do, so they aren't supported. Real support there is parser-level work, tracked separately. We won't pretend they work.