diff --git a/plugins/techdocs-addons/README.md b/plugins/techdocs-addons/README.md
index b97a8d583d..432aa757e4 100644
--- a/plugins/techdocs-addons/README.md
+++ b/plugins/techdocs-addons/README.md
@@ -1,5 +1,61 @@
# @backstage/plugin-techdocs-addons
-Package encapsulating the TechDocs Addons framework.
+Package encapsulating the TechDocs Addon framework.
-todo(backstage/techdocs-core): Fill in with real documentation!
+## What is an addon?
+
+An addon is a isolated piece of functionality that one can use to augment the
+TechDocs experience at render-time. For example: an issue counter showing the
+number of issues reported on the documentation, or the top contributors to the
+documentation.
+
+## Create a new addon
+
+To create a new addon, you can use the `createTechDocsAddon` factory exported
+from this plugin. Normally, addons are provided by Backstage plugins, which can
+then be composed within a Backstage app.
+
+When you create a new Addon, it requires three things.
+
+1. A `name` for debugging and analytics purposes)
+2. A `location`, indicating where/how the addon will be rendered
+3. A `component`, encapsulating the addon's logic and functionality
+
+```tsx
+import {
+ createTechDocsAddon,
+ TechDocsAddonLocations,
+} from '@backstage/plugin-techdocs-addons';
+import { StackOverflowSecondarySidebarAddon } from './components';
+
+export const StackOverflowSecondarySidebar = yourBackstagePlugin.provide(
+ createTechDocsAddon({
+ name: 'StackOverflowSecondarySidebar',
+ type: TechDocsAddonLocations.SECONDARY_SIDEBAR,
+ component: StackOverflowSecondarySidebarAddon,
+ }),
+);
+```
+
+## Compose your app with addons
+
+To configure which addons will augment the TechDocs experience in your
+Backstage app, you need two things:
+
+- The `TechDocsAddons` component, which is responsible for registering the
+ addons.
+- A list of the addons themselves, as exported by their respective plugins.
+
+```tsx
+import {
+ TechDocsAddons,
+ TechDocsReaderPage,
+} from '@backstage/plugin-techdocs-addons';
+import { StackOverflowSecondarySidebar } from '@backstage/plugin-soe';
+
+}>
+
+
+
+;
+```
diff --git a/plugins/techdocs-addons/api-report.md b/plugins/techdocs-addons/api-report.md
index e14ea6112c..f3b3d5fe69 100644
--- a/plugins/techdocs-addons/api-report.md
+++ b/plugins/techdocs-addons/api-report.md
@@ -4,6 +4,13 @@
```ts
import { ComponentType } from 'react';
+import { Extension } from '@backstage/core-plugin-api';
+import { default as React_2 } from 'react';
+
+// @public
+export function createTechDocsAddon(
+ options: TechDocsAddonOptions,
+): Extension>;
// @public
export enum TechDocsAddonLocations {
@@ -21,4 +28,7 @@ export type TechDocsAddonOptions = {
location: TechDocsAddonLocations;
component: ComponentType;
};
+
+// @public
+export const TechDocsAddons: React_2.ComponentType;
```
diff --git a/plugins/techdocs-addons/package.json b/plugins/techdocs-addons/package.json
index 4df0488689..79da76e32a 100644
--- a/plugins/techdocs-addons/package.json
+++ b/plugins/techdocs-addons/package.json
@@ -21,7 +21,10 @@
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
- "dependencies": {},
+ "dependencies": {
+ "@backstage/core-plugin-api": "^0.8.0",
+ "react-router-dom": "6.0.0-beta.0"
+ },
"peerDependencies": {
"@types/react": "^16.13.1 || ^17.0.0",
"react": "^16.13.1 || ^17.0.0"
diff --git a/plugins/techdocs-addons/src/addons.tsx b/plugins/techdocs-addons/src/addons.tsx
new file mode 100644
index 0000000000..36b5d767ba
--- /dev/null
+++ b/plugins/techdocs-addons/src/addons.tsx
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ attachComponentData,
+ createReactExtension,
+ ElementCollection,
+ Extension,
+ useElementFilter,
+} from '@backstage/core-plugin-api';
+import React, { ComponentType, useCallback } from 'react';
+import { useOutlet } from 'react-router-dom';
+
+import { TechDocsAddonLocations, TechDocsAddonOptions } from './types';
+
+export const TECHDOCS_ADDONS_KEY = 'techdocs.addons.addon.v1';
+export const TECHDOCS_ADDONS_WRAPPER_KEY = 'techdocs.addons.wrapper.v1';
+
+/**
+ * TechDocs Addon registry.
+ * @public
+ */
+export const TechDocsAddons: React.ComponentType = () => null;
+
+attachComponentData(TechDocsAddons, TECHDOCS_ADDONS_WRAPPER_KEY, true);
+
+const getDataKeyByName = (name: string) => {
+ return `${TECHDOCS_ADDONS_KEY}.${name.toLocaleLowerCase('en-US')}`;
+};
+
+/**
+ * Create a TechDocs addon.
+ * @public
+ */
+export function createTechDocsAddon(
+ options: TechDocsAddonOptions,
+): Extension> {
+ const { name, component: TechDocsAddon } = options;
+ return createReactExtension({
+ name,
+ component: {
+ sync: (props: TComponentProps) => ,
+ },
+ data: {
+ [TECHDOCS_ADDONS_KEY]: options,
+ [getDataKeyByName(name)]: true,
+ },
+ });
+}
+
+const getTechDocsAddonByName = (collection: ElementCollection, key: string) => {
+ return collection.selectByComponentData({ key }).getElements()[0];
+};
+
+const getAllTechDocsAddons = (collection: ElementCollection) => {
+ return collection
+ .selectByComponentData({
+ key: TECHDOCS_ADDONS_WRAPPER_KEY,
+ })
+ .selectByComponentData({
+ key: TECHDOCS_ADDONS_KEY,
+ });
+};
+
+const getAllTechDocsAddonsData = (collection: ElementCollection) => {
+ return collection
+ .selectByComponentData({
+ key: TECHDOCS_ADDONS_WRAPPER_KEY,
+ })
+ .findComponentData({
+ key: TECHDOCS_ADDONS_KEY,
+ });
+};
+
+export const useTechDocsAddons = () => {
+ const node = useOutlet();
+
+ const collection = useElementFilter(node, getAllTechDocsAddons);
+ const options = useElementFilter(node, getAllTechDocsAddonsData);
+
+ const findAddonByData = useCallback(
+ (data: TechDocsAddonOptions | undefined) => {
+ if (!collection || !data) return null;
+ const nameKey = getDataKeyByName(data.name);
+ return getTechDocsAddonByName(collection, nameKey) ?? null;
+ },
+ [collection],
+ );
+
+ const renderComponentWithName = useCallback(
+ (name: string) => {
+ const data = options.find(option => option.name === name);
+ return data ? findAddonByData(data) : null;
+ },
+ [options, findAddonByData],
+ );
+
+ const renderComponentsWithLocation = useCallback(
+ (location: TechDocsAddonLocations) => {
+ const data = options.filter(option => option.location === location);
+ return data.length ? data.map(findAddonByData) : null;
+ },
+ [options, findAddonByData],
+ );
+
+ return { renderComponentWithName, renderComponentsWithLocation };
+};
diff --git a/plugins/techdocs-addons/src/index.ts b/plugins/techdocs-addons/src/index.ts
index 715da10e20..ba9f2a7266 100644
--- a/plugins/techdocs-addons/src/index.ts
+++ b/plugins/techdocs-addons/src/index.ts
@@ -20,4 +20,5 @@
* @packageDocumentation
*/
+export { createTechDocsAddon, TechDocsAddons } from './addons';
export type { TechDocsAddonLocations, TechDocsAddonOptions } from './types';