From 321802804842086ae664506be45f1e9d8502ba20 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 10 Apr 2026 10:58:21 +0200 Subject: [PATCH] docs: update extension config schema examples to new `configSchema` format Switch all documentation examples from the deprecated `config: { schema: { field: z => z.type() } }` pattern to the new `configSchema: { field: z.type() }` format using zod v4 imports. Also update catalog-graph README to use current blueprint APIs. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .../search/declarative-integration.md | 22 ++++---- .../architecture/20-extensions.md | 16 +++--- .../architecture/23-extension-blueprints.md | 20 +++----- .../architecture/25-extension-overrides.md | 22 ++++---- .../utility-apis/02-creating.md | 8 +-- plugins/catalog-graph/README-alpha.md | 50 ++++++++----------- 6 files changed, 60 insertions(+), 78 deletions(-) diff --git a/docs/features/search/declarative-integration.md b/docs/features/search/declarative-integration.md index 3ad7492114..06d46ae86c 100644 --- a/docs/features/search/declarative-integration.md +++ b/docs/features/search/declarative-integration.md @@ -78,17 +78,16 @@ _Example creating a custom `TechDocsSearchResultItemExtension`_ ```tsx // plugins/techdocs/alpha.tsx import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha'; +import { z } from 'zod'; /** @alpha */ export const TechDocsSearchResultListItemExtension = createSearchResultListItemExtension({ id: 'techdocs', - configSchema: createSchemaFromZod(z => - z.object({ - noTrack: z.boolean().default(false), - lineClamp: z.number().default(5), - }), - ), + configSchema: { + noTrack: z.boolean().default(false), + lineClamp: z.number().default(5), + }, predicate: result => result.type === 'techdocs', component: async ({ config }) => { const { TechDocsSearchResultListItem } = await import( @@ -188,17 +187,16 @@ Here is the `plugins/techdocs/alpha/index.tsx` final version, and you can also t // plugins/techdocs/alpha.tsx import { createPlugin } from '@backstage/frontend-plugin-api'; import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha'; +import { z } from 'zod'; /** @alpha */ export const TechDocsSearchResultListItemExtension = createSearchResultListItemExtension({ id: 'techdocs', - configSchema: createSchemaFromZod(z => - z.object({ - noTrack: z.boolean().default(false), - lineClamp: z.number().default(5), - }), - ), + configSchema: { + noTrack: z.boolean().default(false), + lineClamp: z.number().default(5), + }, predicate: result => result.type === 'techdocs', component: async ({ config }) => { const { TechDocsSearchResultListItem } = await import( diff --git a/docs/frontend-system/architecture/20-extensions.md b/docs/frontend-system/architecture/20-extensions.md index 14c31764ca..35b166115e 100644 --- a/docs/frontend-system/architecture/20-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -278,15 +278,15 @@ In addition to being able to access data passed through the input, you also have With the `app-config.yaml` there is already the option to pass configuration to plugins or the app to e.g. define the `baseURL` of your app. For extensions this concept would be limiting as an extension can be independent of the plugin & initiated several times. Therefore we created a possibility to configure each extension individually through config. The extension config schema is created using the [`zod`](https://zod.dev/) library, which in addition to TypeScript type checking also provides runtime validation and coercion. If we continue with the example of the `navigationExtension` and now want it to contain a configurable title, we could make it available like the following: ```tsx +import { z } from 'zod'; + const navigationExtension = createExtension({ // ... namespace: 'app', name: 'nav', // [3]: Extension `id` will be `app/nav` following the extension naming pattern - config: { - schema: { - title: z => z.string().default('Sidebar Title'), - }, + configSchema: { + title: z.string().default('Sidebar Title'), }, factory({ config }) { return [ @@ -321,12 +321,12 @@ In all examples so far we have defined the extension factory as a regular functi For example, this is how we could define an extension where its output depends on the configuration: ```tsx +import { z } from 'zod'; + const exampleExtension = createExtension({ // ... - config: { - schema: { - disableIcon: z.boolean().default(false), - }, + configSchema: { + disableIcon: z.boolean().default(false), }, output: [coreExtensionData.reactElement, iconDataRef.optional()], *factory({ config }) { diff --git a/docs/frontend-system/architecture/23-extension-blueprints.md b/docs/frontend-system/architecture/23-extension-blueprints.md index 97eb01022d..e23f19eeb8 100644 --- a/docs/frontend-system/architecture/23-extension-blueprints.md +++ b/docs/frontend-system/architecture/23-extension-blueprints.md @@ -31,12 +31,12 @@ Every extension blueprint also provides a `makeWithOverrides` method. It is usef The following is an example of how one might use the blueprint `makeWithOverrides` method to create a new extension: ```tsx +import { z } from 'zod'; + const myPageExtension = PageBlueprint.makeWithOverrides({ // This defines additional configuration options for the extension. - config: { - schema: { - layout: z => z.enum(['grid', 'rows']).default('grid'), - }, + configSchema: { + layout: z.enum(['grid', 'rows']).default('grid'), }, // This defines additional inputs for the extension. inputs: { @@ -118,10 +118,8 @@ export interface MyWidgetBlueprintParams { export const MyWidgetBlueprint = createExtensionBlueprint({ kind: 'my-widget', attachTo: { id: 'page:my-plugin', input: 'widgets' }, - config: { - schema: { - title: z.string().optional(), - }, + configSchema: { + title: z.string().optional(), }, output: [coreExtensionData.reactElement], factory(params: MyWidgetBlueprintParams, { config }) { @@ -196,10 +194,8 @@ const widgetTitleRef = createExtensionDataRef().with({ export const MyWidgetBlueprint = createExtensionBlueprint({ kind: 'my-widget', attachTo: { id: 'page:my-plugin', input: 'widgets' }, - config: { - schema: { - title: z.string().optional(), - }, + configSchema: { + title: z.string().optional(), }, output: [widgetTitleRef, coreExtensionData.reactElement], factory(params: MyWidgetBlueprintParams, { config }) { diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index 22d553b084..f8221dd217 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -182,20 +182,18 @@ const myOverrideExtension = myExtension.override({ Overriding the configuration schema works very similarly to overriding the declared inputs. You can define new configuration fields that will be merged with the existing ones, but you can not re-declare existing fields. The following example shows how to override an extension and add a new configuration field: ```tsx +import { z } from 'zod'; + const exampleExtension = createExtension({ - config: { - schema: { - foo: z => z.string(), - }, + configSchema: { + foo: z.string(), }, // ... }); const overrideExtension = exampleExtension.override({ - config: { - schema: { - bar: z => z.string(), - }, + configSchema: { + bar: z.string(), }, factory(originalFactory, { config }) { // @@ -210,12 +208,12 @@ const overrideExtension = exampleExtension.override({ In all examples so far we have called the `originalFactory` callback without any arguments. It is however possible to override parts of the factory context for the original factory using the first parameter of the original factory. This can be useful if you want to override the provided configuration or change the inputs in some way. Note that if you are implementing a `factory` for a blueprint, the override factory context will instead be the second parameter of the original factory function. The following is an example of how to override the configuration for the original factory: ```tsx +import { z } from 'zod'; + const exampleExtension = createExtension({ name: 'example', - config: { - schema: { - layout: z => z.enum(['grid', 'list']).optional(), - }, + configSchema: { + layout: z.enum(['grid', 'list']).optional(), }, output: [coreExtensionData.reactElement], factory: ({ config }) => [ diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index 2f6959e0ce..c6c6ce3785 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -94,11 +94,11 @@ The extension ID of the work API will be the kind `api:` followed by the plugin Here we will describe how to amend a utility API with the capability of having extension config, which is driven by [your app-config](../../conf/writing.md). You do this by giving an extension config schema to your API extension factory function. Let's refactor the example above to also accept configuration, which will require us to use the [override method of the blueprint](../architecture/23-extension-blueprints.md#creating-an-extension-from-a-blueprint-with-overrides). ```tsx title="in @internal/plugin-example" +import { z } from 'zod'; + const exampleWorkApi = ApiBlueprint.makeWithOverrides({ - config: { - schema: { - goSlow: z => z.boolean().default(false), - }, + configSchema: { + goSlow: z.boolean().default(false), }, factory(originalFactory, { config }) { return originalFactory({ diff --git a/plugins/catalog-graph/README-alpha.md b/plugins/catalog-graph/README-alpha.md index b7cd5f65be..0408849737 100644 --- a/plugins/catalog-graph/README-alpha.md +++ b/plugins/catalog-graph/README-alpha.md @@ -189,31 +189,20 @@ app: Overriding the card extension allows you to modify how it is implemented. -> [!CAUTION] -> To maintain the same level of configuration, you should define the same or an extended configuration schema. - Here is an example overriding the card extension with a custom component: ```tsx -import { createFrontendModule } from '@backstage/backstage-plugin-api'; +import { createFrontendModule } from '@backstage/frontend-plugin-api'; import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'catalog-graph', extensions: [ - EntityCardBlueprint.makeWithOverrides({ + EntityCardBlueprint.make({ name: 'entity-relations', - config: { - schema: { - filter: z => z.string().optional(), - // Omitting the rest of default configs for simplicity in this example - }, - }, - factory(originalFactory, { config }) { - return originalFactory({ - loader: () => - import('./components').then(m => ), - }); + params: { + loader: () => + import('./components').then(m => ), }, }), ], @@ -291,29 +280,30 @@ app: Overriding the page extension allows you to modify how it is implemented. > [!CAUTION] -> To maintain the same level of configuration, you need to define the same or an extended configuration schema. In order to avoid side effects on external plugins that expect this page to be associated with the default path and route reference, remember to use the same default path so that applications that use the default path still point to the same page and the same route reference. +> In order to avoid side effects on external plugins that expect this page to be associated with the default path and route reference, remember to use the same default path so that applications that use the default path still point to the same page and the same route reference. -Here is example overriding the page extension with a custom component: +Here is an example overriding the page extension with a custom component: ```tsx -import { createFrontendModule, createPageExtension, createSchemaFromZod } from '@backstage/backstage-plugin-api'; +import { + createFrontendModule, + PageBlueprint, +} from '@backstage/frontend-plugin-api'; import { convertLegacyRouteRef } from '@backstage/core-compat-api'; import { catalogGraphRouteRef } from '@backstage/plugin-catalog-graph'; export default createFrontendModule({ pluginId: 'catalog-graph', extensions: [ - createPageExtension({ - // Omitting name since it is an index page - path: '/catalog-graph', - routeRef: convertLegacyRouteRef(catalogGraphRouteRef), - createSchemaFromZod(z => z.object({ - path: z.string().default('/catalog-graph') - // Omitting the rest of default configs for simplicity in this example - })), - loader: () => import('./components').then(m => ) - }) - ] + PageBlueprint.make({ + params: { + path: '/catalog-graph', + routeRef: convertLegacyRouteRef(catalogGraphRouteRef), + loader: () => + import('./components').then(m => ), + }, + }), + ], }); ```