← All guides

Which LibreOffice version do you need for VSTACK, HSTACK, TEXTSPLIT, TAKE and DROP?

Excel’s array-shaping functions — VSTACK, HSTACK, TEXTSPLIT, TAKE, DROP and their relatives — are the functions most likely to decide whether a modern workbook survives a move to LibreOffice Calc. They do not degrade gracefully. Either the release you are running has them, in which case our executed runs show them computing the documented answer, or it does not, in which case every one of them returns #NAME?: the same error you would get from a typo.

We ran the same corpus through four LibreOffice builds — 24.2.0.3, 24.8.7.2, 25.2.0.3 and 25.8.7.3 — and the results sort into three clean groups. Fourteen array-shaping and text-split functions arrive in 25.8.7.3 and are #NAME? in everything older. Nine dynamic-array functions arrived a wave earlier, in 24.8.7.2. And a further nine are still #NAME? in the newest build we tested.

The surprise

25.2 is not enough. It is tempting to assume that a 2025 release of LibreOffice has the 2022-era Excel array functions, but our 25.2.0.3 run returns #NAME? for VSTACK, HSTACK, TEXTSPLIT, TAKE, DROP, TOCOL, TOROW, WRAPROWS, WRAPCOLS, CHOOSECOLS, CHOOSEROWS, TEXTBEFORE, TEXTAFTER and EXPAND — all fourteen. They only start evaluating in our 25.8.7.3 run. The gap between “current-ish” and “new enough” is one release, and it is the difference between a working workbook and a sheet full of errors.

What this means for a migration

The practical rule from our runs is short. A workbook that uses any of the fourteen functions in the first table below needs LibreOffice 25.8 or newer. On 24.2, 24.8 or 25.2 those formulas do not compute a different answer — they do not compute at all, and the #NAME? flows downstream into every total, chart and lookup that reads the result. A workbook that stops at the second table’s nine functions (XLOOKUP, FILTER, SORT, UNIQUE, SEQUENCE, LET, SORTBY, XMATCH, RANDARRAY) needs 24.8 or newer. A workbook that uses anything in the third table has no LibreOffice release in our tested range that will run it, and has to be rewritten.

If you are checking a single formula, paste it into the free compatibility checker linked at the foot of this page; if you are checking a whole file, the Migration Audit button below it takes an .xlsx and flags every formula whose function is missing or behaves differently in the target app. For the full per-function support table across all four builds, see LibreOffice Calc function support by version.

Wave one: fourteen functions that land in 25.8.7.3

Inputs, per case: CHOOSECOLS reads A1:C1 = 1, 2, 3; CHOOSEROWS reads A1:A5 = 10, 20, 30, 40, 50; TAKE and DROP read A1:C3 = 1…9 in reading order; HSTACK reads A1:A2 = 1, 2 and B1:B2 = 3, 4; TOCOL, TOROW and VSTACK read A1:B2 = 1, 2 / 3, 4; WRAPCOLS and WRAPROWS read A1:D1 = 1, 2, 3, 4; EXPAND reads A1:A2 = 5, 7 for its row-padding case and A1 = 5 for its column-padding case; the text cases take string literals.

FunctionFormulaExcel, desktop (documented)Google Sheets (executed 2026-08-29)LibreOffice 24.2.0.3, 24.8.7.2 & 25.2.0.3 (executed)LibreOffice 25.8.7.3 (executed)
CHOOSECOLS=CHOOSECOLS(A1:C1,3,1)3, 13 — read-back 3, 1#NAME?3 — read-back 3, 1 (matches)
CHOOSEROWS=CHOOSEROWS(A1:A5,4,2)40, 2040 — read-back 40, 20#NAME?40 — read-back 40, 20 (matches)
DROP=DROP(A1:C3,1,1)5, 6 / 8, 9#NAME?#NAME?5 — read-back 5, 6, 8, 9 (matches)
EXPAND=EXPAND(A1:A2,3,1,0)5, 7, 0#NAME?#NAME?5 — read-back 5, 7, 0 (matches)
HSTACK=HSTACK(A1:A2,B1:B2)1, 3 / 2, 41 — read-back 1, 3, 2, 4#NAME?1 — read-back 1, 3, 2, 4 (matches)
TAKE=TAKE(A1:C3,2,2)1, 2 / 4, 5#NAME?#NAME?1 — read-back 1, 2, 4, 5 (matches)
TEXTAFTER=TEXTAFTER("a-b-c","-")b-c#NAME?#NAME?b-c
TEXTBEFORE=TEXTBEFORE("a-b-c","-")a#NAME?#NAME?a
TEXTSPLIT=TEXTSPLIT("a,b,c",",")a, b, c#NAME?#NAME?a — read-back a, b, c (matches)
TOCOL=TOCOL(A1:B2)1, 2, 3, 41 — read-back 1, 2, 3, 4#NAME?1 — read-back 1, 2, 3, 4 (matches)
TOROW=TOROW(A1:B2)1, 2, 3, 41 — read-back 1, 2, 3, 4#NAME?1 — read-back 1, 2, 3, 4 (matches)
VSTACK=VSTACK(A1:B1,A2:B2)1, 2 / 3, 41 — read-back 1, 2, 3, 4#NAME?1 — read-back 1, 2, 3, 4 (matches)
WRAPCOLS=WRAPCOLS(A1:D1,2)1, 3 / 2, 41 — read-back 1, 3, 2, 4#NAME?1 — read-back 1, 3, 2, 4 (matches)
WRAPROWS=WRAPROWS(A1:D1,2)1, 2 / 3, 41 — read-back 1, 2, 3, 4#NAME?1 — read-back 1, 2, 3, 4 (matches)

The #NAME? column is one column because all three older builds agreed: every one of these cases returned #NAME? in 24.2.0.3, in 24.8.7.2 and in 25.2.0.3. Of the fourteen, our function dataset records CHOOSECOLS, CHOOSEROWS, HSTACK, TOCOL, TOROW, VSTACK, WRAPCOLS and WRAPROWS as documented in Google Sheets’ function list; it does not record DROP, TAKE, TEXTSPLIT, TEXTBEFORE, TEXTAFTER or EXPAND there. We have now run all fourteen in Sheets, and the executed result matches that inventory exactly: the eight it documents returned their values, and DROP, TAKE, TEXTSPLIT, TEXTBEFORE, TEXTAFTER and EXPAND returned #NAME?.

Two caveats on the 25.8.7.3 column, both covered in detail elsewhere so we will not repeat the tables here. TEXTAFTER and TEXTBEFORE evaluate in 25.8.7.3 but do not honour the if_not_found fallback argument in our runs, and FILTER’s empty-result case reports a different error than Excel documents — both are measured and explained in FILTER no match: Excel #CALC! vs LibreOffice #N/A. For hand-rolled replacements of the two text functions, see TEXTBEFORE/TEXTAFTER vs LEFT, MID and RIGHT.

Wave two: nine functions that landed in 24.8.7.2

These are the previous generation of dynamic-array functions. They are #NAME? in 24.2.0.3 only; from 24.8.7.2 onward our runs show them computing.

FunctionFormulaExcel, desktop (documented)Google Sheets (executed 2026-08-29)LibreOffice 24.2.0.3 (executed)LibreOffice 24.8.7.2, 25.2.0.3 & 25.8.7.3 (executed)
FILTER=FILTER(A1:A5,B1:B5>2)c, d, ec — read-back c, d, e (matches)#NAME?c — read-back c, d, e (matches)
LET=LET(x,5,x*2)1010#NAME?10
RANDARRAY=ROWS(RANDARRAY(2,3))*10+COLUMNS(RANDARRAY(2,3))23 (shape check only — individual values are non-deterministic)23#NAME?23
SEQUENCE=SEQUENCE(5)1, 2, 3, 4, 51 — read-back 1, 2, 3, 4, 5#NAME?1 — read-back 1, 2, 3, 4, 5 (matches)
SORT=SORT(A1:A5)1, 1, 3, 4, 51 — read-back 1, 1, 3, 4, 5 (matches)#NAME?1 — read-back 1, 1, 3, 4, 5 (matches)
SORTBY=SORTBY(A1:A3,B1:B3)y, z, x#NAME?#NAME?y — read-back y, z, x (matches)
UNIQUE=UNIQUE(A1:A5)1, 2, 31 — read-back 1, 2, 3#NAME?1 — read-back 1, 2, 3 (matches)
XLOOKUP=XLOOKUP("b",A1:A3,B1:B3)22#NAME?2
XMATCH=XMATCH("b",A1:A3)22#NAME?2

