From 35eeb4d90748424eb39a67bc04f7745bd52db605 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Mon, 11 Dec 2023 16:06:52 +0100 Subject: [PATCH 01/10] Work on extension documentation Co-authored-by: Patrik Oldsberg Co-authored-by: Camila Belo Signed-off-by: Philipp Hugenroth --- .../architecture/03-extensions.md | 78 +++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index 7deafbd74f..e9e979a7f1 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -60,7 +60,7 @@ Extensions are created using the `createExtension` function from `@backstage/fro ```tsx const extension = createExtension({ - id: 'my-extension', + name: 'my-extension', // This is the attachment point, `id` is the ID of the parent extension, // while `input` is the name of the input to attach to. attachTo: { id: 'my-parent', input: 'content' }, @@ -71,10 +71,10 @@ const extension = createExtension({ element: coreExtensionData.reactElement, }, // This factory is called to instantiate the extensions and produce its output. - factory({ bind }) { - bind({ + factory() { + return { element:
Hello World
, - }); + }; }, }); ``` @@ -85,7 +85,75 @@ Note that while the `createExtension` is public API and used in many places, it ## Extension Data -TODO +Communication between extensions happens in one direction, from one child extension through the attachment point to its parent. The child extension outputs data which is then passed as inputs to the parent extension. This data is called Extension Data, where the shape of each individual piece of data is described by an Extension Data Reference. These references are created separately from the extensions themselves, and can be shared across multiple different kinds of extenses. Each reference consists of an ID and a TypeScript type that the data needs to conform to, and represents one type of data that can be shared between extensions. + +### Extension Data References + +To create a new extension data reference to represent a type of shared extension data you use the `createExtensionDataRef` function. When defining a new reference you need to provide an ID and a TypeScript type, for example: + +```ts +export const reactElementExtensionDataRef = + createExtensionDataRef('my-plugin.reactElement'); +``` + +The `ExtensionDataRef` can then be used to describe an output property of the extension. This will enforce typing on the return value of the extension factory: + +```tsx +const extension = createExtension({ + // ... + output: { + element: reactElementExtensionDataRef, + }, + factory() { + return { + element:
Hello World
, + }; + }, +}); +``` + +### Extension Data Uniqueness + +Note that the key used in the output map, in this case `element`, is only used internally within the definition of the extension itself. That actual identifier for the data when consumed by other extensions is the ID of the reference, in this case `core.reactElement`. This means that you can not output multiple different values for the same extension data reference, as they would conflict with each other. That in turn makes overly generic extension data references a bad idea, for example a generic "string" type. Instead create separate references for each type of data that you want to share. + +```tsx +const extension = createExtension({ + // ... + output: { + // ❌ Bad example - outputting values of same type + element1: reactElementExtensionDataRef, + element2: reactElementExtensionDataRef, + }, + factory() { + return { + element1:
Hello World
, + element2:
Hello World
, + }; + }, +}); +``` + +### Core Extension Data + +We provide default `coreExtensionData`, which provides commonly used `ExtensionDataRef` - e.g. for `React.JSX.Element`, `RouteRef` or `AppTheme`. They can be used when creating your own extension. For example, the React Element extension data that we defined above is already provided as `coreExtensionData.reactElement`. For a full list and explanations of all types of core extension data, see the [core extension data reference](#TODO). + +### Optional Extension Data + +By default all extension data is required, meaning that the extension factory must provide a value for each output. However, it is possible to make extension data optional by calling the `.optional()` method. This makes it optional for the factory function to return a value as part of its output. When calling the `.optional()` method you create a new copy of the extension data reference, it does not mutate the existing reference. + +```tsx +const extension = createExtension({ + // ... + output: { + element: coreExtensionData.reactElement.option(), + }, + factory() { + return { + element: Math.random() < 0.5 ?
Hello World
: undefined, + }; + }, +}); +``` ## Extension Inputs From 6e7a52cc0be0e0c3709d392a8f1ea3d479d33483 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Mon, 11 Dec 2023 17:40:46 +0100 Subject: [PATCH 02/10] Fix typo Signed-off-by: Philipp Hugenroth --- docs/frontend-system/architecture/03-extensions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index e9e979a7f1..5fca53f420 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -85,7 +85,7 @@ Note that while the `createExtension` is public API and used in many places, it ## Extension Data -Communication between extensions happens in one direction, from one child extension through the attachment point to its parent. The child extension outputs data which is then passed as inputs to the parent extension. This data is called Extension Data, where the shape of each individual piece of data is described by an Extension Data Reference. These references are created separately from the extensions themselves, and can be shared across multiple different kinds of extenses. Each reference consists of an ID and a TypeScript type that the data needs to conform to, and represents one type of data that can be shared between extensions. +Communication between extensions happens in one direction, from one child extension through the attachment point to its parent. The child extension outputs data which is then passed as inputs to the parent extension. This data is called Extension Data, where the shape of each individual piece of data is described by an Extension Data Reference. These references are created separately from the extensions themselves, and can be shared across multiple different kinds of extensions. Each reference consists of an ID and a TypeScript type that the data needs to conform to, and represents one type of data that can be shared between extensions. ### Extension Data References From 3254843e5bd3263f9ccf2dea19275615d6e51401 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 15 Dec 2023 18:25:42 +0100 Subject: [PATCH 03/10] Section on extension inputs Signed-off-by: Philipp Hugenroth --- .../architecture/03-extensions.md | 96 ++++++++++++++++--- 1 file changed, 85 insertions(+), 11 deletions(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index 5fca53f420..af87a89c05 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -145,11 +145,11 @@ By default all extension data is required, meaning that the extension factory mu const extension = createExtension({ // ... output: { - element: coreExtensionData.reactElement.option(), + element: coreExtensionData.reactElement.optional(), }, factory() { return { - element: Math.random() < 0.5 ?
Hello World
: undefined, + element: , }; }, }); @@ -157,23 +157,97 @@ const extension = createExtension({ ## Extension Inputs - +```tsx + // ... + factory({ inputs }) { + return { + element: ( + + ), + }; + }, +``` ## Extension Configuration From ccf66a33ce1bdf37d4840a5689bb7dcfe1d01886 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Sat, 16 Dec 2023 15:49:59 +0100 Subject: [PATCH 04/10] Continue docs Signed-off-by: Philipp Hugenroth --- .../architecture/03-extensions.md | 53 +++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index af87a89c05..63ea56f884 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -229,7 +229,7 @@ const navigationExtension = createExtension({ In this case the extension input `items` is an array, where each individual item is an extension that attached itself to the extension inputs of this `id`. -With the `inputs` not only the `output` is passed to the extension, but also the `node`. It is discouraged to consume and/or modify the `node` here. If we are looking at the `factory` function from the example above we could access the `node` like the following: +With the `inputs` not only the `output` of an extensions item is passed to the extension, but also the `node`. It is discouraged to consume and/or modify the `node` here. If we are looking at the `factory` function from the example above we could access the `node` like the following: ```tsx // ... @@ -251,22 +251,51 @@ With the `inputs` not only the `output` is passed to the extension, but also the ## Extension Configuration - +For further information on how to write documentation please follow the architecture section on this (TODO: Link). ## Extension Creators +With creating an extension by using `createExtension(...)` you have the advantage that the extension can be anything in your Backstage application. We realised that this comes with the trade-off of having to repeat boilerplate code for similar building blocks. Here extension creators come into play for covering common building blocks in Backstage like pages using `createPageExtension`, themes using the `createThemeExtension` or items for the navigation using `createNavItemExtension`. + + +``` + +``` From fa9e6ac1244744ba5ac9b2ad4be117765d870061 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Tue, 19 Dec 2023 02:04:36 +0100 Subject: [PATCH 05/10] Draft Extension doc Signed-off-by: Philipp Hugenroth --- .../architecture/03-extensions.md | 79 +++++++++++++++---- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index 63ea56f884..ce40c5310b 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -296,32 +296,81 @@ For further information on how to write documentation please follow the architec With creating an extension by using `createExtension(...)` you have the advantage that the extension can be anything in your Backstage application. We realised that this comes with the trade-off of having to repeat boilerplate code for similar building blocks. Here extension creators come into play for covering common building blocks in Backstage like pages using `createPageExtension`, themes using the `createThemeExtension` or items for the navigation using `createNavItemExtension`. +If we follow the example from above all items of the navigation have similarites, like they all want to be attached to the same extension with the same input as well as rendering the same navigation item component. Therefor `createExtension` can be abstracted for this use case to `createNavItemExtension` and if we add the extension to the app it will end up in the right place & looks like we expect a navigation item to look. + +```tsx +export const HomeNavIcon = createNavItemExtension({ + routeRef: routeRefForTheHomePage, + title: 'Home', + icon: HomeIcon, +}); +``` + +### Extension Kind + +With the example `HomeNavIcon` will end up on the extension input `items` of the extensions with the id `app/nav`. It raises the question what the `id` of the `HomeNavIcon` itself is. The extension creator for the navigation item has a defined `kind`, which by convention matches the own name. So in this example `createNavItemExtension` sets the kind to `nav-item`. + +The `id` of the extension is then build out of `namespace`, `name` & `kind` like the following - where `namespace` & `name` are optional properties that can be passed to the extension creator: + +``` +id: kind:namespace/name +``` + +For more information on naming of extension refer to the naming patterns documentation (TODO: Link) + +### Extension Creators in libraries + +Extension creators should be exported from library packages (e.g. `*-react`, `*-common`) rather than plugin packages. + ## Extension Boundary - - -``` +Similar to plugins the `ErrorBoundary` for extension allows to pass in a fallback component in case there is an uncatched error inside of the component. With this the error can be isolated & it would prevent the rest of the plugin to crash. +### Analytics + +Analytics information are provided through the `AnalyticsContext`, which will give `extensionId` & `pluginId` as context to analytics event fired inside of the extension. Additionally `RouteTracker` will capture an analytics event for routable extension to inform which extension metadata gets associated with a navigation event when the route navigated to is a gathered mountpoint. + +The `ExtensionBoundary` can be used like the following in an extension creator: + +```tsx +export function createSomeExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options): ExtensionDefinition { + return createExtension({ + // ... + factory({ config, inputs, node }) { + const ExtensionComponent = lazy(() => + options + .loader({ config, inputs }) + .then(element => ({ default: () => element })), + ); + + return { + path: config.path, + routeRef: options.routeRef, + element: ( + + + + ), + }; + }, + }); +} ``` From 75dcd41d1a42802c3a0198af659f41e5e0c7cf1b Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 22 Dec 2023 10:52:09 +0100 Subject: [PATCH 06/10] Apply suggestions from code review Co-authored-by: Patrik Oldsberg Signed-off-by: Vincenzo Scamporlino --- docs/frontend-system/architecture/03-extensions.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index ce40c5310b..9898668232 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -135,7 +135,9 @@ const extension = createExtension({ ### Core Extension Data -We provide default `coreExtensionData`, which provides commonly used `ExtensionDataRef` - e.g. for `React.JSX.Element`, `RouteRef` or `AppTheme`. They can be used when creating your own extension. For example, the React Element extension data that we defined above is already provided as `coreExtensionData.reactElement`. For a full list and explanations of all types of core extension data, see the [core extension data reference](#TODO). +We provide default `coreExtensionData`, which provides commonly used `ExtensionDataRef`s - e.g. for `React.JSX.Element` and `RouteRef`. They can be used when creating your own extension. For example, the React Element extension data that we defined above is already provided as `coreExtensionData.reactElement`. + + ### Optional Extension Data @@ -182,7 +184,7 @@ const navigationExtension = createExtension({ }); ``` -The input (see [1] above) is an object with the properties `$$type`, `extensionData` and `config`, where `createExtensionInput` helps creating this object by taking the `extensionData` as the first parameter, and the `config` as the second parameter making sure the `$$type` is set correctly. The extension data ensures the input is of the type & format we expect. With `config` the format & type can be defined even more precisely. The extension input can be converted from an array of inputs to just a single input by setting `singleton` to `true`. If the extension input is optional, `optional: true` converts the type to be optional as well. +The input (see [1] above) is an object that we create using `createExtensionInput`. The first argument is the set of extension data that we accept via this input, and works just like the `output` option. The second argument is optional, and it allows us to put constraints on the extensions that are attached to our input. If the `singleton: true` option is set, only a single extension can attached at a time, and unless the `optional: true` option is set it will also be required that there is exactly on attached extension. So how can we now attach the output to the parent extension's input? If we think about a navigation component, like the Sidebar in Backstage, there might be plugins that want to attach a link to their plugin to this navigation component. In this case the plugin only needs to know the extension `id` and the name of the extension `input` to attach the extension `output` returned by the `factory` to the specified extension: @@ -229,7 +231,7 @@ const navigationExtension = createExtension({ In this case the extension input `items` is an array, where each individual item is an extension that attached itself to the extension inputs of this `id`. -With the `inputs` not only the `output` of an extensions item is passed to the extension, but also the `node`. It is discouraged to consume and/or modify the `node` here. If we are looking at the `factory` function from the example above we could access the `node` like the following: +With the `inputs` not only the `output` of an extensions item is passed to the extension, but also the `node`. However, it is discouraged to consume the `node` here unless needed. If we are looking at the `factory` function from the example above we could access the `node` like the following: ```tsx // ... @@ -251,7 +253,7 @@ With the `inputs` not only the `output` of an extensions item is passed to the e ## Extension Configuration -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 independet of the plugin & initiated several times. Therefor we created a possibility to configure each extension indiviually through config. The config schema is created using the library `zod` (TODO: Link), which enables a stronger type checking on top of TypeScript. 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: +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 independet of the plugin & initiated several times. Therefor we created a possibility to configure each extension indiviually 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 const navigationExtension = createExtension({ @@ -316,7 +318,7 @@ The `id` of the extension is then build out of `namespace`, `name` & `kind` like id: kind:namespace/name ``` -For more information on naming of extension refer to the naming patterns documentation (TODO: Link) +For more information on naming of extension refer to the [naming patterns documentation](./08-naming-patterns.md). ### Extension Creators in libraries From 27a90e515a33fa924faac9fee9eb9c60f68b412c Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 22 Dec 2023 22:10:29 +0100 Subject: [PATCH 07/10] docs: return undefined sometimes Signed-off-by: Vincenzo Scamporlino --- docs/frontend-system/architecture/03-extensions.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index 9898668232..ad3364e2f0 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -135,7 +135,7 @@ const extension = createExtension({ ### Core Extension Data -We provide default `coreExtensionData`, which provides commonly used `ExtensionDataRef`s - e.g. for `React.JSX.Element` and `RouteRef`. They can be used when creating your own extension. For example, the React Element extension data that we defined above is already provided as `coreExtensionData.reactElement`. +We provide default `coreExtensionData`, which provides commonly used `ExtensionDataRef`s - e.g. for `React.JSX.Element` and `RouteRef`. They can be used when creating your own extension. For example, the React Element extension data that we defined above is already provided as `coreExtensionData.reactElement`. @@ -151,7 +151,8 @@ const extension = createExtension({ }, factory() { return { - element: , + element: + Math.random() < 0.5 ? : undefined, }; }, }); From 8e434302cdcc3245135900c4bf34204866386295 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 22 Dec 2023 22:10:58 +0100 Subject: [PATCH 08/10] docs: remove link Signed-off-by: Vincenzo Scamporlino --- docs/frontend-system/architecture/03-extensions.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index ad3364e2f0..817426d19d 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -293,8 +293,6 @@ app: title: 'Backstage' ``` -For further information on how to write documentation please follow the architecture section on this (TODO: Link). - ## Extension Creators With creating an extension by using `createExtension(...)` you have the advantage that the extension can be anything in your Backstage application. We realised that this comes with the trade-off of having to repeat boilerplate code for similar building blocks. Here extension creators come into play for covering common building blocks in Backstage like pages using `createPageExtension`, themes using the `createThemeExtension` or items for the navigation using `createNavItemExtension`. From 63f3a4c5207338607cfa827bdb8eb7fc86ca4e45 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 22 Dec 2023 22:17:02 +0100 Subject: [PATCH 09/10] docs: clarify where extension creators should live Signed-off-by: Vincenzo Scamporlino --- docs/frontend-system/architecture/03-extensions.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index 817426d19d..c1cff4d4f2 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -321,14 +321,9 @@ For more information on naming of extension refer to the [naming patterns docume ### Extension Creators in libraries -Extension creators should be exported from library packages (e.g. `*-react`, `*-common`) rather than plugin packages. +Extension creators should be exported from frontend library packages (e.g. `*-react`) rather than plugin packages. - +If an extension is only for in-house tweaks, it's okay to put it in the plugin package. But if you want other open source plugins to use it, or you already have a `-react` package, always put extension creators in the `-react` package. ## Extension Boundary From 04d7f46df88fdaf66d5ee8b33fcdfa213a6fc5f8 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 22 Dec 2023 22:19:04 +0100 Subject: [PATCH 10/10] docs: fix vale checks Signed-off-by: Vincenzo Scamporlino --- docs/frontend-system/architecture/03-extensions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index c1cff4d4f2..b4808cf08f 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -254,7 +254,7 @@ With the `inputs` not only the `output` of an extensions item is passed to the e ## Extension Configuration -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 independet of the plugin & initiated several times. Therefor we created a possibility to configure each extension indiviually 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: +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. Therefor 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 const navigationExtension = createExtension({ @@ -297,7 +297,7 @@ app: With creating an extension by using `createExtension(...)` you have the advantage that the extension can be anything in your Backstage application. We realised that this comes with the trade-off of having to repeat boilerplate code for similar building blocks. Here extension creators come into play for covering common building blocks in Backstage like pages using `createPageExtension`, themes using the `createThemeExtension` or items for the navigation using `createNavItemExtension`. -If we follow the example from above all items of the navigation have similarites, like they all want to be attached to the same extension with the same input as well as rendering the same navigation item component. Therefor `createExtension` can be abstracted for this use case to `createNavItemExtension` and if we add the extension to the app it will end up in the right place & looks like we expect a navigation item to look. +If we follow the example from above all items of the navigation have similarities, like they all want to be attached to the same extension with the same input as well as rendering the same navigation item component. Therefor `createExtension` can be abstracted for this use case to `createNavItemExtension` and if we add the extension to the app it will end up in the right place & looks like we expect a navigation item to look. ```tsx export const HomeNavIcon = createNavItemExtension({ @@ -335,11 +335,11 @@ All React elements rendered by extension creators should be wrapped in the exten ### Error Boundary -Similar to plugins the `ErrorBoundary` for extension allows to pass in a fallback component in case there is an uncatched error inside of the component. With this the error can be isolated & it would prevent the rest of the plugin to crash. +Similar to plugins the `ErrorBoundary` for extension allows to pass in a fallback component in case there is an uncaught error inside of the component. With this the error can be isolated & it would prevent the rest of the plugin to crash. ### Analytics -Analytics information are provided through the `AnalyticsContext`, which will give `extensionId` & `pluginId` as context to analytics event fired inside of the extension. Additionally `RouteTracker` will capture an analytics event for routable extension to inform which extension metadata gets associated with a navigation event when the route navigated to is a gathered mountpoint. +Analytics information are provided through the `AnalyticsContext`, which will give `extensionId` & `pluginId` as context to analytics event fired inside of the extension. Additionally `RouteTracker` will capture an analytics event for routable extension to inform which extension metadata gets associated with a navigation event when the route navigated to is a gathered `mountPoint`. The `ExtensionBoundary` can be used like the following in an extension creator: