A gaming desk displays handheld consoles beside a monitor running a stock-tracking dashboard with price charts and alerts.
Search "Xbox handheld release date" in October 2026 and you get three answers, because there are now three ROG Xbox handhelds on sale. Shattered.io has published a 12-step tutorial for a small self-hosted tracker that watches ASUS and Microsoft pages, records price and stock history in SQLite, and posts alerts to Discord. The idea is useful for Windows tinkerers. The published version has a few factual and technical problems, though, and one of them could send false "back in stock" alerts. Below is the verified lineup, the build steps, and the fixes.

A gaming desk displays handheld consoles beside a monitor running a stock-tracking dashboard with price charts and alerts. The lineup as of October 20, 2026​

Here are the product facts checked against official sources.

ROG Xbox Ally and ROG Xbox Ally X (2025). Xbox Wire's September 25, 2025 pre-order post said pre-orders were open in 38 countries. It said the handhelds would be "Launching globally on October 16," with estimated retail prices of USD $999.99 for the Ally X and $599.99 for the Ally. Pre-orders ran through Xbox, the Microsoft Store in the U.S., the ASUS eShop and some local retailers. Xbox also listed Brazil, China, India and several other markets as places where the devices would be sold through existing ROG Ally channels.

ROG Xbox Ally X20 (2026). ASUS's October 15, 2026 announcement says it's available as a standalone device for $1,299 or bundled with the ROG XREAL R1 Edition 20 Gaming AR Glasses, a custom dbrand Killswitch case and a Pelican Protector Case for $2,499. In the U.S. it will be available from October 15, 2026 in the United States from ASUS Store and BestBuy.

Neither older model has been replaced. ASUS says directly that both the ROG XBOX Ally and ROG XBOX Ally X are also still available for sale at their original MSRPs of $599 and $999 respectively.

ModelU.S. availabilityOfficial price (ASUS)Retail listing priceProcessor
ROG Xbox AllyOct. 16, 2025$599$599.99 (Xbox ERP at launch)AMD Ryzen Z2 A
ROG Xbox Ally XOct. 16, 2025$999$999.99 (Xbox ERP at launch)AMD Ryzen AI Z2 Extreme
ROG Xbox Ally X20Oct. 15, 2026$1,299 standalone / $2,499 bundle$1,299.99 / $2,499.99AMD Ryzen AI Z2 Extreme

Two price formats are in circulation. ASUS's press materials use round numbers. Windows Central reported that U.S. buyers could pre-order the X20 standalone model for $1,299.99 MSRP, while the fully loaded ROG Xbox Ally X20 Bundle is priced at $2,499.99 MSRP. If your tracker compares prices exactly, it will see these as different numbers. Normalize them, or expect false "price changed" alerts.

The X20 changes the screen and controls, not the chip. ASUS describes a 7.4-inch OLED ROG Nebula HDR display, magnetic TMR joysticks with a 3% dead zone, a D-pad that switches between four-way and eight-way, and a redesigned cooling system. It also lists a new Travel Mode that caps charging power at 65W for seat outlets on planes and trains.

Shattered says the X20's memory and storage are unconfirmed. Other coverage disagrees. The Gadgeteer points out that the X20's Ryzen AI Z2 Extreme processor, 24GB of memory, 1TB SSD, and 80Wh battery are also found in the existing ROG Xbox Ally X.

The Gen 10 / Project Helix handheld is still a rumor. Shattered cites NoobFeed's October 18 report of comments from Xbox leadership. According to that report, Microsoft's next generation is a family of devices with a traditional console first and handheld hardware as a possibility. No date, price or form factor has been announced. Treat it as a direction Microsoft has hinted at, not a product.

Summary: three active models, two price formats, one rumor. Your tracker has to keep these apart.

Why build a tracker at all?​

Shattered's main argument holds up: "released" is not the same as "available to you right now." Regional rollouts are staggered. Tech Insider reports that outside the U.S., retailers are rolling out stock on a staggered schedule following the October 15 US debut. Bundles and standalone units also sit on separate listings, and stock comes and goes.

A browser extension only checks pages you have open. A script that runs on a schedule keeps checking while you sleep.

One caveat Shattered skips: polling only notices a change at the next successful check. With hourly polling, an alert can be up to an hour late. That isn't "the moment anything changes."

Step 1: Define event types before writing code​