Inputs: FILTER reads A1:A5 = a…e with B1:B5 = 1…5; SORT and UNIQUE read A1:A5 = 3, 1, 4, 1, 5 and 1, 2, 2, 3, 1 respectively; SORTBY reads A1:A3 = x, y, z with B1:B3 = 3, 1, 2; XLOOKUP and XMATCH read A1:A3 = a, b, c with B1:B3 = 1, 2, 3. Our dataset records FILTER, LET, RANDARRAY, SEQUENCE, SORT, UNIQUE, XLOOKUP and XMATCH as documented in Google Sheets, and does not record SORTBY there. All nine have now been executed in Sheets, including a follow-up run on 2026-08-29 that writes the corpus with plain (unprefixed) function names rather than Excel’s _xlfn._xlws. storage form. LET, RANDARRAY, SEQUENCE, UNIQUE, XLOOKUP, FILTER and SORT all returned their documented values — the plain-name run resolved FILTER and SORT, which an earlier prefixed run had left inconclusive because Sheets’ importer did not map that storage form. Only SORTBY returned #NAME?, and that is a real result: Google does not document SORTBY. The ninth, XMATCH, executed in Sheets and returned 2 — and it exposed an inventory gap on our side: this dataset had recorded XMATCH as not documented in Sheets, because it is absent from the “List of Google Sheets functions” table page we originally scraped. Google does in fact publish a dedicated XMATCH help article, so on 2026-08-29 we fetched and verified it and corrected the inventory: XMATCH is now recorded as documented in Sheets, which is what the executed value said all along.

Sheets semantic split: SORT’s third argument

Excel’s SORT(array, [sort_index], [sort_order], [by_col]) takes a signed sort_order (1 ascending, -1 descending) as its third argument. Google Sheets’ SORT(range, sort_column, is_ascending, …) uses that same position for a boolean is_ascending flag instead. Our corpus’s =SORT(A1:A5,1,-1) case (id SORT_descending) is written the Excel way, intending a descending sort; in the plain-name Sheets run it returned 1 with a read-back of 1, 1, 3, 4, 5 — the ascending result, because Sheets reads the nonzero -1 as truthy for is_ascending rather than as “descending.” A formula copied verbatim from Excel to sort descending will silently sort ascending in Sheets instead. Separately, a degenerate =SORT(A1:A1) case over a single blank cell (id SORT_empty_range_error) returned a blank cell in the Sheets run, not the 0 that a blank sorts to in numeric context and that LibreOffice also returns — sorting a lone blank cell returns blank, not zero, in Sheets.

One wrinkle inside this wave is worth stating because it cuts against the tidy story. A separate SORT case, =SORT(A1:A1) over a single blank cell, returned 0 in 24.8.7.2, #VALUE! in 25.2.0.3, and 0 again in 25.8.7.3. So “landed in 24.8” describes the ordinary path; one degenerate input regressed for a release and came back. It is a reminder that “supported” is per-case, not per-function.

RANDARRAY is volatile, so unlike the other rows in this table our case does not check a literal returned value. The row above is a shape probe: =ROWS(RANDARRAY(2,3))*10+ COLUMNS(RANDARRAY(2,3)) encodes “2 rows, 3 columns” as 23, which is deterministic even though the individual cells are not. A second case in our corpus, =AND(MIN(RANDARRAY(10,10))>=0,MAX(RANDARRAY(10,10))<1), checks that every value in a 10×10 spill lands in [0,1) rather than checking what any cell actually contains. Both cases return #NAME? in 24.2.0.3 and their expected deterministic result (23 and TRUE) in 24.8.7.2, 25.2.0.3 and 25.8.7.3.

