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. Thirteen array-shaping and text-split
functions arrive in 25.8.7.3 and are #NAME? in everything older. Eight dynamic-array
functions arrived a wave earlier, in 24.8.7.2. And nine more 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 and
TEXTAFTER — all thirteen. 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 thirteen
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 eight functions (XLOOKUP,
FILTER, SORT, UNIQUE, SEQUENCE,
LET, SORTBY, XMATCH) 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: thirteen 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; the text cases take string literals.
| Function | Formula | Excel (documented) | Google Sheets | 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, 1 | Not yet executed | #NAME? | 3 — read-back 3, 1 (matches) |
| CHOOSEROWS | =CHOOSEROWS(A1:A5,4,2) | 40, 20 | Not yet executed | #NAME? | 40 — read-back 40, 20 (matches) |
| DROP | =DROP(A1:C3,1,1) | 5, 6 / 8, 9 | Not yet executed | #NAME? | 5 — read-back 5, 6, 8, 9 (matches) |
| HSTACK | =HSTACK(A1:A2,B1:B2) | 1, 3 / 2, 4 | Not yet executed | #NAME? | 1 — read-back 1, 3, 2, 4 (matches) |
| TAKE | =TAKE(A1:C3,2,2) | 1, 2 / 4, 5 | Not yet executed | #NAME? | 1 — read-back 1, 2, 4, 5 (matches) |
| TEXTAFTER | =TEXTAFTER("a-b-c","-") | b-c | Not yet executed | #NAME? | b-c |
| TEXTBEFORE | =TEXTBEFORE("a-b-c","-") | a | Not yet executed | #NAME? | a |
| TEXTSPLIT | =TEXTSPLIT("a,b,c",",") | a, b, c | Not yet executed | #NAME? | a — read-back a, b, c (matches) |
| TOCOL | =TOCOL(A1:B2) | 1, 2, 3, 4 | Not yet executed | #NAME? | 1 — read-back 1, 2, 3, 4 (matches) |
| TOROW | =TOROW(A1:B2) | 1, 2, 3, 4 | Not yet executed | #NAME? | 1 — read-back 1, 2, 3, 4 (matches) |
| VSTACK | =VSTACK(A1:B1,A2:B2) | 1, 2 / 3, 4 | Not yet executed | #NAME? | 1 — read-back 1, 2, 3, 4 (matches) |
| WRAPCOLS | =WRAPCOLS(A1:D1,2) | 1, 3 / 2, 4 | Not yet executed | #NAME? | 1 — read-back 1, 3, 2, 4 (matches) |
| WRAPROWS | =WRAPROWS(A1:D1,2) | 1, 2 / 3, 4 | Not yet executed | #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
thirteen, 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 or TEXTAFTER there. Either way we have not run any of them in
Sheets, so that column says so rather than guessing.
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: eight 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.
| Function | Formula | Excel (documented) | Google Sheets | 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, e | Not yet executed | #NAME? | c — read-back c, d, e (matches) |
| LET | =LET(x,5,x*2) | 10 | Not yet executed | #NAME? | 10 |
| SEQUENCE | =SEQUENCE(5) | 1, 2, 3, 4, 5 | Not yet executed | #NAME? | 1 — read-back 1, 2, 3, 4, 5 (matches) |
| SORT | =SORT(A1:A5) | 1, 1, 3, 4, 5 | Not yet executed | #NAME? | 1 — read-back 1, 1, 3, 4, 5 (matches) |
| SORTBY | =SORTBY(A1:A3,B1:B3) | y, z, x | Not yet executed | #NAME? | y — read-back y, z, x (matches) |
| UNIQUE | =UNIQUE(A1:A5) | 1, 2, 3 | Not yet executed | #NAME? | 1 — read-back 1, 2, 3 (matches) |
| XLOOKUP | =XLOOKUP("b",A1:A3,B1:B3) | 2 | Not yet executed | #NAME? | 2 |
| XMATCH | =XMATCH("b",A1:A3) | 2 | Not yet executed | #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, SEQUENCE, SORT,
UNIQUE and XLOOKUP as documented in Google Sheets, and does not record
SORTBY or XMATCH there; none has been executed in Sheets by us.
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.
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.
| Function | Formula | Excel (documented) | Google Sheets | LibreOffice 25.8.7.3 (executed) |
|---|---|---|---|---|
| ARRAYTOTEXT | =ARRAYTOTEXT({1,2,3}) | 1, 2, 3 | Not yet executed | #NAME? |
| BYCOL | =BYCOL(A1:B2,LAMBDA(c,SUM(c))) | 4, 6 | Not yet executed | #NAME? |
| BYROW | =BYROW(A1:B2,LAMBDA(r,SUM(r))) | 3, 7 | Not yet executed | #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 | Not yet executed | #NAME? |
| MAKEARRAY | =MAKEARRAY(2,2,LAMBDA(r,c,r*c)) | 1, 2 / 2, 4 | Not yet executed | #NAME? |
| MAP | =MAP(A1:A3,LAMBDA(x,x*2)) | 2, 4, 6 | Not yet executed | #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 | Not yet executed | #NAME? |
| REDUCE | =REDUCE(0,A1:A3,LAMBDA(a,b,a+b)) | 6 | Not yet executed | #NAME? |
| SCAN | =SCAN(0,A1:A3,LAMBDA(a,b,a+b)) | 1, 3, 6 | Not yet executed | #NAME? |
Seven of these nine — ARRAYTOTEXT, BYCOL, BYROW,
MAKEARRAY, MAP, 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. The remaining two
are a narrower claim: GROUPBY and PIVOTBY were added to our corpus later
and appear only in our 25.2.0.3 and 25.8.7.3 result files, where both returned #NAME?.
We make no claim about them in 24.2.0.3 or 24.8.7.2 because we did not run them there.
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) 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 thirteen 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 Excel, and nothing on this page is an Excel measurement. Google Sheets has not been run through our harness at all; where we say a function is documented in Sheets that is a claim about our dataset’s record of Google’s function list, not about a result we observed. 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.