The best idea in the tutorial is this one: "release" covers several different events. Store each as its own type:

  • Announcement: a product or date is confirmed. For the X20, pre-orders and pricing were announced on August 25, 2026.
  • Pre-order: the listing exists and accepts orders.
  • Retail availability: the product ships. For the X20, that was October 15, 2026.
  • Regional availability: a market gets stock.
  • Restock: a sold-out listing can be bought again.

Put a one-line comment at the top of tracker.js for each type, saying which database state triggers it. When an alert fires in six months, you'll want to know why.

Step 2: Set up the project (watch for an ESM gotcha)​

Shattered's setup is a Node.js project with cheerio, better-sqlite3 and dotenv:

Code:
mkdir xbox-handheld-tracker && cd xbox-handheld-tracker
npm init -y
npm install cheerio better-sqlite3 dotenv

Fix 1: add "type": "module" to package.json. All of the tutorial's code uses import syntax. A default npm init -y creates a CommonJS project, so Node rejects those imports. Either add the line or rename your files to .mjs.

Fix 2: the Node version guidance is imprecise. The tutorial says "Node 20 and newer include a global fetch." The fuller history: built-in fetch stopped requiring a flag in Node 18 and was marked stable in Node 21. Current Node documentation says fetch is no longer behind --experimental-fetch CLI flag. The simple answer is to install a current LTS release and drop node-fetch.

Fix 3: better-sqlite3 is a native module. It installs smoothly on mainstream platforms with supported Node versions. On a Raspberry Pi or a very new Node release, you may need build tools if no prebuilt binary matches. That's general Node experience, not something specific to this project.

Put .env and node_modules/ in .gitignore before your first commit. Anyone who has your Discord webhook URL can post to your channel.

Step 3: Treat "unknown" as unknown​

The tutorial uses one small scraper ("adapter") per retailer, which is a good idea. ASUS and Microsoft structure their pages differently, and separate functions mean one site's redesign doesn't break both.

The stock check has a bug that matters. It marks a product in stock unless the page contains "out of stock" or "sold out." A redesign, a translated regional page, a bot-check page or a blank error page would all read as "in stock." That is exactly the false restock alert the tracker exists to prevent.

A safer pattern uses three states and a timeout:

Code:
async function fetchWithTimeout(url, ms = 10000) {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), ms);
  try {
    return await fetch(url, { signal: ctrl.signal });
  } finally {
    clearTimeout(timer);
  }
}

function classifyStock(text) {
  const t = text.toLowerCase();
  if (t.includes("sold out") || t.includes("out of stock")) return "out";
  if (t.includes("add to cart") || t.includes("buy now")) return "in";
  return "unknown"; // never alert on this
}

The phrases here are placeholders. Check the real button and label text in your browser's inspector, because selectors and wording change when storefronts are redesigned. Shattered suggests checking selectors every two or three months.

Step 4: Hash announcement headlines​

Announcements appear on press pages before a product page exists. Shattered's method fetches the Xbox Wire and ASUS pressroom index pages, extracts the heading text, hashes it with SHA-256, and alerts when the hash changes.

This works as a "go look" signal, not an "announcement detected" signal. Both sites publish unrelated news all the time. ASUS's own pressroom currently lists Wi-Fi mesh and Zenbook releases next to the X20. You will get noise, so narrow the selectors to headline text and leave out timestamps and ad slots.

Step 5: Store more fields than the tutorial does​

Shattered's SQLite schema stores label, price (as text), stock flag and timestamp. Add a few columns so comparisons stay meaningful:

Code:
CREATE TABLE IF NOT EXISTS snapshots (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  sku TEXT NOT NULL,          -- e.g. 'xbox-ally-x20-standalone'
  retailer TEXT NOT NULL,     -- 'asus', 'microsoft', 'bestbuy'
  region TEXT NOT NULL,       -- 'US', 'CA'
  url TEXT NOT NULL,
  price_cents INTEGER,
  currency TEXT,
  stock_state TEXT NOT NULL CHECK (stock_state IN ('in','out','unknown')),
  checked_at TEXT NOT NULL
);

Here is why each addition matters:

  • Separate SKU IDs: ASUS still sells the non-Xbox ROG Ally X (2024) under a very similar name. Shattered flags this too.
  • Standalone vs. bundle: the X20 standalone and the $2,499 bundle are different listings. A scraper that grabs the wrong price could report a doubled price overnight.
  • Numeric price plus currency: storing price as cents and currency separately avoids converting C$ to US$ by accident.

Shattered's Canadian and Brazilian prices didn't appear in the official sources checked for this article. Log them as reported until a regional ASUS page confirms them.

Step 6: Check the Discord webhook response​

To create a webhook, open a channel's settings, choose Integrations, then Webhooks, and copy the URL into .env. Shattered's alert function sends the POST but never checks the response, so delivery failures go unnoticed. Add this:

Code:
const res = await fetch(webhookUrl, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ content: message }),
});
if (!res.ok) console.error(`Discord webhook failed: HTTP ${res.status}`);

Keep alert messages short, for example: "ROG Xbox Ally X20 (US, ASUS) back in stock at $1,299.99." Don't paste raw HTML into the channel.

Step 7: Schedule it, including on Windows​

On Linux, cron runs with a minimal environment. Use absolute paths and set the working folder explicitly:

0 * * * * cd /home/you/xbox-handheld-tracker && /usr/bin/node tracker.js >> run.log 2>&1

On Windows, Task Scheduler has a similar trap. A task that runs node.exe directly may start in a different folder, so relative paths like schema.sql and tracker.db fail. The fix is a small wrapper script:

Code:
[USER=35331]@echo[/USER] off
cd /d C:\Tools\xbox-handheld-tracker
"C:\Program Files\nodejs\node.exe" tracker.js >> run.log 2>&1

Then register it to run hourly from an elevated prompt:

schtasks /create /tn "XboxHandheldTracker" /tr "C:\Tools\xbox-handheld-tracker\run.cmd" /sc hourly

To confirm it works:

  • Run the task once by hand.
  • Check that run.log gets a new entry.
  • Check that tracker.db changes in the project folder, not in System32.

Hourly is a sensible interval. Checking every minute is more likely to get your IP blocked than to get you an earlier alert.

Steps 8–11: Regions, rumors, dashboard, restocks​

Regions: add one row per region–SKU pair and tag the currency.

Rumors: point the announcement hasher at Xbox Wire's general news index. There's no Gen 10 handheld page to scrape, and Shattered admits this watcher will be noisy. It also suggests separate "rumor" and "confirmed" rows. Do that, so speculation never mixes into your price history.

Dashboard: a short Node script that prints the latest row per SKU from the past seven days is enough. A web framework would be overkill.

Restocks: watch one inconsistency in the tutorial. Its standalone checkRestock helper uses OFFSET 1, which assumes the new row is already saved. The text, though, says to call it before writing the row. The final tracker.js gets this right: it reads the previous row, inserts the new one, then compares. Whichever way you do it, alert only when the state goes from out to in, and only after two in reads in a row. Shattered's own troubleshooting table recommends that guard. A switch from unknown to in should never trigger an alert.

Step 12: Hosting costs and GitHub Actions caveats​

The tutorial quotes hosting at $4 or $5 a month in different places. VPS prices vary, so treat either figure as a rough guide.

GitHub Actions is a reasonable free option, but it has limits the tutorial doesn't mention. Scheduled workflows run on UTC cron syntax. GitHub warns that scheduled runs can be delayed when its systems are busy. And each runner's local disk is wiped after the job. A tracker.db file sitting on the runner disappears unless you commit it back to the repo or upload it somewhere else.

Back up your database off the machine either way. On a cheap VPS, one bad night can wipe a year of price history.

The verdict​

Shattered's tracker design is solid: separate event types, one scraper per retailer, SQLite history, webhook alerts and a schedule. Before you rely on it, make these changes:

  • Add "type": "module" to package.json, or the imports won't run.
  • Use three stock states (in, out, unknown) so a broken page can't look like a restock.
  • Check the webhook response so failed alerts get logged.
  • On Windows, launch through a .cmd wrapper so the task runs in the right folder.
  • Track the X20 standalone and bundle as separate SKUs, and expect both $1,299 and $1,299.99 to show up.

A Gen 10 Xbox handheld may arrive eventually. For now, three ROG Xbox handhelds are on sale, and that's what the tracker should watch.

 

References

  1. Xbox Handheld Release Date Tracker: 12 Steps, 45 Min (2026) - shattered.io shattered.io 2026-10-20T18:38:57+00:00
  2. ASUS Republic of Gamers Announces Availability of ROG XBOX Ally X20 | ASUS Pressroom - Official Global News & Updates press.asus.com
  3. Workflow syntax for GitHub Actions - GitHub Docs docs.github.com