Still unsupported in 25.8.7.3

Nine functions returned #NAME? in every build we ran, including the newest. There is no version in our tested range where these start working, so a migration has to replace them rather than wait for an upgrade.

FunctionFormulaExcel, desktop (documented)Google Sheets (executed 2026-08-29)LibreOffice 25.8.7.3 (executed)
ARRAYTOTEXT=ARRAYTOTEXT({1,2,3})1, 2, 3#NAME?#NAME?
BYCOL=BYCOL(A1:B2,LAMBDA(c,SUM(c)))4, 64 — read-back 4, 6 (matches)#NAME?
BYROW=BYROW(A1:B2,LAMBDA(r,SUM(r)))3, 73 — read-back 3, 7 (matches)#NAME?
GROUPBY=GROUPBY(A1:A4,B1:B4,SUM)Documented; spilled layout varies with optional arguments, so our case is a presence probe with no fixed expected value#NAME?#NAME?
MAKEARRAY=MAKEARRAY(2,2,LAMBDA(r,c,r*c))1, 2 / 2, 41 — read-back 1, 2, 2, 4 (matches)#NAME?
MAP=MAP(A1:A3,LAMBDA(x,x*2))2, 4, 62 — read-back 2, 4, 6#NAME?
PIVOTBY=PIVOTBY(A1:A4,B1:B4,C1:C4,SUM)Documented; spilled pivot layout varies with optional arguments, so our case is a presence probe with no fixed expected value#NAME?#NAME?
REDUCE=REDUCE(0,A1:A3,LAMBDA(a,b,a+b))66#NAME?
SCAN=SCAN(0,A1:A3,LAMBDA(a,b,a+b))1, 3, 61 — read-back 1, 3, 6#NAME?

All nine — ARRAYTOTEXT, BYCOL, BYROW, GROUPBY, MAKEARRAY, MAP, PIVOTBY, REDUCE and SCAN — returned #NAME? in all four builds: 24.2.0.3, 24.8.7.2, 25.2.0.3 and 25.8.7.3. Unlike the two waves above, there is no version in our tested range where any of them start working. Google Sheets tells a different story for six of the nine: MAP, REDUCE and SCAN already executed and matched, and the 2026-08-29 plain-name run resolved BYCOL, BYROW and MAKEARRAY from inconclusive to matched as well — all six run correctly in Sheets even though none of them run in any LibreOffice build we tested. ARRAYTOTEXT, GROUPBY and PIVOTBY remain #NAME? in Sheets too, and that is a real result: Google does not document any of the three.

Six of the nine are the LAMBDA helper family, and they have their own writeup with the rewrite recipes: see LAMBDA, MAP & SCAN: Excel vs LibreOffice, which also covers what happens to an inline =LAMBDA(x,x*2)(5) call in Calc.

How we measured the spilling cases

This matters for reading the tables honestly, because most of these functions return an array rather than a single value. Each case is written into its own sheet with no cached result, and the file is recalculated by LibreOffice from scratch; the formula sits in the top-left cell of a check range recorded with the case. The single value shown in the LibreOffice columns is that anchor cell — which is why =VSTACK(A1:B1,A2:B2) shows as 1 even though its documented result is 1, 2 / 3, 4.

