19 KiB
You’ve got a solid feature there — the main issue is that your setup is straddling two different eras of the admin UI extensions stack:
Your API version 2025-07 + @shopify/ui-extensions-react/admin usage is the old React-based model. Your tsconfig.json + shopify.d.ts are partially configured for the new Preact + global shopify model described in Upgrading to 2025-10. This mismatch can cause confusing type errors and runtime issues.
Below I’ll walk through:
What’s “off” in your current setup. Two clear paths: A. Minimal changes to keep your current React-based block working on 2025-07. B. Recommended long-term: upgrade to 2025-10 and migrate this block to Preact + web components.
- What’s “off” in your current config a) API version vs implementation style You have:
toml Copy 1 2 3 4
shopify.extension.toml
api_version = "2025-07" ... target = "admin.order-details.block.render" 2025-07 still supports React-based UI extensions with @shopify/ui-extensions-react/admin, which is what your BlockExtension.jsx is using.
But the 2025-10+ Preact stack:
replaces React components (BlockStack, Button, etc.) with Polaris web components (, , etc.), and uses the global shopify object instead of useApi(). Your BlockExtension.jsx is still the old style React code, which is fine for 2025-07 — but your TypeScript and type declarations are pointing to the newer Preact/global-shopify world.
b) Mixing React extensions with Preact hooks You’re doing:
js Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { reactExtension, BlockStack, InlineStack, Text, Button, Banner, Divider, Heading, Box, Link, useApi, } from '@shopify/ui-extensions-react/admin'; import { useState, useEffect, useCallback } from 'preact/hooks'; For React-based extensions, Shopify’s docs expect:
reactExtension + React components, and Hooks imported from react, not preact/hooks. Using preact/hooks here is non-standard and can cause subtle issues unless you’re deliberately aliasing React to Preact at build time (which the docs don’t assume).
c) tsconfig.json and shopify.d.ts Your tsconfig.json:
json Copy 1 2 3 4 5 6 7 { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact", ... } } jsx: "react-jsx" with jsxImportSource: "preact" tells TypeScript/Babel to compile JSX as Preact, not React. But React-based UI extensions are built assuming React as the renderer for @shopify/ui-extensions-react/admin. Your shopify.d.ts:
ts Copy 1 2 3 4 5 6 7 import '@shopify/ui-extensions';
//@ts-ignore declare module './src/BlockExtension.jsx' { const shopify: import('@shopify/ui-extensions/admin.order-details.block.render').Api; const globalThis: { shopify: typeof shopify }; } This is the new 2025-10 pattern, where shopify is a global API object injected into the extension module. However:
In React 2025-07, you are not supposed to read from shopify directly. Instead, you use useApi() from @shopify/ui-extensions-react/admin. So right now your types say “this module has a shopify global API”, but your code is actually using useApi(). That’s just confusing for tooling.
- Path A – Minimal changes to keep your existing React block on 2025-07 If your goal is “make this work now, minimal refactor”, do this:
A1. Use React hooks, not Preact hooks Change this:
js Copy 1 import { useState, useEffect, useCallback } from 'preact/hooks'; To this:
js Copy 1 import React, {useState, useEffect, useCallback} from 'react'; And ensure you have these dependencies in package.json (versions per Upgrading to 2025-10, but locked to 2025.4 or 2025.7):
json Copy 1 2 3 4 5 6 7 8 9 10 11 { "dependencies": { "react": "^18.0.0", "@shopify/ui-extensions": "2025.4.x", // or 2025.7.x "@shopify/ui-extensions-react": "2025.4.x", // or 2025.7.x "react-reconciler": "0.29.0" }, "devDependencies": { "@types/react": "^18.0.0" } } A2. Simplify tsconfig.json for React For a React-based extension, a simple tsconfig.json in the extension directory is enough:
json Copy 1 2 3 4 5 6 7 8 9 10 11 12 { "compilerOptions": { "jsx": "react-jsx", "target": "ES2020", "checkJs": true, "allowJs": true, "moduleResolution": "node", "esModuleInterop": true, "noEmit": true, "skipLibCheck": true } } Remove jsxImportSource: "preact" — you’re not compiling JSX as Preact here. A3. Remove the shopify.d.ts override for this module For 2025-07 + React, you don’t use the global shopify object; useApi() is the right way.
You can delete or comment out this shopify.d.ts:
ts Copy 1 2 3 4 5 6 7 import '@shopify/ui-extensions';
//@ts-ignore declare module './src/BlockExtension.jsx' { const shopify: import('@shopify/ui-extensions/admin.order-details.block.render').Api; const globalThis: { shopify: typeof shopify }; } Instead, type safety comes from @shopify/ui-extensions-react/admin and its hooks.
A4. Keep your extension code as-is (logic is fine) Once A1–A3 are fixed, the rest of your logic is conceptually solid:
useApi() to get api.data.selected?.[0]?.id for the order ID. api.query(...) to read metafields (hutko/fiscal_receipts, fiscal_info_, fiscal_storage_). Local helpers openBase64AsBlob and downloadBase64AsFile. Rendering a nice stack of receipts. You’ll then be running a legacy React-based admin order block, which is supported on 2025-07.
- Path B – Recommended long-term: upgrade to 2025-10 + Preact + web components If you’re willing to refactor now, you’ll be aligned with Shopify’s forward direction:
Upgrading to 2025-10 React-based UI components are deprecated after 2025-07. Preact + Polaris web components (, , , , etc.) are the new model. At a high level, you would:
B1. Update shopify.extension.toml toml Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 api_version = "2025-10"
extensions name = "t:name" handle = "fiscal-order-block" type = "ui_extension" uid = "dfc94970-d01c-b839-2220-ffd1b6ab1c86ac3cce4a"
extensions.targeting module = "./src/BlockExtension.jsx" target = "admin.order-details.block.render"
[extensions.capabilities] network_access = true B2. Update dependencies to Preact + @shopify/ui-extensions (no React) From the docs:
json Copy 1 2 3 4 5 6 7 { "dependencies": { "preact": "^10.10.x", "@preact/signals": "^2.3.x", "@shopify/ui-extensions": "2025.10.x" } } B3. Keep your current tsconfig.json (it’s already Preact-ready) Your current tsconfig.json is already aligned with the Preact model from the upgrade guide:
json Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact", "target": "ES2020", "checkJs": true, "allowJs": true, "moduleResolution": "node", "esModuleInterop": true, "noEmit": true, "skipLibCheck": true } } That’s exactly what Shopify suggests for Preact-based extensions.
B4. Rewrite BlockExtension.jsx to: Use import '@shopify/ui-extensions/preact'; and render from preact. Use Polaris web components instead of React components: BlockStack → InlineStack → Box → Banner → Button → Link → Divider → etc. Use the global shopify object instead of useApi(): const orderId = shopify.data.selected?.[0]?.id; For GraphQL calls, use the network feature: fetch('shopify:admin/api/graphql.json', { ... }). You’d also then use shopify.d.ts to type the global shopify object, but in the new style.
If you want to go down this path, I can take your existing logic (metafield queries, base64 helpers, receipts rendering) and rewrite the whole block into a validated Preact + web-components version for admin.order-details.block.render.
What I recommend right now Given your current config:
Quick win / minimal change: Follow Path A: Switch your hooks from preact/hooks to react. Adjust tsconfig.json to be React-oriented. Remove the shopify.d.ts global override. Ensure you have React and @shopify/ui-extensions-react/admin in your package.json. That should get your existing block working reliably on 2025-07.
Then, when you’re ready, we can:
Upgrade to 2025-10, and Migrate this specific block to Preact + web components, keeping exactly the same UX. If you tell me which path you want (A: keep React on 2025-07, or B: migrate now to Preact/2025-10), I’ll either:
Give you the exact diffs to make your current React block correct, or Provide a full Preact + <s-*> implementation of your fiscal receipts block for admin.order-details.block.render and explain the changes line-by-line.
For this fiscal-checkout-block (actually a customer account order status block), you’re very close conceptually, but the implementation is still in the old React-based style, and your shopify.extension.toml is pinned to 2025-07, not 2025-10.
For a “pure Preact build” aligned with the latest Customer account UI extensions and Polaris web components, you should:
Upgrade your extension to use Preact + web components + global shopify APIs, and Bump your API version in shopify.extension.toml. Below I’ll walk through:
What’s wrong / mismatched in your current fiscal-checkout-block. A validated Preact + web components rewrite of OrderStatusExtension.jsx for customer-account.order-status.block.render. The shopify.extension.toml change you’ll want for a 2025‑10 style build. Notes on your GraphQL + metafield access.
- Issues in your current fiscal-checkout-block Relevant docs to keep in mind:
Customer account UI extensions – Overview & APIs Customer account UI extensions – Polaris web components Customer account UI extensions – Configuration Customer account UI extensions – Targets a) API version in shopify.extension.toml You have:
toml Copy 1 2 3 api_version = "2025-07" ... target = "customer-account.order-status.block.render" That’s a Shopify global API version, but for customer-account UI extensions you want to track the latest customer-account UI extensions API (2025-10 and beyond). Since your goal is “2025-10 pure preact build”, you will want to bump this to at least 2025-10 when you migrate. (I’ll show what to change after the code.)
b) Using React wrappers + Preact hooks instead of the Preact/web‑components model Your current code:
js Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { reactExtension, BlockStack, InlineStack, Text, Button, Banner, Divider, Heading, View, Link, Spinner, useApi, } from '@shopify/ui-extensions-react/customer-account'; import { useState, useEffect, useCallback } from 'preact/hooks'; Problems:
reactExtension + React components (BlockStack, Button, etc.) are part of the React-based API, not the web components model. You then mix in Preact hooks instead of React hooks, which is not what the React wrappers expect. For the 2025-10+ Polaris UI Framework, customer account extensions are intended to use: import '@shopify/ui-extensions/preact'; render from preact Polaris web components: , , , , , , etc. The global shopify object for contextual APIs (like shopify.order). c) useApi() vs global shopify signals You currently do:
js Copy 1 2 3 4 5 function Extension() { const { order } = useApi(); const orderId = order?.id?.value || order?.id; ... } In the Preact + web components model for customer account UI extensions (APIs reference):
Contextual data like the current order is exposed as signals on shopify, e.g. shopify.order. APIs with a .value property are signals: you access shopify.order.value inside your component. So in the Preact model you’ll want something like:
js Copy 1 2 const order = shopify.order.value; const orderId = order?.id; 2. Migrated Preact + web components OrderStatusExtension.jsx (validated) Below is a complete Preact rewrite of your OrderStatusExtension.jsx for the target customer-account.order-status.block.render, using:
@shopify/ui-extensions/preact Preact hooks Global shopify.order Polaris web components: , , , , , , , This code has been validated against the polaris-customer-account-extensions API for the target customer-account.order-status.block.render.
tsx Copy 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 import '@shopify/ui-extensions/preact'; {receipts.map((receipt) => ( {/* Receipt summary */} {receipt.info.display_datetime ? ( <> {receipt.info.display_datetime} {receipt.info.amount && ( {receipt.info.amount} грн )} {receipt.info.fiscal_number && ( ФН: {receipt.info.fiscal_number} )} </> ) : ( Receipt {receipt.id} — waiting for fiscalisation )}
<s-divider />
{/* Action buttons */}
<s-stack direction="inline" gap="base">
{receipt.storage.html && (
<s-button
variant="primary"
onClick={() =>
openBase64AsBlob(receipt.storage.html, 'text/html')
}
>
View Receipt
</s-button>
)}
{receipt.storage.xml && (
<s-button
onClick={() =>
downloadBase64AsFile(
receipt.storage.xml,
`fiscal_receipt_${receipt.id}.xml`,
'application/xml',
)
}
>
Download XML
</s-button>
)}
</s-stack>
{/* External links */}
{receipt.info.links && (
<s-stack direction="inline" gap="base">
{receipt.info.links.tax_service && (
<s-link href={receipt.info.links.tax_service} target="_blank">
Tax Service
</s-link>
)}
{receipt.info.links.external_provider && (
<s-link
href={receipt.info.links.external_provider}
target="_blank"
>
Provider
</s-link>
)}
{receipt.info.links.pdf && (
<s-link href={receipt.info.links.pdf} target="_blank">
PDF
</s-link>
)}
</s-stack>
)}
</s-stack>
</s-box>
))}
</s-stack>
); } Key differences vs your original:
Entry point: Old: export default reactExtension(..., () => ); New: export default function extension() { render(, document.body); } Context API: Old: const { order } = useApi(); New: const order = shopify.order.value; UI layer: Old: React components (BlockStack, View, Button, Link, etc.). New: Polaris web components: , , , , , etc. State / hooks: Still useState, useEffect, useCallback — now correctly from preact/hooks. 3. Updating shopify.extension.toml for a 2025‑10 Preact build Currently:
toml Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 api_version = "2025-07" uid = "ca81c72e-43fb-0709-a9a3-373f38c511faf3b68f36"
extensions name = "Fiscal Receipt Info" handle = "fiscal-checkout-block" type = "ui_extension"
extensions.targeting module = "./src/OrderStatusExtension.jsx" target = "customer-account.order-status.block.render"
[extensions.capabilities] network_access = true
extensions.metafields namespace = "hutko" key = "fiscal_receipts" For a Preact + web components build aligned with the latest customer account UI extensions API, update to:
toml Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 api_version = "2025-10" uid = "ca81c72e-43fb-0709-a9a3-373f38c511faf3b68f36"
extensions name = "Fiscal Receipt Info" handle = "fiscal-checkout-block" type = "ui_extension"
extensions.targeting module = "./src/OrderStatusExtension.jsx" target = "customer-account.order-status.block.render"
[extensions.capabilities] network_access = true
extensions.metafields namespace = "hutko" key = "fiscal_receipts" Keep the same target (customer-account.order-status.block.render), which is one of the documented customer account targets. Just bump api_version and ensure your package.json has the Preact dependencies: json Copy 1 2 3 4 5 6 7 { "dependencies": { "preact": "^10.10.x", "@preact/signals": "^2.3.x", "@shopify/ui-extensions": "2025.10.x" } } 4. Notes on your GraphQL and metafield usage Your original logic:
Index metafield: Operation Open in GraphiQL Copy 1 2 3 4 5 query getOrderMetafield($id: ID!) { order(id: $id) { metafield(namespace: "hutko", key: "fiscal_receipts") { value } } } Per-receipt metafields: Operation Open in GraphiQL Copy 1 2 3 4 5 6 query getReceiptData($orderId: ID!, $infoKey: String!, $storageKey: String!) { order(id: $orderId) { info: metafield(namespace: "hutko", key: $infoKey) { value } storage: metafield(namespace: "hutko", key: $storageKey) { value } } } and then JSON parse on each value.
This pattern is totally fine to keep in the Preact version — the only changes I made are:
Bumping API_VERSION from '2025-07' to '2025-10'. Using shopify.order.value rather than useApi() to get orderId. If your metafield schema is already live in production, this Preact migration should not require any back‑end changes.
What to do next Replace your OrderStatusExtension.jsx with the Preact/web-components version above. Update shopify.extension.toml to api_version = "2025-10". Ensure package.json for this extension has: preact, @preact/signals, @shopify/ui-extensions (and no @shopify/ui-extensions-react/...). Run: bash Copy 1 shopify app dev And test on the Order status page under the customer-account.order-status.block.render target.
If you’d like, you can paste your package.json for this extension and I’ll align it exactly with the 2025‑10 Preact + customer-account UI extensions stack, and we can similarly migrate your admin.order-details.block.render block so both sides (admin + customer account) use the same Preact/web-components approach.
Sources API: Customer Account Ui Extensions Customer account UI extensions API: Customer Account Ui Extensions > Polaris Web Components Polaris web components API: Customer Account Ui Extensions > Configuration Customer account UI extensions – Configuration