diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md
index 39e36cbeb2..6d86415817 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,68 +85,288 @@ 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 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
+
+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`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
+
+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.optional(),
+ },
+ factory() {
+ return {
+ element:
+ Math.random() < 0.5 ? : undefined,
+ };
+ },
+});
+```
## Extension Inputs
-
+```tsx
+ // ...
+ factory({ inputs }) {
+ return {
+ element: (
+
+ ),
+ };
+ },
+```
## Extension Configuration
-
+```yaml
+app:
+ # ...
+ extensions:
+ # ...
+ - app/nav:
+ config:
+ title: 'Backstage'
+```
## Extension Creators
-
+```
+id: kind:namespace/name
+```
+
+For more information on naming of extension refer to the [naming patterns documentation](./08-naming-patterns.md).
+
+### Extension Creators in libraries
+
+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
-
+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`.
+
+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: (
+
+
+
+ ),
+ };
+ },
+ });
+}
+```