The harness also reads back every cell of the recorded check range and compares that list, flattened in reading order, against the flattened expected array — that is the “read-back” shown after each anchor value, and where it says matches, every cell in the recorded range equalled the corresponding expected value. So for these cases the honest claim is: the function evaluates (no #NAME?), the anchor cell matches, and the cells inside the recorded check range match value-for-value in reading order. What we did not verify is the shape of the spill — flattening makes a 2×2 block and a 1×4 row indistinguishable — nor anything outside the recorded range, so a result that spilled further than expected would not be caught. For the scalar cases (TEXTAFTER, TEXTBEFORE, LET, XLOOKUP, XMATCH, RANDARRAY) there is no range: the single returned value is what was compared.

How to migrate safely

Pin the version, if you can. The cheapest fix for wave one is not a rewrite but a requirement: install 25.8 or newer everywhere the workbook will be opened. Our runs show all fourteen functions evaluating there, so a workbook that only uses wave one and wave two functions needs no formula changes at all — it needs a supported release. Where you cannot control the target version, treat 24.8 as the floor and rewrite wave one.

Rewrite wave one with classic functions. =VSTACK(A1:B1,A2:B2) is a literal block of cells, so reference the combined range directly or copy the two pieces into one contiguous block. =HSTACK(A1:A2,B1:B2) is the same trick sideways. =TAKE(A1:C3,2,2) and =DROP(A1:C3,1,1) are sub-ranges: use =INDEX($A$1:$C$3,ROW()-ROW($E$1)+1,COLUMN()-COLUMN($E$1)+1) filled across the target block, or simply point at A1:B2 and B2:C3 when the offsets are fixed. =CHOOSECOLS(A1:C1,3,1) and =CHOOSEROWS(A1:A5,4,2) become one =INDEX(range,row,col) per output cell. =TOCOL(A1:B2) and =TOROW(A1:B2), and the WRAPROWS/WRAPCOLS pair, are index arithmetic: =INDEX($A$1:$B$2,INT((ROW()-1)/2)+1,MOD(ROW()-1,2)+1) filled down reproduces the flatten, with the divisor set to the block width. =TEXTSPLIT("a,b,c",",") becomes LEFT/MID/RIGHT around FIND, or Calc’s Text-to-Columns for a one-off.

Do not rely on catching #NAME?. Wrapping a missing function in =IFERROR(VSTACK(...),"") hides the failure instead of fixing it: the workbook opens clean and reports blanks where data should be, which is worse than a visible error. Fix the version or fix the formula.

Check before you commit to a target. The two buttons below are the fast path: the checker answers “does this one formula survive?” and the Migration Audit answers “which formulas in this file do not?” across a whole .xlsx, in the browser.

Honest limits

The Excel column throughout is Microsoft’s documented behaviour as recorded in our test corpus — we did not run desktop Excel, and nothing on this page is an Excel measurement. The Google Sheets column is executed output: the same .xlsx was imported to Google Drive on 2026-08-29, recalculated by Sheets, and exported back for readback. Sheets has no version to pin, so that run is dated rather than versioned. An earlier pass stored these formulas with Excel’s _xlfn._xlws./_xlfn. storage prefix, which Google’s importer did not map for five cases (FILTER, SORT, BYCOL, BYROW, MAKEARRAY), so those cells came back inconclusive; a follow-up run on 2026-08-29 that writes the same cases with plain, unprefixed function names resolved all five, and the values shown above are from that run. Both runs are kept in our results file, with per-function provenance recorded in subset_runs. The LibreOffice columns are executed output, and every version claim on this page traces to one of four result files from LibreOffice Calc 24.2.0.3, 24.8.7.2, 25.2.0.3 and 25.8.7.3. Those are the only four builds we tested: a function we describe as arriving in 25.8.7.3 may have arrived in any release after 25.2.0.3, and we cannot narrow it further. See our methodology for how recalculation is proven rather than assumed.

Check before you migrate

A note on which Excel this is. The Excel column in the tables above is Microsoft’s documented behaviour for desktop Excel, as recorded in our test corpus — we do not run desktop Excel, and no value in that column is a measurement. Excel for the web is a different application with its own calculation engine, and that one we do run (recalculated on OneDrive, 2026-09-01). Its measured results are published on each function’s own page rather than in these guide tables. Because we have no desktop run to compare against, a disagreement between an Excel-web measurement and the documented column is genuinely ambiguous: it may mean the web engine diverges from the desktop one, or that the documentation is wrong about both. We do not claim to know which.