From 86d164b627cd3a851422fa53c60c94e65da00134 Mon Sep 17 00:00:00 2001 From: Henry Stelle Date: Mon, 17 Aug 2026 12:05:06 -0700 Subject: [PATCH 1/6] Support the intercepts target capability Assisted-By: devx/e5cddb80-0e3c-436a-875b-f96ba8a73082 --- .changeset/add-intercepts-capability.md | 5 +++++ packages/app/src/cli/models/extensions/schemas.ts | 1 + .../extensions/specifications/ui_extension.test.ts | 14 +++++++++++--- 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 .changeset/add-intercepts-capability.md diff --git a/.changeset/add-intercepts-capability.md b/.changeset/add-intercepts-capability.md new file mode 100644 index 00000000000..c046ded2e19 --- /dev/null +++ b/.changeset/add-intercepts-capability.md @@ -0,0 +1,5 @@ +--- +'@shopify/app': minor +--- + +Add support for the `intercepts` UI extension target capability. diff --git a/packages/app/src/cli/models/extensions/schemas.ts b/packages/app/src/cli/models/extensions/schemas.ts index 55d0b3d2526..345ee47dd8d 100644 --- a/packages/app/src/cli/models/extensions/schemas.ts +++ b/packages/app/src/cli/models/extensions/schemas.ts @@ -40,6 +40,7 @@ export const ExtensionsArraySchema = zod.object({ const TargetCapabilitiesSchema = zod.object({ allow_direct_linking: zod.boolean().optional(), + intercepts: zod.any().optional(), }) const ShouldRenderSchema = zod.object({ diff --git a/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts b/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts index 089bc427389..17aeff9bc8a 100644 --- a/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts +++ b/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts @@ -241,15 +241,23 @@ describe('ui_extension', async () => { ]) }) - test('targeting object accepts allow_direct_linking for target capabilities', async () => { + test('targeting object accepts typed and arbitrary JSON target capabilities', async () => { const allSpecs = await loadLocalExtensionsSpecifications() const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')! + const intercepts = [ + { + event: 'paymentvalidations', + blocking: true, + applies_to: ['cash'], + future_options: {enabled: true, values: ['one', 2, null]}, + }, + ] const configuration = { targeting: [ { target: 'EXTENSION::POINT::A', module: './src/ExtensionPointA.js', - capabilities: {allow_direct_linking: true}, + capabilities: {allow_direct_linking: true, intercepts}, }, ], api_version: '2023-01' as const, @@ -292,7 +300,7 @@ describe('ui_extension', async () => { metafields: [], urls: {}, default_placement_reference: undefined, - capabilities: {allow_direct_linking: true}, + capabilities: {allow_direct_linking: true, intercepts}, preloads: {}, build_manifest: { assets: { From 81882d9657749bc315d4a20578ae16c4d83aa15b Mon Sep 17 00:00:00 2001 From: Henry Stelle Date: Tue, 25 Aug 2026 11:28:10 -0700 Subject: [PATCH 2/6] Expose extension point capabilities in server kit --- .changeset/add-intercepts-capability.md | 1 + packages/ui-extensions-server-kit/src/types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/.changeset/add-intercepts-capability.md b/.changeset/add-intercepts-capability.md index c046ded2e19..7d3cba12279 100644 --- a/.changeset/add-intercepts-capability.md +++ b/.changeset/add-intercepts-capability.md @@ -1,5 +1,6 @@ --- '@shopify/app': minor +'@shopify/ui-extensions-server-kit': minor --- Add support for the `intercepts` UI extension target capability. diff --git a/packages/ui-extensions-server-kit/src/types.ts b/packages/ui-extensions-server-kit/src/types.ts index 44ce009f07b..25838496e41 100644 --- a/packages/ui-extensions-server-kit/src/types.ts +++ b/packages/ui-extensions-server-kit/src/types.ts @@ -93,6 +93,7 @@ export interface ExtensionPoint { name: string description?: string assets?: Record + capabilities?: Record } type ExtensionPoints = string[] | ExtensionPoint[] | null From 71771b44761ac2abb24fab3072bfc5cfb27a5d41 Mon Sep 17 00:00:00 2001 From: Henry Stelle Date: Wed, 26 Aug 2026 12:55:14 -0700 Subject: [PATCH 3/6] Define intercepts target capability schema --- .../src/cli/models/extensions/schemas.test.ts | 20 ++++++++++++++++++- .../app/src/cli/models/extensions/schemas.ts | 8 +++++++- .../specifications/ui_extension.test.ts | 10 +++------- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/app/src/cli/models/extensions/schemas.test.ts b/packages/app/src/cli/models/extensions/schemas.test.ts index e4244c98a03..754695d2128 100644 --- a/packages/app/src/cli/models/extensions/schemas.test.ts +++ b/packages/app/src/cli/models/extensions/schemas.test.ts @@ -1,4 +1,4 @@ -import {BaseSchema, MAX_UID_LENGTH} from './schemas.js' +import {BaseSchema, MAX_UID_LENGTH, NewExtensionPointsSchema} from './schemas.js' import {describe, expect, test} from 'vitest' const validUIDTestCases = [ @@ -55,3 +55,21 @@ describe('UIDSchema', () => { }) }) }) + +describe('NewExtensionPointsSchema', () => { + test.each([ + ['event', {event: 1, blocking: true}], + ['blocking', {event: 'cartvalidations', blocking: 'true'}], + ['applies_to', {event: 'paymentvalidations', blocking: true, applies_to: [1]}], + ])('rejects an intercept with an invalid %s value', (_field, intercept) => { + const result = NewExtensionPointsSchema.safeParse([ + { + target: 'pos.app.ready.data', + module: './src/app.ts', + capabilities: {intercepts: [intercept]}, + }, + ]) + + expect(result.success).toBe(false) + }) +}) diff --git a/packages/app/src/cli/models/extensions/schemas.ts b/packages/app/src/cli/models/extensions/schemas.ts index 345ee47dd8d..76ede2bd76e 100644 --- a/packages/app/src/cli/models/extensions/schemas.ts +++ b/packages/app/src/cli/models/extensions/schemas.ts @@ -38,9 +38,15 @@ export const ExtensionsArraySchema = zod.object({ extensions: zod.array(zod.any()).optional(), }) +const TargetInterceptSchema = zod.object({ + event: zod.string(), + blocking: zod.boolean(), + applies_to: zod.array(zod.string()).optional(), +}) + const TargetCapabilitiesSchema = zod.object({ allow_direct_linking: zod.boolean().optional(), - intercepts: zod.any().optional(), + intercepts: zod.array(TargetInterceptSchema).optional(), }) const ShouldRenderSchema = zod.object({ diff --git a/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts b/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts index 17aeff9bc8a..a3bcd698075 100644 --- a/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts +++ b/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts @@ -241,16 +241,12 @@ describe('ui_extension', async () => { ]) }) - test('targeting object accepts typed and arbitrary JSON target capabilities', async () => { + test('targeting object accepts intercepts target capabilities', async () => { const allSpecs = await loadLocalExtensionsSpecifications() const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')! const intercepts = [ - { - event: 'paymentvalidations', - blocking: true, - applies_to: ['cash'], - future_options: {enabled: true, values: ['one', 2, null]}, - }, + {event: 'cartvalidations', blocking: true}, + {event: 'paymentvalidations', blocking: true, applies_to: ['cash']}, ] const configuration = { targeting: [ From 62e16a611ca772157d253fe03fd81cf3725316a2 Mon Sep 17 00:00:00 2001 From: Henry Stelle Date: Wed, 26 Aug 2026 20:09:50 -0700 Subject: [PATCH 4/6] Type intercept target capabilities --- configurations/tsconfig.json | 1 + .../src/cli/models/extensions/schemas.test.ts | 20 +++++++++++++------ .../app/src/cli/models/extensions/schemas.ts | 5 +++-- packages/app/tsconfig.build.json | 3 ++- packages/app/tsconfig.json | 7 ++++++- .../ui-extensions-server-kit/src/types.ts | 13 +++++++++++- 6 files changed, 38 insertions(+), 11 deletions(-) diff --git a/configurations/tsconfig.json b/configurations/tsconfig.json index f96b10182d5..c839fe1146f 100644 --- a/configurations/tsconfig.json +++ b/configurations/tsconfig.json @@ -22,6 +22,7 @@ "@shopify/cli-kit/*": ["../packages/cli-kit/src/public/*"], "@shopify/theme/*": ["../packages/theme/src/*"], "@shopify/cli-kit/typing/*": ["../packages/cli-kit/src/typing/*"], + "@shopify/ui-extensions-server-kit": ["../packages/ui-extensions-server-kit/src/index.ts"] }, "types": [ "vitest/importMeta" diff --git a/packages/app/src/cli/models/extensions/schemas.test.ts b/packages/app/src/cli/models/extensions/schemas.test.ts index 754695d2128..d149d24b791 100644 --- a/packages/app/src/cli/models/extensions/schemas.test.ts +++ b/packages/app/src/cli/models/extensions/schemas.test.ts @@ -58,18 +58,26 @@ describe('UIDSchema', () => { describe('NewExtensionPointsSchema', () => { test.each([ - ['event', {event: 1, blocking: true}], - ['blocking', {event: 'cartvalidations', blocking: 'true'}], - ['applies_to', {event: 'paymentvalidations', blocking: true, applies_to: [1]}], - ])('rejects an intercept with an invalid %s value', (_field, intercept) => { + [ + 'valid intercepts', + [ + {event: 'cartvalidations', blocking: true}, + {event: 'paymentvalidations', blocking: true, applies_to: ['cash']}, + ], + true, + ], + ['an invalid event', [{event: 1, blocking: true}], false], + ['an invalid blocking value', [{event: 'cartvalidations', blocking: 'true'}], false], + ['an invalid applies_to value', [{event: 'paymentvalidations', blocking: true, applies_to: [1]}], false], + ])('validates %s', (_description, intercepts, isValid) => { const result = NewExtensionPointsSchema.safeParse([ { target: 'pos.app.ready.data', module: './src/app.ts', - capabilities: {intercepts: [intercept]}, + capabilities: {intercepts}, }, ]) - expect(result.success).toBe(false) + expect(result.success).toBe(isValid) }) }) diff --git a/packages/app/src/cli/models/extensions/schemas.ts b/packages/app/src/cli/models/extensions/schemas.ts index 76ede2bd76e..6579038f6ad 100644 --- a/packages/app/src/cli/models/extensions/schemas.ts +++ b/packages/app/src/cli/models/extensions/schemas.ts @@ -1,4 +1,5 @@ import {zod} from '@shopify/cli-kit/node/schema' +import type {ExtensionPointCapabilities, ExtensionPointIntercept} from '@shopify/ui-extensions-server-kit' export const MAX_EXTENSION_HANDLE_LENGTH = 50 export const MAX_UID_LENGTH = 250 @@ -42,12 +43,12 @@ const TargetInterceptSchema = zod.object({ event: zod.string(), blocking: zod.boolean(), applies_to: zod.array(zod.string()).optional(), -}) +}) satisfies zod.ZodType const TargetCapabilitiesSchema = zod.object({ allow_direct_linking: zod.boolean().optional(), intercepts: zod.array(TargetInterceptSchema).optional(), -}) +}) satisfies zod.ZodType const ShouldRenderSchema = zod.object({ module: zod.string(), diff --git a/packages/app/tsconfig.build.json b/packages/app/tsconfig.build.json index 677bc4b992b..a6c88bb6eca 100644 --- a/packages/app/tsconfig.build.json +++ b/packages/app/tsconfig.build.json @@ -3,6 +3,7 @@ "exclude": ["**/*.test.ts"], "references": [ {"path": "../cli-kit"}, - {"path": "../theme"} + {"path": "../theme"}, + {"path": "../ui-extensions-server-kit"} ] } diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index 65b0195ab48..6858384b889 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -8,5 +8,10 @@ "rootDir": "src", "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" }, - "references": [{"path": "../cli-kit"}, {"path": "../organizations"}, {"path": "../theme"}] + "references": [ + {"path": "../cli-kit"}, + {"path": "../organizations"}, + {"path": "../theme"}, + {"path": "../ui-extensions-server-kit"} + ] } diff --git a/packages/ui-extensions-server-kit/src/types.ts b/packages/ui-extensions-server-kit/src/types.ts index 25838496e41..5a3e469d34f 100644 --- a/packages/ui-extensions-server-kit/src/types.ts +++ b/packages/ui-extensions-server-kit/src/types.ts @@ -83,6 +83,17 @@ interface Metafield { key: string } +export interface ExtensionPointIntercept { + event: string + blocking: boolean + applies_to?: string[] +} + +export interface ExtensionPointCapabilities { + allow_direct_linking?: boolean + intercepts?: ExtensionPointIntercept[] +} + export interface ExtensionPoint { target: string surface: Surface @@ -93,7 +104,7 @@ export interface ExtensionPoint { name: string description?: string assets?: Record - capabilities?: Record + capabilities?: ExtensionPointCapabilities } type ExtensionPoints = string[] | ExtensionPoint[] | null From 9523cd48d932daca93a2a0ae1518b73a9780a5ea Mon Sep 17 00:00:00 2001 From: Henry Stelle Date: Thu, 27 Aug 2026 08:13:49 -0700 Subject: [PATCH 5/6] Decouple intercept schema types --- configurations/tsconfig.json | 1 - packages/app/src/cli/models/extensions/schemas.ts | 5 ++--- packages/app/tsconfig.build.json | 3 +-- packages/app/tsconfig.json | 7 +------ 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/configurations/tsconfig.json b/configurations/tsconfig.json index c839fe1146f..f96b10182d5 100644 --- a/configurations/tsconfig.json +++ b/configurations/tsconfig.json @@ -22,7 +22,6 @@ "@shopify/cli-kit/*": ["../packages/cli-kit/src/public/*"], "@shopify/theme/*": ["../packages/theme/src/*"], "@shopify/cli-kit/typing/*": ["../packages/cli-kit/src/typing/*"], - "@shopify/ui-extensions-server-kit": ["../packages/ui-extensions-server-kit/src/index.ts"] }, "types": [ "vitest/importMeta" diff --git a/packages/app/src/cli/models/extensions/schemas.ts b/packages/app/src/cli/models/extensions/schemas.ts index 6579038f6ad..76ede2bd76e 100644 --- a/packages/app/src/cli/models/extensions/schemas.ts +++ b/packages/app/src/cli/models/extensions/schemas.ts @@ -1,5 +1,4 @@ import {zod} from '@shopify/cli-kit/node/schema' -import type {ExtensionPointCapabilities, ExtensionPointIntercept} from '@shopify/ui-extensions-server-kit' export const MAX_EXTENSION_HANDLE_LENGTH = 50 export const MAX_UID_LENGTH = 250 @@ -43,12 +42,12 @@ const TargetInterceptSchema = zod.object({ event: zod.string(), blocking: zod.boolean(), applies_to: zod.array(zod.string()).optional(), -}) satisfies zod.ZodType +}) const TargetCapabilitiesSchema = zod.object({ allow_direct_linking: zod.boolean().optional(), intercepts: zod.array(TargetInterceptSchema).optional(), -}) satisfies zod.ZodType +}) const ShouldRenderSchema = zod.object({ module: zod.string(), diff --git a/packages/app/tsconfig.build.json b/packages/app/tsconfig.build.json index a6c88bb6eca..677bc4b992b 100644 --- a/packages/app/tsconfig.build.json +++ b/packages/app/tsconfig.build.json @@ -3,7 +3,6 @@ "exclude": ["**/*.test.ts"], "references": [ {"path": "../cli-kit"}, - {"path": "../theme"}, - {"path": "../ui-extensions-server-kit"} + {"path": "../theme"} ] } diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index 6858384b889..65b0195ab48 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -8,10 +8,5 @@ "rootDir": "src", "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" }, - "references": [ - {"path": "../cli-kit"}, - {"path": "../organizations"}, - {"path": "../theme"}, - {"path": "../ui-extensions-server-kit"} - ] + "references": [{"path": "../cli-kit"}, {"path": "../organizations"}, {"path": "../theme"}] } From b7ee9b1721243766f5b3859d332c9fc02c17fda6 Mon Sep 17 00:00:00 2001 From: Henry Stelle Date: Fri, 28 Aug 2026 11:39:57 -0700 Subject: [PATCH 6/6] Default omitted intercept blocking to false --- packages/app/src/cli/models/extensions/schemas.ts | 2 +- .../extensions/specifications/ui_extension.test.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/app/src/cli/models/extensions/schemas.ts b/packages/app/src/cli/models/extensions/schemas.ts index 76ede2bd76e..11d6f2fdc7e 100644 --- a/packages/app/src/cli/models/extensions/schemas.ts +++ b/packages/app/src/cli/models/extensions/schemas.ts @@ -40,7 +40,7 @@ export const ExtensionsArraySchema = zod.object({ const TargetInterceptSchema = zod.object({ event: zod.string(), - blocking: zod.boolean(), + blocking: zod.boolean().default(false), applies_to: zod.array(zod.string()).optional(), }) diff --git a/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts b/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts index a3bcd698075..14aa354b05c 100644 --- a/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts +++ b/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts @@ -245,7 +245,7 @@ describe('ui_extension', async () => { const allSpecs = await loadLocalExtensionsSpecifications() const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')! const intercepts = [ - {event: 'cartvalidations', blocking: true}, + {event: 'cartvalidations'}, {event: 'paymentvalidations', blocking: true, applies_to: ['cash']}, ] const configuration = { @@ -296,7 +296,13 @@ describe('ui_extension', async () => { metafields: [], urls: {}, default_placement_reference: undefined, - capabilities: {allow_direct_linking: true, intercepts}, + capabilities: { + allow_direct_linking: true, + intercepts: [ + {event: 'cartvalidations', blocking: false}, + {event: 'paymentvalidations', blocking: true, applies_to: ['cash']}, + ], + }, preloads: {}, build_manifest: { assets: {