tutorials #tanstack-table #vue #beta-adoption #global-filter #change-control #frontend-maintenance #lockfile #pilot-gates #laravel-vue

A table beta bump is still a pilot decision

TanStack Table 9.0.0-beta.64 honors explicit global-filter opt-in. That is pilot hygiene, not permission to unpin stable 8.21.3. Here is the gate list before a Vue admin table leaves the spike branch.

The release note says the table got smarter about global filters.

TanStack Table v9.0.0-beta.64, published 2026-07-31, includes one core fix that sounds small enough to merge while someone else is talking: table-core honors an explicit global-filter opt-in (#6439). Packages on that tag include @tanstack/[email protected] and @tanstack/[email protected]. [Source: https://github.com/TanStack/table/releases/tag/v9.0.0-beta.64]

That line is useful. It is not a production decision.

On the same calendar day, npm dist-tags for both @tanstack/table-core and @tanstack/vue-table still showed:

dist-tagVersion (verified 2026-08-01)
latest8.21.3
beta9.0.0-beta.65 (already past .64)

[Source: https://registry.npmjs.org/@tanstack/table-core] [Source: https://registry.npmjs.org/@tanstack/vue-table]

Read that table twice. Stable is still 8.21.3. The beta channel did not freeze on the opt-in fix you just bookmarked. beta.65 landed minutes later with another global-filter fix (undefined first values). If your spike branch says “install beta” without an exact pin, you are not testing the release note you read. You are testing whichever beta the registry served that hour.

The question is not whether beta.64 demos cleanly in a fresh sandbox. The question is whether a multi-surface admin table — filters, sort, pagination, selection, SSR hydrate, worker parity — still has a named owner, a fixture that would fail if opt-in is ignored, and a one-command rollback when the pilot goes sideways.

Stable pin versus beta spike with filter fixture, owner, and rollback gates

What beta.64 actually changed (primary sources only)

Do not upgrade table infrastructure from a social recap. Read the tag you will install, then confirm the npm tag you will resolve.

Release body — v9.0.0-beta.64

Published 2026-07-31T00:07:47Z, prerelease true.

Fix

  • table-core: honor explicit global filter opt-in (#6439)

Packages on the tag (partial list relevant to Vue admin stacks)

[Source: https://github.com/TanStack/table/releases/tag/v9.0.0-beta.64]

Pull request rationale — #6439

PR #6439 (fix(table-core): honor explicit global filter opt-in) merged 2026-07-30T23:57:23Z. The summary is concrete:

  • Let an explicit per-column global-filter opt-in bypass the default primitive-value heuristic
  • Preserve table-wide filtering controls and custom column eligibility predicates
  • Cover object-valued custom global filtering at capability and row-model levels

Rationale in the PR: by default, global filtering still only opts primitive string and number accessor values in automatically. When a column sets enableGlobalFilter: true, rejecting that column with the same primitive heuristic is surprising and blocks a custom globalFilterFn from handling object or array values.

[Source: https://github.com/TanStack/table/pull/6439]

Nearby betas you will confuse with “the” table beta

TagPublished (UTC)What the body claimsOps takeaway
v9.0.0-beta.592026-07-29More adapter reactivity tests; react adapter sync statePilot hygiene / test depth — still not “unpin stable”
v9.0.0-beta.632026-07-30More readonly columns internallyInternal core churn — pin exact
v9.0.0-beta.642026-07-31Honor explicit global filter opt-in (#6439)Filter fixture belongs on the pilot checklist
v9.0.0-beta.652026-07-31Global filtering with undefined first values (#6438)npm beta tag moved here after .64
v9.0.0-beta.672026-07-31Ship Octane Table source (#6480)Package-scope change on octane line — do not generalize to vue-table

[Source: https://github.com/TanStack/table/releases/tag/v9.0.0-beta.59] [Source: https://github.com/TanStack/table/releases/tag/v9.0.0-beta.63] [Source: https://github.com/TanStack/table/releases/tag/v9.0.0-beta.65] [Source: https://github.com/TanStack/table/releases/tag/v9.0.0-beta.67]

If your team says “we are on table v9 beta,” ask: which tag, which lockfile hash, which fixture file? Anything less is folklore.

Source TanStack Table v9.0.0-beta.64 GitHub release

Source PR #6439 — honor explicit global filter opt-in

Source npm @tanstack/vue-table

Stable is still the production default

This section exists so nobody “helpfully” edits the package.json to ^9.0.0-beta.0 after reading the opt-in fix.

Verified against the npm registry JSON on 2026-08-01:

  • @tanstack/table-coredist-tags.latest = 8.21.3, dist-tags.beta = 9.0.0-beta.65
  • @tanstack/vue-tabledist-tags.latest = 8.21.3, dist-tags.beta = 9.0.0-beta.65
  • Both 9.0.0-beta.64 and 9.0.0-beta.65 exist as published versions; latest does not point at either

[Source: https://registry.npmjs.org/@tanstack/table-core] [Source: https://registry.npmjs.org/@tanstack/vue-table]

Production posture for a normal Laravel + Vue admin app:

LanePackage resolutionAllowed surfaces
Production / main@tanstack/[email protected] (exact or locked latest)Customer admin tables, SSR routes, billing grids
Pilot / spike branchExact beta pin, e.g. 9.0.0-beta.64 or the newer beta you deliberately choseFeature-flagged route, internal tools, dogfood tenant
Broken middle"@tanstack/vue-table": "beta" or floating rangeNowhere — this is how lockfiles lie on Monday

I already wrote the general version of this argument in Before You Adopt a Beta Library, Prove the Exit Path. This post is the Saturday build-in-public instance: one fresh artifact (global-filter opt-in), one stable pin that still holds, one multi-surface gate list you can run before anyone calls the bump “ready to try.”

Production 8.21.3 versus pilot exact pin versus forbidden floating beta tag

Why “more filter tests” is not a green light

beta.59-era notes leaned on adapter reactivity tests. beta.64 adds a behavioral filter fix. Both are healthy upstream signals. Neither answers:

  1. Who owns the pilot after merge?
  2. Which fixture fails if enableGlobalFilter: true is ignored again?
  3. What is the named exit — pin stable forever, or graduate under a flag with a rollback command?
  4. Do sort, filter, pagination, and selection still agree across the main thread and any worker/row-model boundary you already built?

If you already invested in workerized row models, re-read TanStack Table v9 row-model workers. Parity before performance still applies. A global-filter opt-in fix that only passes on the main-thread happy path is not done.

The pilot gate list (copy into the PR)

Treat this as a release checklist, not inspiration.

Gate 0 — Classify the signal

1Signal: TanStack Table v9.0.0-beta.64 (#6439 global filter opt-in)
2Class:  beta application library (spike / pilot only)
3Not:    production default, security hotfix, or "just types"
4Stable: @tanstack/[email protected] remains production pin

If someone argues for production from this tag alone, the classification failed.

Gate 1 — Exact pins + lockfile

 1{
 2  "private": true,
 3  "dependencies": {
 4    "@tanstack/table-core": "9.0.0-beta.64",
 5    "@tanstack/vue-table": "9.0.0-beta.64"
 6  },
 7  "scripts": {
 8    "check:table-pins": "node scripts/check-table-pins.mjs",
 9    "test:table-pilot": "vitest run tests/table-global-filter-pilot.test.ts"
10  }
11}
 1// scripts/check-table-pins.mjs
 2import fs from "node:fs";
 3
 4const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
 5const lockOk = fs.existsSync("pnpm-lock.yaml") || fs.existsSync("package-lock.json");
 6
 7const required = {
 8  "@tanstack/table-core": "9.0.0-beta.64",
 9  "@tanstack/vue-table": "9.0.0-beta.64",
10};
11
12const deps = { ...pkg.dependencies, ...pkg.devDependencies };
13let failed = false;
14
15for (const [name, version] of Object.entries(required)) {
16  const actual = deps[name];
17  if (actual !== version) {
18    console.error(`pin mismatch: ${name} want ${version} got ${actual}`);
19    failed = true;
20  }
21  if (typeof actual === "string" && /^(beta|latest|\^|~|>=)/.test(actual)) {
22    console.error(`floating range forbidden: ${name}=${actual}`);
23    failed = true;
24  }
25}
26
27if (!lockOk) {
28  console.error("lockfile missing — pilot is not reproducible");
29  failed = true;
30}
31
32if (failed) process.exit(1);
33console.log("table pilot pins ok");

Swap 9.0.0-beta.64 for 9.0.0-beta.65 only if the PR description says you intentionally absorbed #6438 as well. Silent drift from .64.65 because someone ran bare npm install is a failed gate, not a free upgrade.

Gate 2 — Global-filter fixture that would have caught the bug class

You need at least one column that is not a primitive string/number accessor, with explicit global-filter opt-in, plus a custom filter function. If the suite only searches string names, you never exercised #6439.

V9 table construction is feature-registered (global filtering is not “all columns forever by default” the way many V8 apps behaved). Copy the invariant, then wire it with the exact helper imports from the beta tag you pinned — do not paste an old V8 createTable snippet into a V9 spike and call it green.

 1// tests/table-global-filter-pilot.test.ts
 2// Behavioral contract for PR #6439. Wire createTable/features from the
 3// exact @tanstack/table-core version in package.json (beta pin).
 4import { describe, expect, it } from "vitest";
 5import { readFileSync } from "node:fs";
 6
 7type OrderRow = {
 8  id: string;
 9  customer: { name: string; code: string };
10  total: number;
11};
12
13const data: OrderRow[] = [
14  { id: "o1", customer: { name: "Ada", code: "A-100" }, total: 40 },
15  { id: "o2", customer: { name: "Ben", code: "B-200" }, total: 12 },
16];
17
18function customerMatches(row: OrderRow, filterValue: unknown): boolean {
19  const q = String(filterValue ?? "").toLowerCase();
20  if (!q) return true;
21  return (
22    row.customer.name.toLowerCase().includes(q) ||
23    row.customer.code.toLowerCase().includes(q)
24  );
25}
26
27describe("global filter opt-in pilot", () => {
28  it("pins the table packages named in the PR", () => {
29    const pkg = JSON.parse(readFileSync("package.json", "utf8"));
30    expect(pkg.dependencies["@tanstack/table-core"]).toBe("9.0.0-beta.64");
31    expect(pkg.dependencies["@tanstack/vue-table"]).toBe("9.0.0-beta.64");
32  });
33
34  it("documents the #6439 fixture rows the product table must satisfy", () => {
35    // Replace filterRows() with your real table.getFilteredRowModel() after
36    // registering global filtering + an object column with enableGlobalFilter.
37    const filterRows = (q: string) =>
38      data.filter((row) => customerMatches(row, q)).map((r) => r.id);
39
40    expect(filterRows("b-200")).toEqual(["o2"]);
41    expect(filterRows("Ada")).toEqual(["o1"]);
42  });
43});

When you wire the real table instance, the acceptance criteria stay fixed:

  1. Column accessor yields an object (not only string / number).
  2. That column explicitly opts into global filtering.
  3. A custom global filter function can read object fields.
  4. Assertions use the filtered row model, not a hand-rolled array filter left in the test forever.

Until step 4 uses the library row model, mark the test it.todo or fail CI — a pure array.filter only proves you understand the bug class, not that the pinned beta honors opt-in.

Gate 3 — Sort / filter / pagination parity matrix

SurfaceFixturePass signal
Global filterObject opt-in column (Gate 2)Expected row ids only
Column filterOne discrete status filter combined with global filterIntersection correct
SortSort by total while filter activeOrder stable and deterministic
PaginationPage size 1 with two matchesPage count and page items agree with filter
SelectionSelect filtered row, clear filterSelection policy documented (keep vs clear)
SSR / hydrateDehydrate filtered state, hydrate clientNo flicker to unfiltered full set
Worker path (if any)Same fixture off main threadSame row ids as main thread

A green unit test on Gate 2 with a broken pagination footer is still a failed pilot.

Six pilot gates checklist: pin exact, filter fixture, parity matrix, feature flag, owner sign-off, rollback command

Gate 4 — Feature flag and surface allow-list

 1// config/tablePilot.ts
 2export const TABLE_PILOT = {
 3  enabled: process.env.TABLE_V9_PILOT === "1",
 4  // Exact strings only — never "beta"
 5  tableCore: "9.0.0-beta.64",
 6  vueTable: "9.0.0-beta.64",
 7  routes: ["/internal/ops/orders-pilot"],
 8  exitDate: "2026-08-15",
 9  owner: "orders-admin",
10} as const;

Production routes keep importing the stable-locked module path. Pilot routes import through a single factory that throws if the env flag is off. No shared “smart” resolver that picks beta because NODE_ENV !== "production" — staging surprises are still surprises.

Gate 5 — Named owner and exit

Write these four lines in the PR body before anyone merges:

1## Table pilot exit
2- Owner: @handle (orders-admin)
3- Success: Gate 2–3 green on CI + dogfood tenant for 5 business days
4- Graduate: only with stable pin plan or continued exact beta pin + flag
5- Rollback: `git revert <sha>` + `pnpm install` restores 8.21.3 lockfile entries
6- Kill date: 2026-08-15 (remove flag and spike deps if not graduated)

No owner means the beta becomes permanent platform drift with a cheerful name.

Gate 6 — Rollback command you have actually run

1#!/usr/bin/env bash
2# scripts/rollback-table-pilot.sh
3set -euo pipefail
4
5git checkout main -- package.json pnpm-lock.yaml
6pnpm install --frozen-lockfile
7pnpm run test:tables
8echo "table pilot rolled back to locked stable"

If rollback needs a hero and a spreadsheet, you do not have rollback. You have hope.

Green signals that still lie

Green signalWhat it usually provesWhat beta.64 still needs
pnpm install exit 0Something resolvedExact 9.0.0-beta.64 (or chosen tag) on every install path
Storybook table rendersChrome mountsObject-column global search on a product route
vite build exit 0Bundle compilesFilter + sort + page footer agree after search
“Reactivity tests upstream”Upstream caresYour adapter version and your state wiring
Demo tenant “looks fine”One happy pathUndefined values, empty filter, selection after clear
CI unit tests on strings onlyStrings work#6439 class never executed

This is the same honesty pattern as UI kit lockfile bumps and runtime patch inventories: green is not done until the artifact that would catch this failure mode exists.

Build-in-public: how I would run the spike this weekend

Saturday rotation is build-in-public. Here is the boring sequence I want on a real orders grid, not a toy:

  1. Branch from main with production still on 8.21.3.
  2. Add exact beta.64 pins (or explicitly beta.65 if you want #6438 in the same spike — write it down).
  3. Land Gate 2 fixture first, before any visual polish.
  4. Wire one internal route behind TABLE_V9_PILOT=1.
  5. Run the parity matrix once on main thread; if you have a worker row-model path, run it twice.
  6. Paste Gate 5 exit block into the PR; set the kill date on a calendar, not in chat.
  7. Dogfood Monday with support/ops, not only engineering.

If step 3 is skipped because “we will add tests after the UI feels right,” stop. That ordering is how betas become load-bearing without evidence.

Optional: package identity assert at boot (pilot route only)
 1// pilot/assertTablePilotVersion.ts
 2import { TABLE_PILOT } from "../config/tablePilot";
 3
 4export function assertTablePilotVersion(actualCore: string, actualVue: string) {
 5  if (actualCore !== TABLE_PILOT.tableCore || actualVue !== TABLE_PILOT.vueTable) {
 6    throw new Error(
 7      `table pilot version drift: core ${actualCore} vue ${actualVue} ` +
 8        `expected ${TABLE_PILOT.tableCore} / ${TABLE_PILOT.vueTable}`,
 9    );
10  }
11}

Resolve actual* from your lockfile audit at build time or from a tiny generated module — not from a hand-edited constant that can desync.

What this post is not claiming

  • That npm latest has left 8.21.3 for a stable v9 line (it has not, as of this writing).
  • That beta.64 is safe for all Vue SSR apps.
  • That you should jump from 8.21.3 on the strength of one filter fix.
  • That upstream reactivity tests replace your product fixtures.
  • Personal outage numbers, invented benchmarks, or unofficial performance wins.

If official docs later publish a migration guide section for global-filter opt-in defaults, update the fixture — do not replace the gate list with vibes.

What you should do Monday morning

  1. Open the npm package pages or registry JSON for @tanstack/vue-table and @tanstack/table-core. Write down latest and beta dist-tags in the ticket. If they differ from this post, trust the registry, not the article date.
  2. Confirm production lockfiles still resolve 8.21.3 (or your chosen stable pin). If anything floats on beta, fix that before any feature work.
  3. If you are not piloting v9, stop here. Add a calendar note to re-check monthly. Do not open a “quick bump” PR from a social link.
  4. If you are piloting, create a branch that pins one exact beta (document .64 vs .65) and commits the lockfile.
  5. Add the object-column global-filter fixture (Gate 2). Make CI fail when it fails.
  6. Fill the parity matrix (Gate 3) for every table surface you already ship: filter, sort, page, selection, hydrate, worker.
  7. Put owner, kill date, and rollback command in the PR body (Gate 5–6). Merge only behind a flag.
  8. Link the PR to your internal developer tools change-control doc or the public hub so the next person does not rediscover the same gates from scratch.

Further reading

A beta bump can be the right engineering move. Calling it done because upstream added filter tests is how quiet registry tags become loud Monday incidents. Pin the version, own the fixture, name the exit.