diff --git a/gatsby-config.js b/gatsby-config.js
index f08c7e23..5f13ab6a 100644
--- a/gatsby-config.js
+++ b/gatsby-config.js
@@ -37,6 +37,10 @@ module.exports = {
title: "AEM Content Fragments Editor",
path: "/services/aem-cf-editor/"
},
+ {
+ title: "Universal Editor",
+ path: "/services/aem-universal-editor/"
+ },
{
title: "Adobe Commerce Admin",
path: "https://developer.adobe.com/commerce/extensibility/admin-ui-sdk/"
@@ -214,7 +218,25 @@ module.exports = {
path: "/extension-manager/extension-developed-by-adobe/content-fragments-workflows"
}
]
- }
+ },
+ {
+ title: "Extension Points",
+ path: "/services/aem-universal-editor/api/",
+ pages: [
+ {
+ title: "Common Concepts",
+ path: "/services/aem-universal-editor/api/commons/"
+ },
+ {
+ title: "Header Menu",
+ path: "/services/aem-universal-editor/api/header-menu/"
+ },
+ {
+ title: "Modal Dialogs",
+ path: "/services/aem-universal-editor/api/modal/"
+ },
+ ]
+ },
]
},
plugins: [
diff --git a/src/pages/index.md b/src/pages/index.md
index 5ca35162..7e8f8fe0 100644
--- a/src/pages/index.md
+++ b/src/pages/index.md
@@ -96,6 +96,10 @@ Start building extensions for AEM Content Fragments console
Start building extensions for AEM Content Fragments editor
+[Universal Editor](services/aem-universal-editor/)
+
+Start building extensions for the Universal Editor
+
[Adobe Commerce Admin](https://developer.adobe.com/commerce/extensibility/admin-ui-sdk/)
diff --git a/src/pages/services/aem-universal-editor/api/commons/index.md b/src/pages/services/aem-universal-editor/api/commons/index.md
new file mode 100644
index 00000000..afdc449e
--- /dev/null
+++ b/src/pages/services/aem-universal-editor/api/commons/index.md
@@ -0,0 +1,180 @@
+---
+title: AEM Universal Editor Extensibility
+description: Learn how to customize AEM Universal Editor
+contributors:
+ - https://github.com/AdobeDocs/uix
+---
+
+# Common Concepts in Creating Extensions
+
+Learn about common approaches and methods that can be used in any extension for the universal editor.
+
+## Extension Registration
+
+Interaction between UI Extension and Universal Editor starts with the initialization process that includes extension's
+capabilities registration so Universal Editor knows when to invoke the extension. Registration is done by `register`
+method provided by `@adobe/uix-guest` library. This asynchronous method takes single object that describes extension
+and returns object representing connection to the Universal Editor.
+
+Method `register` should be invoked after extension initialization page is loaded.
+
+Extension registration data must include:
+
+- `id` - string with random extension identifier. This identifier useful for debugging of interaction between Universal
+Editor and extension and needed if extension provides custom UI.
+- `methods` - objects with extension code exposed to the Universal Editor console. All methods are grouped into
+namespaces that represents extension points provided by the Universal Editor.
+Currently, the following **namespaces** are available:
+- _headerMenu_, that allows to add buttons to the header of the Universal Editor;
+- _rightPanel_, that allows to add custom content under the rails to the right panel of the Universal Editor;
+- _canvas_, that allows to add custom renderer for data types
+
+```js
+import { register } from "@adobe/uix-guest";
+
+// ...
+
+ const guestConnection = await register({
+ id: "extension-id",
+ methods: {
+ headerMenu: {
+ getButtons() {
+ // ..
+ }
+ },
+ rightPanel: {
+ getPanels() {
+ // ..
+ }
+ },
+ canvas: {
+ getRenderers() {
+ // ..
+ }
+ }
+ }
+ });
+// ...
+```
+## Extension UI
+
+For use-cases when UI Extension provides any data handling or send data to remote service `register` is the only method
+that is expected to be invoked. If UI Extension implements own UI it should be provided as separate page.
+If this UI requires data from Universal Editor or need to invoke any logic it should establish connection with `attach`
+method.
+
+```js
+import { attach } from "@adobe/uix-guest";
+
+const guestConnection = await attach({ id: "extension-id" });
+```
+
+## Connection Object
+
+Both `register` and `attach` function of `@adobe/uix-guest` returns same connection object that has `host` property and
+expose API of Universal Editor exposed for UI Extensions.
+
+
+### Shared Context
+
+The shared context is a dataset that Universal Editor shares with UI Extensions. It's used to understand the context of
+the user who is running Universal Editor. You can access the shared context through the `sharedContext` property of the
+connection object.
+
+```js
+import { attach } from "@adobe/uix-guest";
+
+ useEffect(() => {
+ (async () => {
+ const guestConnection = await attach({ id: extensionId });
+
+ setGuestConnection(guestConnection);
+ })();
+ }, []);
+
+ ...
+
+const context = guestConnection.sharedContext;
+const hostAppLocale = context.get("locale");
+```
+
+Available data in the shared context:
+
+| Key | Type | Description |
+|--------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| locale | `string` | Locale string for globalization of current user |
+| theme | `string` | Available [options](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/overview/aem-cloud-service-on-unified-shell#changing-to-dark-theme): "light" or "dark". The theme selected by current user |
+| orgId | `string` | IMS org ID |
+| token | `string` | User token |
+| authScheme | `string` | Auth schema that should be used during communication with host application |
+
+
+### Editor State
+
+The editor state is a dataset that Universal Editor shares with UI Extensions. It's used to understand the current state
+of the editor. You can access the editor state through the `editorState` property of the host object.
+
+Below is an example of how you can access editor state properties, e.g., connections.
+
+```js
+import { attach } from "@adobe/uix-guest";
+
+...
+
+ useEffect(() => {
+ (async () => {
+ const guestConnection = await attach({ id: extensionId });
+
+ setGuestConnection(guestConnection);
+ })();
+ }, []);
+ ...
+
+const editorState = await guestConnection.host.editorState.get();
+const {connections} = editorState;
+
+...
+
+```
+Available data in the editor state:
+
+| Key | Type | Sample | Description |
+|-----|---------------------------|-----------------------------------------------------------------------|-------------|
+| connections | `obj` | { "aemconnection": "aem:%auth_instance_url%" } | [Connections](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/getting-started#connections) which are used in the app are stored as `` tags in the page’s `
`. |
+| selected | `obj` | {fcb38012-c4c7-51a8-896c-79e76kjk: true} | Element that is currently edited. |
+| editables | `[objects]` | Array[{id: '33661..", type: 'reference', resource: "urn:..., ..}, {}] | List of elements that might be edited. The editable element object includes [proper metadata](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/attributes-types). |
+| location | `string` | "%locationString%" | The current page location |
+| customTokens | `obj` | {"aemconnection":""} | Custom tokens available for connections |
+
+### Working with Events
+
+The Universal Editor offers a list of events that extensions can subscribe to in order to respond to changes in content or the user interface.
+Refer to this [document](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/events) for the available event list.
+
+For instance, here's an example of how to subscribe to the "aue:ui-select" event:
+
+```js
+
+ useEffect(() => {
+ (async () => {
+ const guestConnection = await attach({id: extensionId});
+ ...
+ await guestConnection.host.remoteApp.addEventListener('aue:ui-select', console.log('event recieved!'));
+ ...
+ })();
+ }, []);
+```
+
+If your business logic requires sending an event to the Universal Editor, you can use the `dispatchEvent` method.
+Here's an example of how to dispatch the "aue:ui-select" event:
+
+```js
+ useEffect(() => {
+ (async () => {
+ const guestConnection = await attach({id: extensionId});
+ ...
+ await guestConnection.host.remoteApp.dispatchEvent('aue:ui-select', {data: 'some data'});
+ ...
+ })();
+ }, []);
+```
diff --git a/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item-with-submenu.png b/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item-with-submenu.png
new file mode 100644
index 00000000..c01143d1
Binary files /dev/null and b/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item-with-submenu.png differ
diff --git a/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item.png b/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item.png
new file mode 100644
index 00000000..3a4ce3ec
Binary files /dev/null and b/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item.png differ
diff --git a/src/pages/services/aem-universal-editor/api/header-menu/index.md b/src/pages/services/aem-universal-editor/api/header-menu/index.md
new file mode 100644
index 00000000..fe14c4da
--- /dev/null
+++ b/src/pages/services/aem-universal-editor/api/header-menu/index.md
@@ -0,0 +1,115 @@
+---
+title: AEM Universal Editor Extensibility
+description: Learn how to customize AEM Universal Editor
+contributors:
+ - https://github.com/AdobeDocs/uix
+---
+
+# Header Menu
+
+Header menu can be customized via methods defined in `headerMenu` namespace.
+
+You have the ability to:
+
+- create multiple buttons from single extension;
+- implement drop-down menu buttons;
+- use different [variations](https://spectrum.adobe.com/page/button/#Options) of buttons from React Spectrum;
+- use any [icon](https://react-spectrum.adobe.com/react-spectrum/workflow-icons.html#available-icons) from React Spectrum;
+
+## Custom button with callback
+
+```js
+import { register } from "@adobe/uix-guest";
+
+// ...
+
+const guestConnection = await register({
+ id: "my.company.extension-with-header-menu-button",
+ methods: {
+ headerMenu: {
+ getButtons() {
+ return [
+ {
+ id: "my.company.export-button",
+ label: "Export",
+ icon: 'Export',
+ onClick: () => {
+ console.log('Export button has been pressed.');
+ },
+ },
+ ];
+ },
+ },
+ },
+});
+```
+
+The `onClick` callback is invoked when a user clicks on the button. It does not receive any arguments.
+
+![Header menu item](./header-menu-item.png)
+
+## Custom button with sub menu
+
+```js
+import { register } from "@adobe/uix-guest";
+
+// ...
+
+const guestConnection = await register({
+ id: "my.company.extension-with-header-menu-button",
+ methods: {
+ headerMenu: {
+ async getButtons() {
+ return [
+ {
+ id: "my.company.export-button",
+ label: "Export",
+ icon: 'Export',
+ subItems: [
+ {
+ id: 'xml',
+ label: 'XML',
+ onClick: async () => {
+ // ...
+ },
+ },
+ {
+ id: 'csv',
+ label: 'CSV',
+ onClick: async () => {
+ // ...
+ },
+ },
+ ],
+ },
+ ];
+ },
+ },
+ },
+});
+```
+
+![Header menu item with submenu](./header-menu-item-with-submenu.png)
+
+## API Reference
+
+### Button API
+
+| Field | Type | Required | Description |
+|----------|-----------------------------------------------------------------------------| ------ |-------------------------------------------------------------------------------------------------------------------------------|
+| id | `string` | ✔️ | **Must be unique** across all extensions. Consider adding a vendor prefix to this field |
+| label | `string` | ✔️ | Button label that will be visible on UI |
+| icon | `string` | | Name of a [React-Spectrum workflow icon](https://react-spectrum.adobe.com/react-spectrum/workflow-icons.html#available-icons) |
+| variant | `cta` `primary` `secondary` `negative` `action` | | The [visual style](https://spectrum.adobe.com/page/button/#Options) of the button |
+| subItems | `array` | | A list with sub menu items. |
+| onClick | `callback(): void` | ✔️ | A callback for a button `onClick` event |
+
+### Sub menu item API
+
+| Field | Type | Required | Description |
+|----------|-------------------------------------------------------------------------| ------ |--------------------------------------------------------------------------------------------------------------|
+| id | `string` | ✔️ | **Must be unique** across the current button sub menu |
+| label | `string` | ✔️ | Button label that will be visible on UI |
+| icon | `string` | | Name of a [React-Spectrum workflow icon](https://react-spectrum.adobe.com/react-spectrum/workflow-icons.html#available-icons) |
+| onClick | `callback(): void` | ✔️ | A callback for a button `onClick` event |
+
diff --git a/src/pages/services/aem-universal-editor/api/index.md b/src/pages/services/aem-universal-editor/api/index.md
new file mode 100644
index 00000000..7c202a7e
--- /dev/null
+++ b/src/pages/services/aem-universal-editor/api/index.md
@@ -0,0 +1,32 @@
+---
+title: AEM Universal Editor Extensibility
+description: Learn how to customize AEM Universal Editor
+contributors:
+ - https://github.com/AdobeDocs/uix
+---
+
+# The Universal Editor Extension Points
+
+This section covers the utilization of existing extension points, extension registration, and common methods that can be used in any application that leverages extension points for service customization.
+
+
+
+[Common Concepts in Creating Extensions](commons)
+
+Learn about common concepts, extension registration, and methods that can be used in any extension
+
+
+
+[Header Menu](header-menu)
+
+Explore the ways to extend and customize Header Menu
+
+
+
+[Modal Dialogs](modal)
+
+Learn about modal host API methods that can be used in any extension
+
+[Custom data types renderers for properties rail](item-types-renderers)
+
+Learn how to customize the user interface of a data type field within the properties rail of Universal Editor.
\ No newline at end of file
diff --git a/src/pages/services/aem-universal-editor/api/item-types-renderers/custom-renderer-field.png b/src/pages/services/aem-universal-editor/api/item-types-renderers/custom-renderer-field.png
new file mode 100644
index 00000000..277a13d5
Binary files /dev/null and b/src/pages/services/aem-universal-editor/api/item-types-renderers/custom-renderer-field.png differ
diff --git a/src/pages/services/aem-universal-editor/api/item-types-renderers/index.md b/src/pages/services/aem-universal-editor/api/item-types-renderers/index.md
new file mode 100644
index 00000000..16b6270d
--- /dev/null
+++ b/src/pages/services/aem-universal-editor/api/item-types-renderers/index.md
@@ -0,0 +1,180 @@
+---
+title: Custom item types renderers for properties rail - Universal Editor Extensibility
+description: Discover how to personalize the user interface of a data type field within the properties rail of Universal Editor.
+contributors:
+ - https://github.com/AdobeDocs/uix
+---
+
+# Custom element rendering for properties rail by data-type
+
+This feature allows third-party developer to build custom input UI for specific data types in the Universal Editor.
+The custom UI is rendered in an iframe and replaces the standard UI for the field in the [properties rail](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/sites/authoring/universal-editor/authoring#properties-rail).
+
+![](./custom-renderer-field.png)
+
+## Define override rules
+
+An UIX extension can define a custom renderer to replace the standard UI with an iframe, which then renders the custom UI provided by the extension.
+Check this document to learn about available [data types for universal editor](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/attributes-types#item-types).
+```js
+function ExtensionRegistration() {
+ const init = async () => {
+ const guestConnection = await register({
+ id: extensionId,
+ methods: {
+ canvas: {
+ getRenderers() {
+ return [
+ {
+ dataType: "text",
+ url: '/#/renderer/1'
+ },
+ {
+ dataType: "custom-type",
+ url: '/#/renderer/2',
+ },
+ ];
+ },
+ },
+ },
+ });
+ };
+ init().catch(console.error);
+
+ return IFrame for integration with Host (AEM)...;
+}
+```
+
+## Implement custom rendering logic
+
+1. Define the router in the extension application to handle the custom rendering of the field.
+```js
+function App() {
+ return (
+
+
+
+ } />
+ }
+ />
+ }
+ />
+
+
+
+ )
+
+ // Methods
+
+ // error handler on UI rendering failure
+ function onError(e, componentStack) {}
+
+ // component to show if UI fails rendering
+ function fallbackComponent({ componentStack, error }) {
+ ...
+}
+
+export default App
+```
+
+2. Implement custom field renderer
+
+```js
+export default () => {
+ const [isLoaded, setIsLoaded] = useState(true);
+ const [connection, setConnection] = useState();
+ const [model, setModel] = useState();
+ const [value, setValue] = useState();
+ const [error, setError] = useState();
+ const [validationState, setValidationState] = useState();
+
+ const { rendererId } = useParams();
+ if (!rendererId) {
+ console.error('Renderer id parameter is missed');
+ return;
+ }
+
+ useEffect(() => {
+ const init = async () => {
+ // connect to the host
+ const connection = await attach({ id: extensionId });
+ setConnection(connection);
+ // get model
+ setModel(await connection.host.field.getModel());
+ // get field value
+ setValue(await connection.host.field.getValue());
+ // get field error
+ setError(await connection.host.field.getError());
+ // get field validation state
+ setValidationState(await connection.host.field.getValidationState());
+ setIsLoaded(false);
+ };
+ init().catch((e) =>
+ console.log("Extension got the error during initialization:", e)
+ );
+ }, []);
+
+ const onChangeHandler = (v) => {
+ console.log("onChange on extension side", v);
+ connection.host.field.onChange(v);
+ };
+
+ return (
+
+ {!isLoaded ? (
+ <>
+ Content generated by the extension Renderer#{rendererId}
+
+
+
+ >
+ ) : (
+
+
+
+ )}
+
+ );
+};
+
+```
+
+## Extension Registration Reference
+
+| Field | Type | Required | Description |
+|-------------|-----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| url | `string` | ✔️ | URL of the page to load in the iframe that will replace the original field. The URL must have the same origin as the extension declaring the rules for field replacement. |
+| dataType | `string` | ✔️ | Value of `data-aue-type` |
+| icon | DynamicIconVariant | | Icons to be displayed in the properties rail if data type is matched. |
+## Field Reference
+
+```js
+{
+ await connection.host.field
+}
+```
+| Methods | Description |
+|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| getModel | Returns the model of the field. |
+| getValue | Returns the value of the field. |
+| getError | Returns the error of the field. |
+| getValidationState | Returns the validation state of the field. |
+| onChange | Sets the value of the field. |
+| setHeight | Sets the height of the field. |
\ No newline at end of file
diff --git a/src/pages/services/aem-universal-editor/api/modal/index.md b/src/pages/services/aem-universal-editor/api/modal/index.md
new file mode 100644
index 00000000..695b0135
--- /dev/null
+++ b/src/pages/services/aem-universal-editor/api/modal/index.md
@@ -0,0 +1,164 @@
+---
+title: AEM Universal Editor Extensibility
+description: Learn how to customize AEM Universal Editor
+contributors:
+ - https://github.com/AdobeDocs/uix
+---
+
+# Modal dialogs
+
+Describes basic methods for using modals dialogs within an extension.
+
+The AEM Universal Editor (host instance) provides an API for showing modal dialogs with custom UI defined by an extension. These modals can be triggered by a click on the button or other events. Modal API is defined in the `modal` namespace.
+
+Content of the modal is rendered in an iframe with source defined by extension. Before showing modal you should create a page which renders Modal UI. This UI should use Adobe Spectrum UI library to provide consistent experience to the user.
+
+## An example of opening and closing a modal
+
+In order to display modal dialog extension must call `showUrl` method in `modal` namespace.
+
+```js
+import { useEffect } from "react";
+import { Text } from "@adobe/react-spectrum"
+import { extensionId } from "./Constants"
+import { register } from "@adobe/uix-guest";
+
+function ExtensionRegistration() {
+ useEffect(() => {
+ const init = async () => {
+ const guestConnection = await register({
+ id: "my.company.extension-with-modal",
+ methods: {
+ headerMenu: {
+ getButtons() {
+ return [
+ {
+ id: "example.button.actionWithSubItems",
+ label: "UIX Sub Items",
+ variant: "action",
+ icon: 'PublishCheck', // Spectrum workflow icon code from https://spectrum.adobe.com/page/icons/
+ subItems: [
+ {
+ id: 'modalDialog',
+ label: 'Modal Dialog',
+ icon: 'PublishSchedule',
+ onClick: async () => {
+ console.log('Button has been pressed.');
+ url: "/index.html#/modal", // absolute or relative path
+ guestConnection.host.modal.showUrl({
+ title: 'Modal Dialog: ',
+ url,
+ width: '900px',
+ });
+ },
+ },
+ ],
+ }
+ ]
+ }
+ }
+ }
+ });
+ }
+ init().catch(console.error)
+ }, []);
+ return IFrame for integration with Host...
+}
+
+export default ExtensionRegistration
+```
+
+Modal may be closed by `close` method
+
+```js
+import React, { useState, useEffect } from "react";
+import { attach } from "@adobe/uix-guest";
+import {
+ Provider,
+ Content,
+ defaultTheme,
+ Button
+} from "@adobe/react-spectrum";
+
+export default ModalComponent = () => {
+ const [guestConnection, setGuestConnection] = useState(null);
+
+
+ useEffect(() => {
+ (async () => {
+ const guestConnection = await attach({ id: "my.company.extension-with-modal" });
+
+ setGuestConnection(guestConnection);
+ })();
+ }, []);
+
+...
+ const onCloseHandler = () => {
+ guestConnection.host.modal.close();
+ };
+ ...
+ return (
+
+
+ ...
+
+
+
+ );
+ }
+
+
+```
+![](./modal.png)
+
+## API Reference
+
+### Modal API Request Object
+
+The `modal.showUrl` and `modalInstance.set` methods accept a `ModalRequest` object.
+
+| Property | Type | Required | Default | Description |
+|-----------|------------------------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `url` | `string` | ✔️ | | URL of the page to load in the dialog frame. The URL must have the same origin as the extension making the modal request. |
+| `title` | `string` | ✔️ | | Title of the modal to display. |
+| `height` | `string` `number` `"auto"` | | `auto` | A number of pixels, a CSS value, or the string `auto`. The `auto` keyword will grow or shrink the modal to the height of the document in the iframe every time the guest document resizes, to a minimum of 20% and a maximum of 75% of window height. In fullscreen mode, this is ignored. |
+| `width` | `string` `number` `"auto"` | | `auto` | A number of pixels, a CSS value, or the string `auto`. The `auto` keyword will grow or shrink the modal to the width of the document in the iframe every time the guest document resizes, to a minimum of 20% and a maximum of 75% of window width. In fullscreen mode, this is ignored. |
+| `fullscreen` | `boolean` | | `false` | Display the dialog as large as possible. It will overlay most of the application, leaving small borders to indicate overlay. If true, any "width" and "height" parameters will be ignored. |
+| `isDismissable` | `boolean` | | `true` | Show the dismiss button, so a user can close the modal no matter what state it is in. If an extension disables this, it must provide its own UI control which calls `modal.close()`. |
+| `loading` | `boolean` | | `false` | Preserve the progress spinner that displays before the modal contents load. When `false` or unset, the modal will show a progress spinner until the guest in the iframe is connected, and then display the frame contents. If the `modal.showUrl()` call sets `{ loading: true }`, the spinner will continue displaying after the guest has connected, until the guest calls `modal.set({ loading: false })`. A modal which needs to do data fetching or layout adjustment after connecting should set `{ loading: true }` and then dismiss it from the modal when its UI is ready. |
+
+### Modal API
+
+The modal API is available to all extensions at the host.modal property.
+
+#### `modal.showUrl(request: ModalRequest): Promise`
+
+Any guest's GuestServer may call `modal.showUrl` to open a modal dialog. The provided `ModalRequest` object must have a `url` of the page to be displayed inside the modal. Typically this is another page or route in the calling extension app.
+
+If another modal is displaying and it belongs to a different extension, it rejects.
+
+#### `modal.close(): Promise`
+
+Close the current modal. If the current modal doesn't belong to the calling extension, it rejects.
+
+### Modal Instance API
+
+This API is returned to the GuestServer and also shared with the modal that loads inside the frame. A GuestServer loaded in a modal must call `set` instead of `showUrl`.
+
+#### `modal.set(request: ModalRequest): Promise`
+
+Modify the currently displaying modal. GuestUI frames running inside the modal may call this method to change their dimensions, or to change other parameters.
+
+#### `modal.close(): Promise`
+
+Close the modal.
+
+### Resizing
+
+When `height` and/or `width` are set to `"auto"`, the modal will attempt to resize whenever the displayed page changes size. This is to make the content of the iframe behave, as much as possible, like they're part of the content flow of the host application.
+
+- ⚠️ Resizes are detected every 100ms.
+- ⚠️ Height is limited to between 20% and 75% of page height, and width is similarly limited.
+- ⚠️ **If the page sets certain kinds of relative dimensions on its body elements, such as `99vh` or `101vmin`, it can cause slow expansion or contraction of the displayed frame.** Those measurements change as the outer document resizes the inner document, which triggers a loop. Remedy this by using other units to define the outside of your app.
diff --git a/src/pages/services/aem-universal-editor/api/modal/modal.png b/src/pages/services/aem-universal-editor/api/modal/modal.png
new file mode 100644
index 00000000..ef6c8650
Binary files /dev/null and b/src/pages/services/aem-universal-editor/api/modal/modal.png differ
diff --git a/src/pages/services/aem-universal-editor/index.md b/src/pages/services/aem-universal-editor/index.md
new file mode 100644
index 00000000..83bbbed7
--- /dev/null
+++ b/src/pages/services/aem-universal-editor/index.md
@@ -0,0 +1,13 @@
+---
+title: AEM Content Fragments Editor Extensibility
+description: AEM Content Fragments Editor Extensibility
+contributors:
+ - https://github.com/AdobeDocs/uix
+---
+
+# AEM Universal Editor
+
+The [AEM Universal Editor](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/introduction) is a versatile visual editor that is part of Adobe Experience Manager Sites. It enables authors to do what-you-see-is-what-you-get (WYSIWYG) editing of any headless or headful experience. Understand how it can help content authors deliver exceptional experiences and how it offers unparalleled freedom for developers.
+
+In this section, you will find the available extension points and examples of how to utilize them.
+![universal-editor](universal-editor.png)
\ No newline at end of file
diff --git a/src/pages/services/aem-universal-editor/universal-editor.png b/src/pages/services/aem-universal-editor/universal-editor.png
new file mode 100644
index 00000000..cc6af730
Binary files /dev/null and b/src/pages/services/aem-universal-editor/universal-editor.png differ