# Overview During your development, it's important to be able to debug your code and troubleshoot any issues that may arise. Fortunately, most modern browsers come with built-in developer tools that can help you do just that. ## Introduction Check out this short video below on how to test and debug your add-on to help you get started more quickly, then read on for more details.

## Browser Developer Tools Some of the key debugging features available in the browser developer tools include: - **Console** - The console allows you to log messages and errors from your code, as well as execute JavaScript code and interact with the page or add-on. - **Debugger** - The debugger allows you to set breakpoints in your code and step through it line by line, so you can see exactly what's happening at each stage of execution. - **Network Monitor** - The network monitor allows you to monitor the network requests made by your add-on, so you can see how it's interacting with other resources and services. - **Profiler** - The profiler allows you to analyze the performance of your add-on and identify areas where it may be slow or inefficient. ### Debugging Steps To get started with debugging your add-on: Access the developer tools by right-clicking on the browser window where Adobe Express is running, and selecting **Inspect Element** or **Inspect** from the context menu. ![inspect](./img/inspect.png) Make sure you right click outside of the document area or you will not see the context menu pop-up. A good place to right-click is in the header of your add-on, where the title is. But if you're debugging because your add-on isn't running due to an issue, then you can right-click in the top frame of Adobe Express. Next, navigate to the **Sources** tab, and from there you can locate and select the JavaScript file that contains the code you want to debug. You can locate it in the filesystem list or by using the **Search** tab. If the Search tab isn't displayed, clicking the 3 vertical dots will reveal it as shown below: ![locate source](./img/find-source.png) Once you've selected your file, you can set breakpoints by clicking on the line number where you want the breakpoint to be set. This will pause the execution of your code at that breakpoint, allowing you to inspect variables and step through your code one line at a time. By leveraging these tools, you will develop a deeper understanding of how your add-on is working, be able to identify and fix bugs more quickly, and benefit from a high-performing add-on. ### Console When logging messages in your code, use the appropriate severity level that best describes the message. For example, an **Info** message might be used to provide general information about the application's state, while a **Warning** message might be used to alert developers about potential issues that could cause problems with the add-on. Similarly, an **Error** message might be used to indicate that an unexpected error has occurred, and a **Verbose** message might be used to see more descriptive information about the internal workings of the processing occurring in your add-on. Use the `console.*` methods as shown below to represent the severity level you would like to see for debugging: ```bash console.log('Info level) console.warn('Warning level') console.error('Error level) console.debug(Verbose level) ``` You can specifically filter which levels you want to view in the developer tools with the **Custom levels** drop-down as well to help you find your specific messages more quickly: ![custom levels](./img/log-levels.png) To make it easier to filter and identify relevant messages in the console, it's also a good practice to include an obvious identifier as a prefix. This identifier could be a unique string or tag that is specific to your add-on, making it easier to distinguish your messages from other messages in the console. For example: `console.log([MyAddOn] - Initialization complete);`. Then you can filter on `MyAddOn` in the devtools and easily see what is relevant to your add-on. Using appropriate severity levels and including identifiers in your console messages can greatly help improve the efficiency and effectiveness of your debugging, making it easier to identify and resolve issues. ### Printing JSON Data Another helpful console method is `.dir()`, which displays a JSON representation of an object. For example, running `console.dir(document.head)` would generate the following output: ![console.dir method](./img/dir-method.png) ## Add-on SDK Developer Tools The **Add-on Development** tools panel provides useful logging details and action buttons to allow for refreshing and clearing the data associated with your add-on, which are also useful for debugging and troubleshooting your add-on. ![add-ons tools screenshot](./img/add-on-devtools.png) ### Status messages The **Add-on Development** panel also provides useful information via status messages like below to indicate when and where an error is occurring to help you target specific issues in your add-on. For instance, if an invalid value is found in the manifest, you will see something like the following: ![manifest error screenshot](./img/manifest-error.png) ### Refreshing and clearing data The **Refresh** and **Clear data** buttons in the add-on developer tools can also be helpful when you want to manually force refresh your code (or when you update the manifest), or clear data you no longer want to persist. For instance, in the case of the ToDo list sample add-on (aka: `use-client-storage`), if you had added some items previously they will still be displayed when you open it again unless you actually clear the data. See the demo workflow video at the bottom of the boilerplate section for an example of this in action. ![add-ons tools clear data screenshot](./img/clear-data.png) To make use of the add-on SDK's [ClientStorage API](../../references/addonsdk/instance-clientStorage.md) and store data in an underlying IndexedDB store, explore the ToDo list sample. You can view this store in the browser developer tools by navigating to the **Application** tab. Look for the IndexedDB store associated with your add-on ID to locate it. Here's an example: ![application tab indexed db screenshot](./img/application-indexed-db.png) See [the Client Storage API](../../references/addonsdk/instance-clientStorage.md) for more details about storing and persisting data with your add-ons. # Overview This section provides a set of guides to help you in the debugging stage of your add-on. ## Introduction During your development, it's important to be able to debug your code and troubleshoot any issues that may arise. Fortunately, most modern browsers come with built-in developer tools that can help you do just that. Check out this short video below on how to test and debug your add-on to help you get started more quickly, then read through the rest of the guides provided in this section for more details.

# Known Issues & Limitations ## Supported Browsers ## Sandboxed iFrame Caveats # Debugging with Visual Studio Code If you are a Visual Studio Code user, you can easily debug your add-on by following the steps in this guide. ## Steps 1. Begin by locating the existing `launch.json` file in the `.vscode` folder in the root of your project. This file will exist if you have created your add-on with the add-on CLI. Double check to ensure the URL points to `https://new.express.adobe.com/new/`. **NOTE:** If it's a sample add-on that you downloaded, you may need to create one first with the **create a launch.json file**. ![New launch.json file option](img/new-launch-json.png) Then copy in the JSON configuration included below (or copy one in from an add-on you previously generated). ```json { "version": "0.1.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Debug(Chrome) Add-On", "webRoot": "${workspaceFolder}", /** * For add-ons which donot specify the mappings automatically, * user will be required to secify the mappings under pathMapping property. */ "pathMapping": { "index.html": "${workspaceFolder}/src/index.html" }, /** * This will be the link of the document where the add-on is hosted * or the url of the new document where you want to load the add-on */ "url": "https://new.express.adobe.com/new/" }, { "type": "msedge", "request": "launch", "name": "Debug(MS Edge) Add-On", "webRoot": "${workspaceFolder}", /** * For add-ons which donot specify the mappings automatically, * user will be required to secify the mappings under pathMapping property. */ "pathMapping": { "index.html": "${workspaceFolder}/src/index.html" }, /** * This will be the link of the document where the add-on is hosted * or the url of the new document where you want to load the add-on */ "url": "https://new.express.adobe.com/new/" } ] } ``` 2. Start your add-on from your terminal as normal with `npm run start`. 3. Back in VS Code, click the **Run and Debug** option from the left panel and then select the profile related to where you want to debug (note that Chrome is the first one and selected by default but you can modify your configuration in the `launch.json` to your liking). ![launch.json file](img/vscode-debug-option.png) ![launch profiles](img/launch-profiles.png) 4. Once you have your selection set from above, simply hit the green play button outlined below to start debugging. ![start debugging](img/start-debug.png) 5. A new browser window will open for your debugging session directly to the Express URL you configured above. Connect to your add-on as you normally would in Express. 6. You can now set breakpoints as desired, and you will see the code execution stop with the line highlighted. You can also check the **DEBUG CONSOLE** window to see any console output directly in VS Code. ![debugging screenshot](img/debugging.png) 7. Note the toolbar added to the top of your screen in VS Code when you're in debug mode which allows you to step through your code after it's been stopped on a breakpoint. ![debugging tools](img/debugger-tool.png) # Overview This set of best practices are important to keep in mind as you develop your add-on since they can ultimately make or break the user experience with your add-on. ## Best Practices - Design responsively and remember the width and height of your add-on panel will vary by device. - If the user needs to drill down into multiple panels, ensure you provide a way for them to navigate back. - If the user will be logging in to a 3rd party service, ensure you provide a way for them to log out through your add-on UI. - It's best to use a vertical layout for your UI components, with full-width buttons and components and vertical scrolling for overflow. - If your add-on contains a gallery of images, a grid layout can work well. - Use the header to help provide context in cases where your add-on requires a multi-step workflow, and to help with navigation. - Use a footer if your add-on requires vertical scrolling for a primary CTA to be shown if needed. - If you're using a search, use placeholder text to guide users in what they can search for, and display the search results directly below the search field. - Use loading and progress indicators to provide visual feedback while things are in process. - Ensure your add-on is able to adapt seamlessly to appearance changes like if the theme changes from light to dark. Only the light theme will be supported at GA but you should still code your add-ons to adapt for when new support is added. **Note:** See the **SWC** sample for a reference on handling theme changes. - Always build with accessibility in mind. For instance, consider tab order and color, dark mode etc. Adobe Spectrum CSS provides design guidelines for ensuring accessibility in [the design system base docs](https://spectrum.adobe.com/), but it's also automatically built in to the Spectrum Web Components and React Spectrum implementations. - Light mode is currently the only theme available, but dark mode will be supported in the future, so be sure to consider how your UI will look in dark mode as well. # Using Fonts in Add-ons ## Adobe Fonts The following Adobe Express fonts are injected into the add-on and can be used automatically. ```css { family: "adobe-clean", source: "url('https://use.typekit.net/af/c0160f/00000000000000007735dac8/30/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n4&v=3') format('woff2'), url('https://use.typekit.net/af/c0160f/00000000000000007735dac8/30/d?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n4&v=3') format('woff'), url('https://use.typekit.net/af/c0160f/00000000000000007735dac8/30/a?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n4&v=3') format('opentype')", weight: "400", style: "normal", display: "auto" }, { family: "adobe-clean", source: "url('https://use.typekit.net/af/95bf80/00000000000000007735dacd/30/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=i4&v=3') format('woff2'), url('https://use.typekit.net/af/95bf80/00000000000000007735dacd/30/d?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=i4&v=3') format('woff'), url('https://use.typekit.net/af/95bf80/00000000000000007735dacd/30/a?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=i4&v=3') format('opentype')", weight: "400", style: "italic", display: "auto" }, { family: "adobe-clean", source: "url('https://use.typekit.net/af/5c07ba/00000000000000007735dad8/30/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n7&v=3') format('woff2'), url('https://use.typekit.net/af/5c07ba/00000000000000007735dad8/30/d?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n7&v=3') format('woff'), url('https://use.typekit.net/af/5c07ba/00000000000000007735dad8/30/a?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n7&v=3') format('opentype')", weight: "700", style: "normal", display: "auto" }, { family: "adobe-clean", source: "url('https://use.typekit.net/af/2dda0a/00000000000000007735dad4/30/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n8&v=3') format('woff2'), url('https://use.typekit.net/af/2dda0a/00000000000000007735dad4/30/d?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n8&v=3') format('woff'), url('https://use.typekit.net/af/2dda0a/00000000000000007735dad4/30/a?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n8&v=3') format('opentype')", weight: "800", style: "normal", display: "auto" }, { family: "adobe-clean", source: "url('https://use.typekit.net/af/bc79c1/00000000000000007735dad9/30/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n9&v=3') format('woff2'), url('https://use.typekit.net/af/bc79c1/00000000000000007735dad9/30/d?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n9&v=3') format('woff'), url('https://use.typekit.net/af/bc79c1/00000000000000007735dad9/30/a?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n9&v=3') format('opentype')", weight: "900", style: "normal", display: "auto" } ``` In the near future, all of the Adobe Express fonts will be injected for use, however, at the moment these specific fonts are being injected for you to access in your add-on without having to bundle them. ## Importing Fonts from a URL You can use a font with a URL by either linking to it via an import rule, via the <link> tag, or `font-face`. ### Import with the <import> tag: ```html ``` or ### Import with the <link> tag: ```html ``` # Implementation Guide This section will help you implement the concepts and best practices outlined in the UX Guidelines. ## Spectrum Design System Leveraging Spectrum in your add-on allows you to take advantage of all of the built-in benefits it provides while saving front-end development time. There are a few different implementations of Spectrum that are outlined in the sections below for reference, and in order of preferred use. Check out our [code samples](../../samples.md) for examples of how to use the libraries described here. Refer to the **export-sample** and **Pix** sample for a reference on using **Spectrum Web Components**, and the **Dropbox** and **import-images-using-oauth** for specific examples using **React Spectrum**. ### Spectrum Express Theme If you want your add-on UI to match the [Express look-and-feel](https://spectrum.adobe.com/page/theming/#Resources-for-Spectrum-for-Adobe-Express), you can find Express-themed components available within the [Spectrum CSS](https://github.com/adobe/spectrum-css), [Spectrum Web Components](https://opensource.adobe.com/spectrum-web-components/tools/theme/) and [React Spectrum](https://www.npmjs.com/package/@react-spectrum/theme-express) libraries. Each of the sections below has more details on how to use the theme with the different libraries. ### Icons Check out the variety of icons available for use in your add-ons from [Spectrum here](https://spectrum.adobe.com/page/icons/) as well. Additionally, there's a set of icons specificlly for the Express theme in an alpha stage currently available. To use those, install the package with `npm i @spectrum-icons/express` and then import the ones you want to use. ## Spectrum Web Components The [Spectrum Web Components](https://opensource.adobe.com/spectrum-web-components/) project is an implementation of Spectrum with a set of pre-built UI components that can be easily customized and integrated into your application. These components are designed to work seamlessly together and provide a consistent user experience across different devices and platforms. ***We highly recommend Spectrum Web Components as the preferred approach for building the UI of your add-ons***, since it offers a comprehensive set of components and built-in benefits that make it easy to create consistent, accessible, and responsive user interfaces. Some additional benefits include: - Framework agnostic - Lightweight and performant - Standards based ### Spectrum Web Components with React [**swc-react**](https://opensource.adobe.com/spectrum-web-components/using-swc-react/) is a collection of React wrapper components for the Spectrum Web Components (SWC) library, allowing you to use SWC in your React applications with ease. Currently, `swc-react` supports 62 components. To install `swc-react`, simply replace the scope name `@spectrum-web-components` with `@swc-react` in the package naming convention in your `package.json` (ie: `"@swc-react/button"` instead of `"@spectrum-web-components/button"`). The sub-package name remains identical to the original SWC component and will be automatically installed along with the **swc-react** component. Then, you can import and use it in your `.jsx` like shown in the example below: ```js import { Button } from "@swc-react/button"; ``` Check out the [swc-react-theme-sampler code sample](../../samples.md#swc-react-theme-sampler) for a specific example of how to use it with various components in your add-on, as well as [the official documentation](https://opensource.adobe.com/spectrum-web-components/using-swc-react/) for more details on **swc-react**. We recommend choosing [**swc-react**](https://opensource.adobe.com/spectrum-web-components/using-swc-react/) over [React Spectrum](#react-spectrum) in your add-ons based on React because it currently offers a more comprehensive set of components and built-in benefits. ### Spectrum Web Components with Express Theme Below are the steps for using the Express theme with your Spectrum Web Components UI: - Install the `spectrum-web-components` packages you would like to use. The `theme` package is one you will always want to install, but the others are included for illustration. See the [Spectrum Web Components site](https://opensource.adobe.com/spectrum-web-components/getting-started/) for all of the components available. ```bash npm install @spectrum-web-components/theme npm install @spectrum-web-components/field-label npm install @spectrum-web-components/textfield npm install @spectrum-web-components/button ``` - Next, start adding your imports. All add-ons should have this base set of imports, which provide support for Spectrum typography and the base Spectrum theme component, the Express themes, including colors (lightest, light, dark, and darkest) and scale options (medium, large). ```js import '@spectrum-web-components/styles/typography.css'; import '@spectrum-web-components/theme/sp-theme.js'; import '@spectrum-web-components/theme/express/theme-darkest.js'; import '@spectrum-web-components/theme/express/theme-dark.js'; import '@spectrum-web-components/theme/express/theme-light.js'; import '@spectrum-web-components/theme/express/theme-lightest.js'; import '@spectrum-web-components/theme/express/scale-medium.js'; import '@spectrum-web-components/theme/express/scale-large.js'; ``` - Then import the specific components you want to use in your code, such as: ```js import '@spectrum-web-components/button/sp-button.js'; import '@spectrum-web-components/field-label/sp-field-label.js'; import '@spectrum-web-components/textfield/sp-textfield.js'; ``` **Note:** The `import '@spectrum-web-components/theme/src/express/themes.js';` includes all of the definitions for the Express theme, but you can also only include the specific parts you need. For instance, if you only want to support the light theme and the medium scale, you could specifically include those with: `import '@spectrum-web-components/theme/express/theme-light.js'; import '@spectrum-web-components/theme/express/scale-medium.js';` For more details on themes and all of the color and scale options, see [this link](https://opensource.adobe.com/spectrum-web-components/tools/theme/). - Use a `webpack.config.js` for bundling the Spectrum Web Components and your JavaScript into a bundle. If you used the basic javascript template for your add-on, you can copy it in from a sample add-on, such as the SWC one in the contributed samples folder. Also be sure to include the webpack specific dependencies and script options in your `package.json`, which you can also copy from a sample like SWC. If you find that some files aren't being moved to `dist` after you build, you'll want to edit the file (line 31,32) to add more file types to copy. - Now you can use the `scale`, `color` and `theme` selections you desire with the `` component. Within those tags is where you should place all of your content that you want styled with those settings. For example: ```html /* Everything you want styled with those settings */ /* goes within the tag */ Enter your full name in the field below Submit ``` #### Default vs Express Theme The screenshots below are from a Spectrum Web Components sample app showing some an example of how the components differ between the themes to illustrate some differences for reference. #### Default Theme sample: ![Default theme](./img/swc-default-theme.png) #### Express Theme sample: ![Express theme](./img/swc-express-theme.png) Check out the [code samples](../../samples.md) in the contributed folder for **SWC** and **Pix** for examples of using Spectrum Web Components with plain JavaScript and React accordingly. ## React Spectrum [React Spectrum](https://react-spectrum.adobe.com/react-spectrum/index.html) is a project that implements the Adobe's Spectrum design language into React UI components. React Spectrum is composed of three parts: - **react-spectrum**: a component library implementing the Adobe Spectrum design system - **react-aria**: a library of React hooks implementing the patterns defined in the ARIA practices spec, including mouse, touch, and keyboard behavior, accessibility, and internationalization support - **react-stately**: a library of React hooks implementing cross platform (e.g. web/native) state management for components that need it. We recommend using [**swc-react**](https://opensource.adobe.com/spectrum-web-components/using-swc-react/) over [React Spectrum](#react-spectrum) in your add-ons based on React, because it currently offers a more comprehensive set of components which provide built-in benefits as detailed above in the [Spectrum Web Components section](#spectrum-web-components), and is more actively supported. ### React Spectrum with Express Theme [The React Spectrum Express theme](https://www.npmjs.com/package/@react-spectrum/theme-express) is still in an alpha stage currently, but can be used with the following steps: 1. Install it in your project with: `npm install @react-spectrum/theme-express` 2. Install the Express themed icons: `npm install @spectrum-icons/express` 3. Import the theme and icons into your code to use them. For example, notice the following code snippet which imports and sets the Express `theme`, light `colorScheme` option and medium `scale` option on the `` object. It also illustrates how to use the Express version of the `Delete` icon. ```js import { theme as expressTheme } from '@react-spectrum/theme-express'; import Delete from '@spectrum-icons/express/Delete'; const App = ({ addOnUISdk }) => { return ( ) } ``` #### React Spectrum Theme Examples Below is an example of some components from React Spectrum with the two themes. Please note, since the [React Spectrum with Express theme project](https://www.npmjs.com/package/@react-spectrum/theme-express) is still in an alpha state, there will be components that haven't been completely ported over yet. ##### Default theme sample: ![Default theme](./img/default-theme.png) ##### Express theme sample: ![Express theme](./img/express-theme.png) The [React Spectrum Express theme](https://www.npmjs.com/package/@react-spectrum/theme-express) is still in an alpha state, but you can use [Spectrum Web Components](https://opensource.adobe.com/spectrum-web-components/tools/theme/) with React as well. See the **Pix** code sample in the provided samples for an example of how to mix Spectrum Web Components with React. Specifically, you should note that there are some intricacies when using this combination of Spectrum Web Components and React in terms of event handling, but they can be handled by using a component that wraps the Spectrum Web Components for providing the event handling instead. In the **Pix** sample, take a look at the wrapper component called `WC.jsx` for a reference of how to do this. #### CAUTION: Using React Spectrum with a Slider Component If you're using a slider component with React Spectrum, you may notice behavior where if the user moves their mouse out of the panel and releases the mouse pointer, the slider still thinks the mouse pointer is down when the mouse is moved back inside the panel. You can fix this with the following steps: 1. Wrap your slider(s) with another element. A <div> works fine. 2. Add a ref that points to this div so you can refer to it later. 3. Add two event handlers: - `onPointerDownCapture` will call `sliderContainer.current.setPointerCapture(evt.nativeEvent.pointerId)` - `onPointerUp` will call `sliderContainer.current.releasePointerCapture(evt.nativeEvent.pointerId)` **Example Snippet** ```js import React, {useRef} from "react"; import { theme as expressTheme } from '@react-spectrum/theme-express'; import {Slider, Provider} from '@adobe/react-spectrum' const App = ({ addOnUISdk }) => { const sliderContainer = useRef(); const startDrag = (evt) => { sliderContainer.current.setPointerCapture(evt.nativeEvent.pointerId); } const stopDrag = (evt) => { sliderContainer.current.releasePointerCapture(evt.nativeEvent.pointerId); } return <>
}; export default App; ``` ## Spectrum CSS [Spectrum CSS](https://opensource.adobe.com/spectrum-css/) is an open-source implementation of Spectrum and includes components and resources to make applications more cohesive. Spectrum CSS is designed to be used in partnership with [Spectrum’s detailed usage guidelines](https://spectrum.adobe.com/). You should only rely on using the base [Spectrum CSS](https://opensource.adobe.com/spectrum-css/) library for simple applications that need basic things like typography, checkboxes, text fields, etc. Otherwise you should try using one of the other implementations provided like [Spectrum Web Components](https://opensource.adobe.com/spectrum-web-components/) and [React Spectrum](https://react-spectrum.adobe.com/react-spectrum/index.html) since they include interactivity, event handling etc built-in over what's possible with pure CSS. The best place to start with each of these libraries is to go to the **Getting Started** page in the top of the docs for each. ## Tips Use the existing Adobe Express UI as an example of the types of patterns and behaviors to use in your own add-on design. For instance, you could take a closer look at the other panels and how the UI is implemented in them to help guide you, such as the Media, Theme and Text panels shown below, which are already part of Express. #### Media Panel ![Express Media Panel](img/media-panel.png) #### Theme Panel ![Express Theme Panel](img/theme-panel.png) #### Text Panel ![Express Text Panel](img/text-panel.png) **Color Picker Component Tip:** If you're using the native browser color picker, it looks slightly different in every browser and doesn't fit the Express theme by default. You can make this control look more like Spectrum with CSS as [illustrated in this codepen](https://codepen.io/kerrishotts/pen/QWZazJP) for reference, or borrow the code used in [this guide](../tutorials/grids-addon.md#coding-the-grids-add-on). ## Using Fonts The following `adobe-clean` fonts and weights are injected into the add-on and can be used automatically. ```css { family: "adobe-clean", source: "url('https://use.typekit.net/af/c0160f/00000000000000007735dac8/30/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n4&v=3') format('woff2'), url('https://use.typekit.net/af/c0160f/00000000000000007735dac8/30/d?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n4&v=3') format('woff'), url('https://use.typekit.net/af/c0160f/00000000000000007735dac8/30/a?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n4&v=3') format('opentype')", weight: "400", style: "normal", display: "auto" }, { family: "adobe-clean", source: "url('https://use.typekit.net/af/95bf80/00000000000000007735dacd/30/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=i4&v=3') format('woff2'), url('https://use.typekit.net/af/95bf80/00000000000000007735dacd/30/d?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=i4&v=3') format('woff'), url('https://use.typekit.net/af/95bf80/00000000000000007735dacd/30/a?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=i4&v=3') format('opentype')", weight: "400", style: "italic", display: "auto" }, { family: "adobe-clean", source: "url('https://use.typekit.net/af/5c07ba/00000000000000007735dad8/30/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n7&v=3') format('woff2'), url('https://use.typekit.net/af/5c07ba/00000000000000007735dad8/30/d?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n7&v=3') format('woff'), url('https://use.typekit.net/af/5c07ba/00000000000000007735dad8/30/a?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n7&v=3') format('opentype')", weight: "700", style: "normal", display: "auto" }, { family: "adobe-clean", source: "url('https://use.typekit.net/af/2dda0a/00000000000000007735dad4/30/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n8&v=3') format('woff2'), url('https://use.typekit.net/af/2dda0a/00000000000000007735dad4/30/d?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n8&v=3') format('woff'), url('https://use.typekit.net/af/2dda0a/00000000000000007735dad4/30/a?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n8&v=3') format('opentype')", weight: "800", style: "normal", display: "auto" }, { family: "adobe-clean", source: "url('https://use.typekit.net/af/bc79c1/00000000000000007735dad9/30/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n9&v=3') format('woff2'), url('https://use.typekit.net/af/bc79c1/00000000000000007735dad9/30/d?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n9&v=3') format('woff'), url('https://use.typekit.net/af/bc79c1/00000000000000007735dad9/30/a?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n9&v=3') format('opentype')", weight: "900", style: "normal", display: "auto" } ``` ### Importing custom fonts You can use a custom font with a URL by either linking to it via the `@import` rule, the <link> tag, or the `@font-face` rule. #### Via the `@import` rule: ```css ``` #### Via the `` tag: ```html ``` #### Via the `@font-face` rule Specify a name for the font and where it can be found, then use it on the desired HTML tag with the specified name. For example: ```css @font-face { font-family: "MyWebFont"; src: url("https://mdn.github.io/css-examples/web-fonts/VeraSeBd.ttf"); } body { font-family: "MyWebFont", serif; } ``` Using custom fonts can be great for design, but it’s important to also understand that it can can cause a performance hit because the font must be downloaded before it’s displayed. Note that the `@font-face` `src:` supports a `local` attribute as well which will try to load the font from the users local system first, which can help mitigate the performance hit. See [this link](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face) for full details. ## Useful Resources - [Figma plugin](https://www.figma.com/community/file/1211274196563394418/Adobe-Spectrum-Design-System) that provides Spectrum UI elements. --- hideBreadcrumbNav: true --- # Design Overview The design of your add-on is just as important to the success of your add-on as the features it provides. ## Introduction This set of design guides is provided to help lead you through the design process of your add-on and includes useful guidelines, tips, and resources. First, we encourage you to watch this short video about designing your add-on interface, then read on for more details.

--- keywords: - Adobe Express - Express Add-on - Extend - Extensibility - User Interface - User Experience - UI - UX - Guidelines - Branding title: Branding Guidelines description: This document provides an overview of the UX guidelines to follow when designing your Adobe Express add-on. contributors: - https://github.com/undavide --- # Branding Guidelines When creating your add-on, a distinct and consistent brand identity is your ticket to a lasting impression. This section focuses on key aspects of branding, like Icon and Publisher Logo design, as well as best practices for incorporating promotional images (screenshots or illustrations) for the Adobe Express add-on Marketplace. Please refer to the [Developer Brand Guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf) for comprehensive details on integrating Adobe branding, including when and how to use Adobe logos, product names, and terms. ## Add-on Icon The add-on's icon should be original, suitable for different devices and browsers, and not violate the [Developer Brand Guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf). Please request a written license agreement from Adobe if you wish to use any Adobe logo, product icon, or image in your preview and feature graphics, product icons, or website. Three sizes are required for the add-on's icon: **36x36**, **64x64**, and **144x144** pixels as PNG or JPG files. ![add-on icon](./img/branding_add-on-icon.png) ## Publisher Logo The publisher's logo is only required the first time you [submit for distribution](/guides/distribute/public-dist.md#2-prepare-your-assets), in case you've never created a publisher profile before. It should be a square, **250x250** pixels PNG or JPG file. ![publisher logo](./img/branding_publisher-logo.png) ## Imagery When creating a [public listing](../../distribute/public-dist.md) for the Adobe Express add-on Marketplace, you can provide promotional images, such as screenshots and illustrations. At least one is required. ![screenshots and illustrations](../../distribute/img/public-listing-screenshots-v2.png) ### Screenshots and illustrations Any image used in your listing must comply with the [Developer Brand Guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf). They should accurately depict the add-on's functionality and features and not contain any misleading information or inappropriate content. The recommended image size is **1360x800** pixels, as PNG or JPG. ![screenshots and illustrations](./img/branding_screenshot.png) --- keywords: - Adobe Express - Express Add-on - Extend - Extensibility - User Interface - User Experience - UI - UX - Guidelines - Branding title: Changelog description: UX Guidelines Changelog. contributors: - https://github.com/undavide --- ## Changelog ### 8 October 2024 - Created a new web version of the Adobe Express add-on UX Guidelines. - Integrated the existing content from the Adobe XD document into the new structure. The old version is [available here](https://xd.adobe.com/view/urn:aaid:sc:US:fd638450-1af8-49c3-ad29-0e76c2a2136f/) as a reference. ### 28 August 2023 - Last update to the Adobe XD document version of the Adobe Express add-ons UX Guidelines. --- keywords: - Adobe Express - Express Add-on - Extend - Extensibility - User Interface - User Experience - UI - UX - Guidelines - Design Principles title: Design Principles description: This document provides an overview of the UX guidelines to follow when designing your Adobe Express add-on. contributors: - https://github.com/undavide --- # Design Principles When you're designing add-ons for Adobe Express, it's important to start with the principles that guide all of Adobe's products. They will be your roadmap, ensuring that what you create is not only visually appealing but also consistent, accessible, and user-friendly. Think of them as your toolkit for building something that looks great, works well, and fits seamlessly into the application. ## Consistency: creating a cohesive experience You should design your add-ons to feel like a natural extension of the platform, conforming to Adobe Express's visual language: the Spectrum Design System. This coherence isn't just about visual elements but extends to interaction patterns and behaviours. Keeping everything aligned creates a smoother experience where users can easily navigate and understand your add-on, achieving their goals effortlessly. ## Accessibility: designing for inclusion All add-ons you develop should be accessible to everyone, regardless of their abilities or circumstances. This includes considering elements like contrast and text size, as well as accommodating a broad spectrum of digital skills. An accessible design adapts to the user, offering clear and intuitive interactions, removing barriers, and making sure that every user can fully engage with it. ## Usability: prioritizing ease of use Your add-on should be as easy to operate as it is powerful. Design it with users in mind so they can navigate it effortlessly, where each feature is clearly accessible and straightforward. Eliminate unnecessary steps and ensure that every interaction feels natural. Always provide feedback so users know what to expect and how to achieve their goals. The more intuitive the experience, the more likely they are to engage and rely on it over again. ## Aesthetics: balancing form and function The aesthetic appeal of your add-on plays a significant role in the overall user experience: a pleasing design attracts more users and enhances their interaction with it. When the design is functional and visually charming, it creates an enjoyable experience and complements usability. Keeping the aesthetics aligned with Adobe's brand identity will reinforce the connection to the platform and tighten the overall user experience. ## Bringing it all together By embracing these design principles, you'll build add-ons that integrate smoothly with Adobe Express and provide real value to users. Each concept works in harmony with the others; keep these guidelines in mind as you develop, and you'll create tools that are both effective and a pleasure to use. In the following pages, you'll find detailed recommendations, requirements, and best practices to help you apply these principles to your projects. --- keywords: - Adobe Express - Express Add-on - Extend - Extensibility - User Interface - User Experience - UI - UX - Guidelines - Feedback - Messaging title: Feedback & Messaging description: This document provides an overview of the UX guidelines to follow when designing your Adobe Express add-on. contributors: - https://github.com/undavide --- # Feedback & Messaging Feedback and messaging are essential UX components. Your add-ons should always provide users with information about the state of the system, the results of their actions, and any errors that may have occurred. Unclear feedback and mishandled errors can lead to confusion and, crucially, are a cause of [rejection](../../../guides/distribute/rejections.md#error-handling) when submitting add-ons to the Adobe Express Marketplace. On the other hand, well-designed feedback can help users understand what is happening and what they need to do next. ## Loading Indicators Loading indicators are particularly important when the system is processing a request that may take some time to complete; for instance, when fetching data from a server or authenticating a user. They show that the system is working and users should wait for the process to finish. ### Progress Circles [Progress circles](https://spectrum.adobe.com/page/progress-circle/) show the progression of a system operation such as downloading, uploading, processing, etc. in a visual way. ![Progress circles](./img/feedback_progress-circles.png) They can represent determinate or indeterminate progress: either the percentage of the operation that has been completed or spin indefinitely until completion. ![](./img/feedback_progress-circles-animation-wide.gif) ### Ghost Loading When a group of cards are loading, they follow the [ghost loading convention](https://spectrum.adobe.com/page/cards/#Ghost-loading). ![](./img/feedback_ghost-loading.png) There are 5 phases for ghost loading: 1. Card group (including metadata) ghost loads. 2. If metadata for all cards is loaded before all preview images are loaded, the metadata is displayed for all cards as soon as the last piece of metadata loads. Previews continue to ghost-load. 3. If all preview images load within a certain period (usually measured in seconds, which you need to specify depending on the use case), they are shown as soon as the last preview loads. 4. If all previews have not finished loading at the end of the x period, the loaded previews are shown, and the pending previews each receive an individual progress circle. The group is no longer in a ghost-loading state. 5. If the preview load times out, an error is shown along with a mechanism to retry loading. ## In-line Alerts [In-line alerts](https://opensource.adobe.com/spectrum-css/inlinealert.html) display a non-modal message associated with objects in a view. These are often used in form validation, providing a place to aggregate feedback related to multiple fields. ![](./img/feedback_ghost-loading.png) ## Toasts [Toasts](https://spectrum.adobe.com/page/toast/) display brief, temporary notifications. They’re meant to be noticed without disrupting a user’s experience or requiring an action to be taken. ![](./img/feedback_toasts.png) They come in multiple kinds, each with a different purpose: ![](./img/feedback_toast-kinds.png) ## Full-panel Messaging When the feedback is more complex or requires more space, a full-panel message can be used. ![](./img/feedback_full-panel-messaging.png) Please mind the padding between the graphics, text and other UI elements to ensure a clear and organized layout. ![](./img/feedback_full-panel-messaging-padding.png) --- keywords: - Adobe Express - Express Add-on - Extend - Extensibility - User Interface - User Experience - UI - UX - Guidelines hideBreadcrumbNav: true title: Add-on UX Guidelines description: This document provides an overview of the UX guidelines to follow when designing your Adobe Express add-on. contributors: - https://github.com/undavide --- # UX Guidelines for Adobe Express Add-ons A successful add-on provides a seamless and intuitive User Experience (UX) that blends harmoniously with Adobe Express. ![](./img/introduction_cover.png) These Guidelines are your roadmap to creating add-ons that align with Adobe's design principles and visual language, ensuring a consistent and enjoyable experience. Following them will help you build tools that resonate with users and feel like a natural extension of the application. ## Intended audience This document is intended for developers, designers, and product teams involved in creating Adobe Express add-ons. Whether you're crafting your first add-on or improving an existing one, these guidelines offer valuable insights and best practices to help you align with Adobe's UX standards. ## How to navigate this document To get the most out of the UX Guidelines, start by familiarizing yourself with the [Design Principles](design_principles.md) section. They provide the foundation for all design decisions and will help you understand the core values that should drive your work. Next, the [Theming](theming.md) section shows you how to implement the Spectrum for Adobe Express theme, along with customization options and examples. [Visual Elements](./visual_elements.md) is a detailed list of structural and functional components that make up the Adobe Express visual language. It covers everything from typography to grids, navigation, and a variety of UI elements alongside with implementation examples. [Feedback & Messaging](feedback_and_messaging.md) provides best practices for communicating feedback or alert errors to users. Explore the [Branding Guidelines](branding_guidelines.md) to ensure your add-on is legally & visually aligned with Adobe Express. They cover key aspects of branding, as well as best practices for incorporating promotional images for the Adobe Express add-on Marketplace. Finally, look at the [Resources & References](./resources_and_references.md) for videos and further reading. Refer to the [Changelog](./changelog.md) to stay up-to-date with the latest features and improvements to these guidelines: they are a living document we encourage you to consult regularly, both as a reference and a source of inspiration. --- keywords: - Adobe Express - Express Add-on - Extend - Extensibility - User Interface - User Experience - UI - UX - Guidelines - Mobile title: Mobile UX description: This document provides an overview of the UX guidelines to follow when designing your Adobe Express add-on. contributors: - https://github.com/undavide --- # Mobile UX Designing add-ons that can be used when Adobe Express runs on mobile devices requires some attention. Although the same general principles so far outlined still apply, component sizing, layout, and typography should be tweaked to ensure the proper user experience on smaller screens. The following sections outline the key differences and best practices for optimizing your add-ons for mobile devices. ![Mobile UX overview](./img/mobile_overview.png) ## Basic Structure Mobile interfaces feature a streamlined layout, with two primary content areas controlled by Adobe: the title bar and the footer. The add-on's content is displayed in the body area; considering the smaller screen size, you should optimize the design for readability and ease of interaction. ![Mobile basic structure](./img/mobile_basic-structure.png) ## Default panel height vs. full panel height By default, the panel covers around half of the screen's vertical space, with a height of 375px. This layout balances the add-on interface and the document view, ensuring both are easily accessible. When the user swipes up the handlebar, which is always visible, the panel expands upward until the entire 699px height available is covered. This full-height view is ideal for displaying detailed content or additional options. ![Mobile height](./img/mobile_default-vs-full.png) ## Typography Typography on mobile requires adjustments from desktop standards to ensure readability; the font size is generally smaller, and the weight is lighter. For **headings**, we recommended a font-size in the 15-19px range, and not exceeding 20px. The font-weight should be 400-500, typically the latter. On desktop, headings are in the 14-22px range (rarely, up to 34px) and weights of 600-700 are common. ![Mobile typography](./img/mobile_typography.png) On mobile, **body** is in the same 15-19px range, with a font-weight of 300. ![Mobile typography specs](./img/mobile_typography-specs.png) ## Foundational components While mobile and desktop share many common components, sizing and placement are crucial distinctions. Mobile interfaces should avoid using extra-large (XL) component instances, which can overwhelm the screen. Instead, opt for Medium (M) or Large (L) sizes, more suitable for mobile displays. ![Mobile foundational components](./img/mobile_components.png) ### Buttons On mobile, buttons that convey singular, primary actions should be styled using the Medium size and Primary variant. Center them horizontally and align them to the bottom—remember to account for the footer area controlled by Adobe. ![Mobile buttons](./img/mobile_buttons.png) ### Button Groups Button groups should extend across the available width of the UI, minus a 40px margin (16px on each side, with an 8px gap between buttons). This layout ensures that buttons are easily tappable and well-spaced on smaller screens. ![Mobile buttons](./img/mobile_button-groups.png) --- keywords: - Adobe Express - Express Add-on - Extend - Extensibility - User Interface - User Experience - UI - UX - Guidelines - Resources - References title: Resources and References description: This document provides an overview of the UX guidelines to follow when designing your Adobe Express add-on. contributors: - https://github.com/undavide --- # Resources and References To inform and inspire your design process, we've compiled a list of resources and references to help you create engaging, consistent and functional user experiences for your Adobe Express add-ons. ## Video Tutorials Design tips for creating Adobe Express add-ons, by Steph Corrales (Sr. Experience Designer, Adobe Developer Experience).


Adobe Express add-on's with Spectrum Web Components [workshop](../../tutorials/spectrum-workshop/index.md), by Holly Schinsky (Sr. Developer Relations Engineer, Adobe Developer Experience).


## Further Reading - [Adobe Spectrum](https://spectrum.adobe.com/): Adobe's design system. - [Spectrum Web Components](https://opensource.adobe.com/spectrum-web-components/): Web components implementation of Spectrum. - [Adobe Spectrum 2](https://s2.spectrum.adobe.com/): the next major version of Spectrum, currently in preview. --- keywords: - Adobe Express - Express Add-on - Extend - Extensibility - User Interface - User Experience - UI - UX - Guidelines - Theming title: Theming description: This document provides an overview of the UX guidelines to follow when designing your Adobe Express add-on. contributors: - https://github.com/undavide --- # Theming Adobe Express is based on Spectrum, Adobe's design system, which provides a set of guidelines and components for creating consistent and visually engaging user interfaces. While Spectrum is the main reference throughout this guide, the Adobe Design team has released a preview of the next major version, [Spectrum 2](https://s2.spectrum.adobe.com/): a completely rebuilt user experience that is more modern, friendly, accessible, and enjoyable to use. ## Spectrum for Adobe Express Theme The Spectrum design system supports theming, allowing you to customize features like icon sets, font weight and size, and accent colors to suit your brand or use case better. We recommend using the provided [Adobe Express Theme](https://spectrum.adobe.com/page/theming/#Available-themes), specifically designed to accommodate the needs of a mainstream, creative consumer audience. It features a friendlier visual tone, bolder typography, softer rounding on elements, and indigo as the accent color. See the Spectrum for Adobe Express theme in action below on the right, compared to the default on the left: ![Spectrum for Adobe Express](./img/components_theme.png) ## Customization Guidelines When customizing your add-on's theme, you're free to incorporate brand-specific elements, such as color schemes and logos tailored to your identity. It is essential that the overall experience remains consistent with the Spectrum for Adobe Express styling guidelines, though. They ensure a cohesive user experience across all add-ons, maintaining a professional and polished appearance while allowing room for personalization. If you’re developing a custom component, it must adhere to the Spectrum Express style standards, too, in order to ensure visual harmony and usability within the Adobe Express ecosystem. For detailed guidance on these requirements, please refer to [these guidelines](https://opensource.adobe.com/spectrum-web-components/tools/theme/#usage). --- keywords: - Adobe Express - Express Add-on - Extend - Extensibility - User Interface - User Experience - UI - UX - Guidelines - Visual Elements title: Visual Elements description: This document provides an overview of the UX guidelines to follow when designing your Adobe Express add-on. contributors: - https://github.com/undavide --- import '/src/styles.css' # Visual Elements You can use Color, Typography and Layout to establish hierarchy in your designs. Adobe Express implements the "Spectrum for Adobe Express theme", which has been designed specifically for it. You’ll notice a friendlier visual tone, with bolder typography, softer rounding on elements, and indigo serving as the accent color. ## Color The colors available in the Spectrum for Adobe Express light theme are designed to provide users with clear visual context and establish a hierarchy of actions within the UI. These colors help guide users through their workflow, ensuring that key actions stand out while maintaining a cohesive and accessible design. When applying color, follow the specific styling recommendations at the component level to ensure consistency. Avoid overusing the accent color: it's meant to highlight primary actions, not dominate the entire interface. Additionally, adhere to the contrast ratio guidelines to maintain readability and accessibility for all users. For more details on best practices, please refer to this [Color system guide](https://spectrum.adobe.com/page/color-system/#Colors). ## Typography Spectrum is based on the Adobe Clean and Adobe Clean Han typefaces. The former is used for Latin characters, while the latter is for Simplified Chinese, Traditional Chinese, Japanese, and Korean ones. ### Headings Headings in Spectrum for Adobe Express use bolder typography. The default weight is 700 (Adobe Clean Black, Sans Serif), with a size ranging from M to XXS. ![](./img/visual_heading.png) ### Body Text The available sizes for body text in Spectrum for Adobe Express span from XS to XXL, while the weight is usually around 400-500. ![](./img/visual_body.png) ### Usage Here's an example of how you can implement the Adobe Clean typeface in your add-on; please note the recommended spacing between text and other user interface elements. ![](./img/visual_example.png) ## Layout & Structure A well-structured layout sets the stage for a great design. Adobe Express is built on a clean and consistent structure designed for clarity and usability: add-ons should follow the same rules and blend in. ### Basic Structure Add-ons on Desktop are assigned a specific width of **320px** and a **100%** height to ensure uniformity within the Adobe Express user interface and its modules. The layout is divided into the following parts: - The title bar with the add-on's icon—controlled by Adobe. - The body holding the add-on's content. - Optionally, a footer with Call to Action (CTA) buttons. ![Basic Structure](./img/layout_basic.png) ### Padding Padding plays a crucial role in maintaining visual harmony and readability. You should consistently apply the following padding scheme: - **24px** on the body to create a balanced spacing around the content. An additional padding of the same amount is applied at the footer's bottom. - **16px** between internal elements to ensure they are appropriately spaced. This padding structure ensures that content is well-separated and easy to interact with. ![Padding](./img/layout_padding.png) ### Responsive Grid and Core Container Styles Adobe Express add-ons rely on a [responsive grid system](https://spectrum.adobe.com/page/responsive-grid/). Core container styles define a few key media sizes: - **272x128px** for banners. - **128x128px** tops for standard media. - **80x80px** for smaller media items. Remember to apply the standard gap of 16px between grid elements for consistency. ![Responsive Grid and Core Container Styles](./img/layout_grid.png) ### Panel Structure and core content actions Panel structure in Adobe Express add-ons is designed to support core content actions, such as searching, sorting, and filtering. These actions are typically placed at the top of the panel for easy access, between the title and main body. ![Panel Structure and core content actions](./img/layout_actions.png) ### Structural Grids and Foundational Patterns Structural grids in Adobe Express add-ons are designed to accommodate various content types, from simple lists to more visually complex media grids. Three of the container variants are as follows: - **Media Grid:** A straightforward grid for displaying media items. - **Media Grid with Labels:** Enhances the basic grid by adding labels beneath each item, ensuring clarity. - **Media Grid with Cards:** Adds a card-like structure on each media item. Please note the specific paddings on each of these grid types. ![Structural Grids and Foundational Patterns](./img/layout_patterns.png) ## Panel Actions Panel Actions are interactive elements that allow users to search, sort, filter, and manage content within your add-on. ### Actions Overview Commonly found at the top of the add-on panel, Panel Actions include: - **Search**: allows users to quickly find specific content. - **Filter**: enables users to narrow down visible content based on criteria. - **Sort**: organizes content by relevance, date, or other metrics. - **Add Folder**: gives users the ability to create new folders within the add-on. - **Tabs**: lets users navigate between different sections or views. These actions enable users to "drill into" specific sections, for example dynamically refining a list of thumbnails that follows. ![Panel Actions](./img/visual_actions-search.png) ### Best Practices Panel Actions, when available, should be arranged at the top of the add-on's content area. This ensures that users can easily access them and that they are always visible even when the grid below scrolls. ### Examples Here are some examples of common panel organizations and hierarchies for Adobe Express add-ons. ![Examples](./img/visual_actions.png) ## Components Spectrum includes a number of widgets that allow you to build rich UIs for your add-ons. Foundational components should use the Spectrum for Adobe Express theme whenever possible. This is a [Spectrum theme](https://spectrum.adobe.com/page/theming/) specifically designed for the Adobe Express product suite to accommodate the needs of a mainstream, creative consumer audience. It features a friendlier visual tone, bolder typography, softer rounding on elements, and indigo as the accent color. See the Spectrum for Adobe Express theme in action below on the right, compared to the default on the left: ![Spectrum for Adobe Express](./img/components_theme.png) ### Buttons [Buttons](https://spectrum.adobe.com/page/button/#Usage-guidelines) allow users to perform an action or navigate to a different part of the add-on. They have multiple styles to fit various needs and are ideal for calling attention when a user needs to take action to move forward in the workflow. ![Buttons](./img/components_buttons.png) Please mind the padding when using CTA buttons in the add-on's footer. ### Button Groups A [Button Group](https://spectrum.adobe.com/page/button-group/) is a collection of buttons that perform related actions. See the [Usage Guidelines](https://spectrum.adobe.com/page/button-group/#Usage-guidelines) for more information on their correct use. ![Button Groups](./img/components_button-groups.png) ### Secondary Variant Buttons The [Secondary Variant Button](https://spectrum.adobe.com/page/button/#Usage-guidelines) is for low emphasis. It’s paired with other button types to surface less prominent actions, and should never be the only button in a group. ![Button Groups](./img/components_secondary-button-variants.png) Spectrum buttons support several variants (Accent, Primary, Secondary, Negative, Icon) to fit different use cases; refer to the [Spectrum reference](https://spectrum.adobe.com/page/button/#Options) to see all available options and when to use them. ## Form Elements Form elements are essential for collecting user input and enabling interactions within your add-on. They include a variety of components such as text fields, search fields, pickers, and more. ### Text Fields [Text fields](https://spectrum.adobe.com/page/text-field/) allow users to input custom text entries with a keyboard. Various options can be shown with the field to communicate their requirements. ![Text Fields](./img/form_text-fields.png) It's recommended to let text fields span the entire add-on's width—minus the container's padding—when possible, for a more consistent look. ### Search Fields A [Search Field](https://spectrum.adobe.com/page/search-field/) is used for searching and filtering content. As mentioned in the [Layout & Structure](#layout--structure) section, the search field should be placed at the top of the panel, between the title and the main body. ![Search Fields](./img/form_search-field.png) ### Pickers [Pickers](https://spectrum.adobe.com/page/picker/) (sometimes known as "dropdowns" or "selects") allow users to choose from a list of options in a limited space. The list of options can change based on the context. ![Pickers](./img/form_pickers.png) Like all the components covered so far, it's best for pickers to take advantage of the add-on's full width. ### Color Pickers Color pickers are a special type of picker that allows users to select a color. ![Color Pickers](./img/form_color-pickers.png) The Spectrum component used as a Color Picker is the [Swatch](https://spectrum.adobe.com/page/swatch/), which shows a small sample of a fill—such as a color, gradient, texture, or material—that is intended to be applied to an object. The Swatch itself doesn't embed any color picker functionality, but it can be used in conjunction with a native `` hidden element to trigger the browser's color picker. You can find an example with sample code in [this tutorial](/guides/tutorials/grids-addon.md#coding-the-grids-add-on). ## Navigation Navigation is a key component of any user interface. It helps users understand where they are, where they can go, and how to get there. Spectrum provides a few components that we recommend to help you build a clear and consistent navigation system for your add-on. ### Tabs Tabs organize content into multiple sections and allow users to navigate between them; the content under the set of tabs should be related and form a coherent unit. Please always include a label for accessibility. ![Tabs](./img/navigation_tabs.png) ### Accordions The [Accordion](https://opensource.adobe.com/spectrum-web-components/components/accordion/) element contains a list of items that can be expanded or collapsed to reveal additional content or information associated with each item. There can be zero expanded items, exactly one expanded item or more than one item expanded at a time, depending on the configuration. This list of items is defined by child elements that are targeted to the default slot of their parent. ![Accordions](./img/navigation_accordions.png) # Cross-origin isolation handling How to ensure your add-ons work properly with the cross-origin isolation headers enforced by Adobe Express. ## Overview Adobe Express will soon be enforcing cross-origin isolation on the associated domains (i.e., “new.express.adobe.com”) for Chromium-based browsers (including Chrome, Microsoft Edge, Opera, and others). This *may* impact your add-on due to stricter rules enforced by the browser. We expect the enforcement of cross-origin isolation headers to begin around the end of 2024, and we will update you the moment we have a more specific date. In the meantime, you’ll want to ensure that any add-ons you’ve developed or are developing now work in this new environment. Specifically, Adobe Express will be setting the following response headers: - `Cross-Origin-Embedder-Policy: credentialless` - `Cross-Origin-Opener-Policy: same-origin` - `Cross-Origin-Resource-Policy: same-site` This change is being done to support Adobe Express’ use of certain browser capabilities requiring cross-origin isolation. ## Impact This change may impact your add-on’s access to external resources, especially if it relies on iframes to display content or support payment flows. It could also impact `fetch` or image requests from external sources. In these cases, users may see missing content or be presented with silent failures if your add-on can’t load a remote resource. Since this results in a poor experience, developers must ensure that their add-ons work in the above environment. Please note that this change affects *all* add-ons, even those not published in the add-on marketplace (i.e., private and internally distributed add-ons). Currently, this change *only impacts* Chromium-based browsers (e.g., Chrome, Edge, Opera, etc.). Firefox and Safari browsers are currently exempt. If you've developed a an add-on with mobile support, this would apply to add-ons running on Android devices as well. ## Types of failures Some failures will be more evident than others, but all will negatively impact the user experience of your add-on. - If a nested iframe fails to load, Chrome displays a very obvious error message inside the iframe indicating that the domain "refused to connect". - If an image fails to load, you may notice missing images in your add-on’s user interface. You should also see failures in the Network section of the browser’s developer tools. - If a network call fails due to JavaScript code, you should see warning and error messages in the browser’s developer tools. ## Testing your add-on Until Adobe Express enables these headers by default, you must configure your local development environment to simulate these changes. ### Apply local header overrides The developer tools in Chromium-based browsers on the desktop allow you to specify header overrides. Using this, you can simulate the headers that Adobe Express will enable to test whether your add-on has any problems in this environment. Mobile add-ons You cannot test this on mobile devices. You should test your add-on on a desktop web browser powered by Chromium. Any issues you run into would also appear on mobile devices, and any fixes you apply would also apply to mobile users. To enable this environment yourself, perform the following steps: 1. Open the developer tools: Launch your Chromium-based browser (e.g., Chrome, Edge, Opera). Open the developer tools by pressing `F12` or `Ctrl+Shift+I` (Windows/Linux) or `Cmd+Option+I` (Mac). 2. Navigate to the **Network** tab: In the developer tools, click on the **Network** tab, and then navigate to [Adobe Express](https://new.express.adobe.com) in the browser. Your network panel should fill up with a lot of network traffic, like this: ![Network panel screenshot](./img/coi-test-1.png) 3. Apply header overrides: To apply an override, right click on the entry for **new.express.adobe.com** and select **Override headers**. ![Override headers screenshot](./img/coi-test-2.png) **IMPORTANT:** Assuming you haven’t done this before, the developer tools will ask you to pick a folder on your local file system where these overrides are stored. The alert is easy to miss, since it doesn’t present as a dialog box, but rather a message near the top of your developer tool window. ![Override storage folder screenshot](./img/coi-test-3.png) 4. Select a local folder: Click **Select folder** to choose where you want to store the overrides. This will open your browser’s file picker. You’ll likely want to create a new folder for this step. You can put this anywhere you’d like. For example, in the following image we've selected the **Downloads** folder. ![Override downloads folder screenshot](./img/coi-test-4.png) 5. Allow access, if prompted: Depending on your operating system and the folder's location, the developer tools may need to request additional permissions to access it. If so, the message will again appear near the top of the developer tool window. ![Allow access screenshot](./img/coi-test-5.png) Click **Allow**. This may prompt an operating system level permissions prompt. In that case, be sure to allow that as well: ![OS permissions prompt screenshot](./img/coi-test-6.png) 6. Navigate to the **Sources** tab: Once any required permissions have been granted, navigate to the **Sources** tab in the developer tools: ![Sources panel screenshot](./img/coi-test-7.png) 7. Open the **Overrides** menu: Next, click the **`>>`** icon and select the **Overrides** menu option: ![>> Icon clicked screenshot](./img/coi-test-8.png) Expand the entry you see there completely. You should see something like the following: ![Overrides screenshot](./img/coi-test-9.png) 8. Add an override rule: To add an override rule, click the **Add override rule** option: ![Add override rules screenshot](./img/coi-test-10.png) 9. Enter the header names and values to override: Click on `header-name-1` and start entering the name of the first header -- in this case, `Cross-Origin-Embedder-Policy`. ![Header name screenshot](./img/coi-test-11.png) Press TAB or click into the field that says `header value` and update it to the appropriate value -– in this case, `credentialless`. Once done, click the **+** icon to add the next header. ![Credentialless header value](./img/coi-test-12.png) 10. Finalize header overrides: You’ll want to add headers until your overrides look like the following: ![Final header values screenshot](./img/coi-test-13.png) 11. Reload and test: At this point, you can reload Adobe Express and test your add-on. Be sure to watch the network panel for errors that your add-on might encounter. ### What to test in your add-on You should test flows in your add-on that involve the following: - **Purchase flows:** In particular, if you’re using an iframe to handle the purchase experience, you should also test an international purchase to ensure that any additional verification flows your payment provider requires also work. *Note: You're probably not impacted if you handle purchases in a new tab.* - **Flows that load external domains in iframes:** For example, you may be using an iframe to generate a preview for the user or using an iframe to embed a video player. - **Flows that display images and other content:** For example, if you’ve built an add-on that allows the user to add stickers and the stickers are served from your domain or another third party, you should verify that the images appear in the add-on’s user interface correctly. (If the content is bundled with your add-on, you should already be covered.) - **Flows that add content to the user’s document:** Make sure that users can successfully add images or other assets to the document if your add-on provides this functionality. This should only apply to assets loaded from your domain or an external domain. Generated assets or assets bundled with your add-on should not have any issues. If your add-on doesn’t access external content, make network calls, or use iframes to display content or payment flows, you should not be affected by this change. ## Disabling overrides When you’re done testing, you’ll likely want to disable any header overrides in your browser’s developer tools. You can disable the overrides by unchecking **Enable Local Overrides** or by removing the header override directly. ![Disable overrides screenshot](./img/coi-test-14.png) ## Addressing issues found in your add-on Applying fixes to your add-on is generally straightforward, but it depends on the issue you’re seeing. ### Fixing assets that fail to load If the asset is in an `` tag, you’ll want to set the `crossorigin` attribute. You can also set the `crossOrigin` property when using JavaScript. For examples, see the [MDN web documentation](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin). ### Fixing iframes or network calls that fail to load You’ll need to set the following headers on the endpoint you’re trying to load. If loading an iframe, you’ll likely need to ensure these headers apply to all assets that can be loaded by the iframe. - `Cross-Origin-Embedder-Policy: credentialless` - `Cross-Origin-Resource-Policy: cross-origin` If the endpoint is managed by a third party, you may have more difficulty in addressing the issue. Some things you can do: - Check if the third party provider provides a mechanism to specify header overrides. - Ask if the third party provider can set headers for you via their existing support channels. - Create a proxy that you control to act as an intermediary. This has security and privacy implications since you need to ensure that the proxy is secure, doesn’t mix up or serve incorrect data, and doesn’t preserve user information for any longer than necessary to complete the transaction. ## [Review process impact](https://developer.adobe.com/express/add-ons/docs/guides/distribute/guidelines/general/) All new add-ons published to the marketplace will be reviewed with these headers in place. If the reviewer finds a problem with your submission related to cross-origin isolation that impacts the usability of your add-on, the reviewer will reject your add-on. You can then use the above to address the issues before submitting again. # Add-on iframe Context Important details about the context of your add-on; permissions, security, CORS and more. ## iframe Sandbox Your add-on is essentially a website running in a [sandboxed](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox) iframe. As a result, the content of your add-on runs in a low-privileged environment, has a subset of capabilities, and has an extra set of restrictions by default. It's important to understand how this may affect the add-on you're building, as well as how to learn to mitigate any problems you may run into along the way. ### Restrictions The following set of restrictions are enabled when the `sandbox` attribute is applied to the ` ## Webpack & JavaScript bundler When using Node libraries or other frameworks, you'll often need to use a JavaScript bundler. All of the templates the CLI provides (other than the basic `javascript` template) are pre-configured for webpack via the `--use` option set on the `ccweb-add-on-scripts` commands. If you create a new add-on project based on a react or typescript based template for instance, you will see the following `scripts` block generated in your `package.json`, and the existence of a `webpack.config.js` in the root of your project: ```json "scripts": { "clean": "ccweb-add-on-scripts clean", "build": "ccweb-add-on-scripts build --use webpack", "start": "ccweb-add-on-scripts start --use webpack", "package": "ccweb-add-on-scripts package --use webpack" }, ``` However, if you want to use any other transpiler or bundler of your choice, you can do so, provided you have the correct configurations and packages installed (similar to how these templates have `webpack.config.js` defined, and its plugins and loaders installed). For example, if you want to use `tsc` to transpile the `.ts` files, you'll need to install the typescript package and add `tsconfig.json` to your project, then configure the script commands to make the transpilation and hosting work: ```json "scripts": { "build": "ccweb-add-on-scripts build --use tsc", "start": "ccweb-add-on-scripts start --use tsc", "package": "ccweb-add-on-scripts package --use tsc" } ``` The `src` folder in your project should contain all of your code and static asset files to ensure any changes you make are automatically detected by the hot module reloader, allowing you to see your updates immediately. #### Update `webpack.config.js` with any new files to be copied Configurations are included in the `webpack.config.js` generated with your add-on project for both development and production bundling (assuming your project was based on any template other than the basic javascript one). You should be aware that you will need to update the [`CopyWebpackPlugin`](https://www.npmjs.com/package/copy-webpack-plugin) block in your `webpack.config.js` to ensure any new files are copied into the `dist` folder at build time. For instance, if you add new image assets into your `src` folder that your add-on is using, you would need to ensure you include the file extension in the patterns of files getting copied, or you will get a 404 indicating the images are not found. If the images were type `.png` for instance, then you could include the additional `src/*.png` line like below to ensure they are copied: ```json new CopyWebpackPlugin({ patterns: [ { from: "src/*.json", to: "[name][ext]" }, { from: "src/*.png", to: "[name][ext]" } ] }) ``` ## React The CLI supports two different [react-based templates](../getting_started/dev_tooling.md#templates), and the [code samples](../../samples.md) repository contains various add-ons built with React for you to use as a reference. ## Lit Framework The CLI provides [starter template options](../getting_started/dev_tooling.md#templates) which provide a basic setup to allow you to use the Lit framework, a lightweight library for building fast, lightweight web components. There are currently template options available for either using basic JavaScript (`swc-javascript`) with Lit or TypeScript (`swc-typescript`), preconfigured to help you get started. ## Other JavaScript and CSS libraries You should be able to use any other JavaScript or CSS libraries you might want to include in your add-ons (ie: jQuery, Bootstrap) without any issues. Just make sure you include the necessary scripts and stylesheets in your project, and ensure they are bundled correctly by your chosen bundler. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Rendition - createRendition - exporting - output title: Create Renditions description: Create Renditions. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Create Renditions Renditions are different output versions of a document made for specific purposes; for example, a high-quality PDF for printing or a smaller JPG for sharing online. ## Rendition settings Renditions are created via the [`createRendition()`](../../../references/addonsdk/app-document.md#createrenditions) method of the `addOnUISdk.app.document` object. The method accepts two parameters: 1. [`renditionOptions`](../../../references/addonsdk/app-document.md#renditionoptions): controls the page range that is meant to be exported and the file format (jpg, png, mp4 and pdf). 2. [`renditionIntent`](../../../references/addonsdk/addonsdk-constants.md) constant (optional): controls the intent of the exported content (preview, export, print). ## Export content Usually, you create renditions to allow users to download or share your content in different formats. This is a multi-step process that involves: 1. **Creating a new rendition** based on specific export configuration options via the [`createRendition()`](../../../references/addonsdk/app-document.md#createrenditions) method of the `addOnUISdk.app.document` object. 2. **Converting** the returned `blob` object into a URL via the `URL.createObjectURL()` method. 3. **Creating a download link** for the user to download the rendition, e.g., using the URL string from the previous step as the `href` attribute of an `` element. ### Example In the following snippet, we create a rendition of the current page in PNG format when the user clicks a button. We'll create a temporary anchor element to trigger the download of the rendition. ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { // Attach the rendition creation to a button click event document .querySelector("#download-button") .addEventListener("click", async () => { // Create a rendition of the current page const rendition = await addOnUISdk.app.document.createRenditions( // renditionOptions { range: addOnUISdk.constants.Range.currentPage, format: addOnUISdk.constants.RenditionFormat.png, }, // renditionIntent addOnUISdk.constants.RenditionIntent.preview ); console.log("Renditions created: ", rendition); // [{ // type: "page", // blob: { size: 16195, type: "image/png" }, // title: "", // metadata: { ... }, // }]; // Convert the blob into a URL to be consumed by an anchor element const downloadUrl = URL.createObjectURL(rendition[0].blob); // Create a temp/disposable anchor element to trigger the download const a = document.createElement("a"); a.href = downloadUrl; // Set the URL a.download = "Preview_rendition.png"; // Set the desired file name document.body.appendChild(a); // Add the anchor to the DOM a.click(); // Trigger the download document.body.removeChild(a); // Clean up URL.revokeObjectURL(downloadUrl); // Release the object URL }); }); ``` There are multiple classes that inherit from the `RenditionOptions` class, such as [`JpgRenditionOptions`](../../../references/addonsdk/app-document.md#jpgrenditionoptions), [`PngRenditionOptions`](../../../references/addonsdk/app-document.md#pngrenditionoptions), and [`PdfRenditionOptions`](../../../references/addonsdk/app-document.md#pdfrenditionoptions). Each of these classes has specific properties that can be set to control the output of the rendition. ```js const JpgRendition = await addOnUISdk.app.document.createRenditions( // JpgRenditionOptions { range: addOnUISdk.constants.Range.currentPage, format: addOnUISdk.constants.RenditionFormat.jpg, // number in the range [0, 1] quality: 0.41, // no upscaling, result depends on the original image size/ratio requestedSize: { width: 600, height: 600 }, } ); ``` ```js const pdfRendition = await addOnUISdk.app.document.createRenditions( // PdfRenditionOptions { range: addOnUISdk.constants.Range.currentPage, format: addOnUISdk.constants.RenditionFormat.pdn, bleed: { amount: 5, unit: addOnUISdk.constants.BleedUnit.mm }, } ); ``` To allow the user to download the rendition, the **"permissions"** section should include `"allow-downloads"` in the `"sandbox"` array. ```json { "testId": "cbe48204-578d-47cc-9ad4-a9aaa81dc3d3", "name": "Hello World", "version": "1.0.0", "manifestVersion": 2, "requirements": { "apps": [ { "name": "Express", "apiVersion": 1 } ], }, "entryPoints": [ { "type": "panel", "id": "panel1", "main": "index.html", "documentSandbox": "sandbox/code.js", "permissions": { "sandbox": [ "allow-popups-to-escape-sandbox", "allow-popups", "allow-downloads" 👈 👀 ] } } ] } ``` Please also check out the [export-sample add-on](/samples.md#export-sample) for a more detailed example. ## The Preview intent When the `renditionIntent` is set to `RenditionIntent.preview`, the output is created for **preview purposes only**. This means that the rendition is not meant to be downloaded or shared; for example, because the user is not on a paid Adobe Express plan and the design contains Premium content. In this case, preview renditions are used either for processing purposes (e.g., if the add-on needs to perform data analysis on the design), or to be displayed in the add-on's panel or in a new window—making sure users cannot extract the content. Please see [this page](./premium_content.md#allow-only-the-preview-of-premium-content) for more detail on handling such scenarios. When the `renditionIntent` is set to `RenditionIntent.preview`, you must add to the `manifest.json` a `"renditionPreview"` flag set to `true` in the **"requirements"** section. ```json { "testId": "cbe48204-578d-47cc-9ad4-a9aaa81dc3d3", "name": "Hello World", "version": "1.0.0", "manifestVersion": 2, "requirements": { "apps": [ { "name": "Express", "apiVersion": 1 } ], "renditionPreview": true 👈 👀 }, "entryPoints": [ // ... ] } ``` --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Metadata - Document title: Document Metadata description: Document Metadata. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Use Document Metadata ## Get the Document ID and Title Through the [Add-on UI SDK Document object](../../../references/addonsdk/app-document.md), you can retrieve some information about the current document. Currently, there are asynchronous methods that allow you to retrieve the `id()` of the document and the `title()`. Also, associated events will let you listen for when the Document ID or the Document Title have changed, respectively via the `documentIdAvailable` and `documentTitleChange` events, which you can listen for with the [`addOnUISdk.app.on()`](../../../references/addonsdk/addonsdk-app.md#on) method. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(() => { // Get the document ID const docId = await addOnUISdk.app.document.id(); // urn:aaid:sc:VA6C2:679a7c92-33ce-4320-a610-f58ccaf56aa8 // Get the document title const docTitle = await addOnUISdk.app.document.title(); // Get the document Link const docLink = await addOnUISdk.app.document.link(); console.log(`Document ID: ${docId}; Document Title: ${docTitle}`; `Document Link: ${docLink}`); // Listen for document ID change addOnUISdk.app.on("documentIdAvailable", data => { console.log(`Document ID changed to: ${data.documentId}`); }); // Listen for document title change addOnUISdk.app.on("documentTitleChange", data => { console.log(`Document title changed to: ${data.documentTitle}`); }); }); ``` Please remember that `id()`, `title()`, and `link()` are asynchronous methods and not properties of the `addOnUISdk.app.document` object. You need to call them and `await` for the promise to be resolved before using the returned value. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Drag and Drop title: Use Drag-and-Drop description: Use Drag-and-Drop. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Use Drag-and-Drop ## With local images You must invoke the [`addOnUISdk.app.enableDragToDocument()`](/references/addonsdk/addonsdk-app.md#enabledragtodocument) method for each draggable image to implement this feature. It accepts two parameters: the `HTMLElement` and an object with a `previewCallback()` that returns the image URL for preview purposes, and a `completionCallback()` that fetches the corresponding blob to finalize insertion into the document. You also need to listen for `"dragstart"` and `"dragend"` events to manage logs or other custom behaviour when the user interacts with the images. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; // Wait for the SDK to be ready before rendering elements in the DOM. addOnUISdk.ready.then(async () => { // Create the image element in the DOM. const image = document.createElement("img"); image.id = "image.jpg"; // The image is local to the add-on. image.src = "./images/image.jpg"; image.addEventListener("click", addToDocument); // Enable drag to document for the image. addOnUISdk.app.enableDragToDocument(image, { previewCallback: (element) => { // return the new URL for preview purposes return new URL(element.src); }, completionCallback: async (element) => { // return the blob for the image return [{ blob: await getBlob(element.src) }]; }, }); // Add the image to the document. document.body.appendChild(image); }); // Utility functions //Add image to the document. async function addToDocument(event) { const url = event.currentTarget.src; const blob = await getBlob(url); addOnUISdk.app.document.addImage(blob); } // Handle "dragstart" event function startDrag(eventData) { console.log("The drag event has started for", eventData.element.id); } // Handle "dragend" event function endDrag(eventData) { if (!eventData.dropCancelled) { console.log("The drag event has ended for", eventData.element.id); } else { console.log("The drag event was cancelled for", eventData.element.id); } } // Get the binary object for the image. async function getBlob(url) { return await fetch(url).then((response) => response.blob()); } ``` ## With remote images or audio To implement drag and drop with remotely hosted images, you similarly invoke `addOnUISdk.app.enableDragToDocument()`, but you fetch the resource from its remote URL. Provide a `previewCallback()` that returns the preview URL and a `completionCallback()` that retrieves the image as a blob. You can then attach the same `"dragstart"` and `"dragend"` event handlers to log or customize interactions as needed. To drag audio content, you must specify an additional `attributes` object with a `title` property. A note on how to include it is found in the following example. ### Example ```ts import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; // Enable drag support for an element function makeDraggableUsingUrl(elementId: string, previewUrl: string) { const image = document.getElementById(elementId); const dragCallbacks = { previewCallback: (image: HTMLElement) => { // Return a new URL for the remote preview return new URL(previewUrl); }, completionCallback: async (image: HTMLElement) => { // Fetch and return the image blob const imageBlob = await fetch(image.src).then((response) => response.blob() ); return [{ blob: imageBlob }]; // ⚠️ for audio content, an attributes object // with the title is mandatory. For example: // return [{ blob: audioBlob, attributes: { title: "Jazzy beats" } }]; }, }; try { addOnUISdk.app.enableDragToDocument(image, dragCallbacks); } catch (error) { console.log("Failed to enable DragToDocument:", error); } } // Log start of the drag event addOnUISdk.app.on("dragstart", (eventData: DragStartEventData) => { console.log("The drag event has started for", eventData.element); }); // Log end of the drag event addOnUISdk.app.on("dragend", (eventData: DragEndEventData) => { if (!eventData.dropCancelled) { console.log("The drag event has ended for", eventData.element); disableDragToDocument(); } else { console.log("The drag event was cancelled for", eventData.element); console.log("Cancel Reason:", eventData.dropCancelReason); } }); ``` ## Notes If the content being dragged is an animated GIF, it will be added as an animated GIF to the document, as long as it fits [the size criteria for animated GIF's](https://helpx.adobe.com/express/create-and-edit-videos/change-file-formats/import-gif-limits.html). In the event that it doesn't fit the size criteria, an error toast will be shown to the user. Since the Add-on SDK uses pointer event handlers to perform drag operations, you should ensure that you don't attach any pointer event handlers that prevent default or stop propagation. Adding those types of handlers will kill the built-in handlers and cause the events not to work. You should not attach `click` event listeners to drag-enabled elements in the capture phase, as the Add-on SDK attaches a `cancelClickEvent` handler to drag-enabled elements to ensure that the automatic click (pointer down + pointer up automatically fires a click event) doesn't fire. Adding other handlers to this same element will trigger them on drag & drop completion. Use Chrome devTools to check the handlers attached to the element and its ancestors to identify any that may be causing conflicts with drag and drop handlers. There are several [code samples](/samples.md) that implement drag and drop, including the [import-images-using-oauth](/samples.md#import-images-using-oauth) and [pix](/samples.md#pix) projects that you can reference. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Metadata - Element title: Element Metadata description: Element Metadata. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Element Metadata ## Get and Set Element Metadata Add-ons can store **private metadata** (custom data accessible only to the add-on that set it) on any node within the Express document. Currently, each node can hold up to **3 KB** of data, organized as key/value pairs where both keys and values are Strings. Additionally, there is a limit of **20 key/value pairs** per node. All nodes that inherit from the [`BaseNode`](../../../references/document-sandbox/document-apis/classes/BaseNode.md) class have a `addOnData` property that can be used to store and retrieve metadata. It is an instance of the [`AddOnData`](../../../references/document-sandbox/document-apis/classes/AddOnData.md) class, which provides methods to perform operations such as `getItem()`, `setItem()`, `removeItem()`, and `clear()`. With the `remainingQuota` property, you can check how much space is left, both in terms of `sizeInBytes` and `numKeys`, while `keys()` returns an array of the keys in use. While Document and Page metadata operate from the `addOnUISdk.app.document` object and belong to the [Add-on UI SDK](../../../references/addonsdk/index.md), Element metadata are part of the [Document Sandbox](../../../references/document-sandbox/document-apis/index.md) and are accessed through the `node.addOnData` property. ### Example ```js import { editor } from "express-document-sdk"; // Create some dummy node const text = editor.createText(); text.fullContent.text = "Hello, World!"; // Store some metadata as key/value pairs text.addOnData.setItem("originalText", "Hello, World!"); text.addOnData.setItem("date", new Date().toISOString()); // Retrieve the metadata console.log("Original text: ", text.addOnData.getItem("originalText")); // Check the remaining quota console.log("Remaining quota: ", text.addOnData.remainingQuota); // { // "sizeInBytes": 3062, // "numKeys": 19 // } // Check the keys in use console.log("Keys in use: ", text.addOnData.keys()); // ["originalText", "date"] // Remove the metadata text.addOnData.removeItem("originalText"); // clear all metadata text.addOnData.clear(); ``` Please note that the `addOnData` property is iterable with `for...of` loops, so you can use it to iterate over the key/value pairs; each pair is an array with the key as the first element and the value as the second. ```js // iterate over key/value pairs for (let pair of text.addOnData) { console.log(pair); // ['originalText', 'Hello, World!'] // ['date', '2025-01-20T11:06:19.051Z'] } ``` Alternatively, you can use the `keys()` method to get an array of all keys and then iterate over them. ```js // Iterate over all keys text.addOnData.keys().forEach((key) => { console.log(`Key: ${key}, Value: ${text.addOnData.getItem(key)}`); }); ``` ## Use Cases Per-element metadata can be useful to keep track, for example, of the original properties a node has been created with, the history of the subsequent changes made to it, or to tag some nodes in a way that is meaningful for the add-on (e.g., it's supposed to be skipped when a certain routine is launched). It can also be used to store temporary data that is not meant to be persisted. Please, refer to the SDK Reference section for [`AddOnData`](../../../references/document-sandbox/document-apis/classes/AddOnData.md) for a complete list of methods, and the [`per-element-metadata`](https://github.com/AdobeDocs/express-add-on-samples/tree/main/document-sandbox-samples/per-element-metadata) sample add-on for a demonstrative implementation. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Group - Lock - Nest title: Group Elements description: Group Elements. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Group Elements ## Create a group Groups are just like any other element in Adobe Express, very much like Text or Shapes: you must create them, and append them to the page. Interestingly, as instances of the [`GroupNode`](../../../references/document-sandbox/document-apis/classes/GroupNode.md) class, they can host other nodes in their `children` property. To create a Group, you can use the [`editor.createGroup()`](../../../references/document-sandbox/document-apis/classes/Editor.md#creategroup) method. ### Example ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Create some Text const greeting = editor.createText(); greeting.fullContent.text = "Hiya!"; greeting.translation = { x: 100, y: 50 }; // Create some other Text const saluto = editor.createText(); saluto.fullContent.text = "Ciao!"; saluto.translation = { x: 100, y: 150 }; // Create a Group 👈 const greetingsGroup = editor.createGroup(); greetingsGroup.translation = { x: 100, y: 100 }; // Append the Text nodes to the Group 👈 greetingsGroup.children.append(greeting, saluto); // Append the Group to the page 👈 editor.context.insertionParent.children.append(greetingsGroup); ``` Group append order You can append the Group to the page and then append the Text nodes to the Group, or the other way around. The order doesn't matter, as long as the Group hits the page at some point. Please note that the Text nodes in the example above haven't been appended to the page before getting into the group—they're invisible, until they're part of the Group and the Group itself ends on the page. ## Nest groups Groups can be nested, meaning that you can have a Group inside another Group; just create the needed Group nodes and `append()` elements to their `children` property. ### Example ```js // sandbox/code.js // Create three different Text nodes const greeting = editor.createText(); greeting.fullContent.text = "Hiya!"; const saluto = editor.createText(); saluto.fullContent.text = "Ciao!"; const salutation = editor.createText(); salutation.fullContent.text = "Salut!"; // Create an inner Group with the first two Text nodes const innerGroup = editor.createGroup(); innerGroup.children.append(greeting, saluto); // Create an outer Group with the inner Group and the third Text node const outerGroup = editor.createGroup(); outerGroup.children.append(innerGroup, salutation); editor.context.insertionParent.children.append(outerGroup); ``` This code results in the following grouping: ```txt Outer Group ├── Inner Group │ ├── Text Node: "Hiya!" │ └─ Text Node: "Ciao!" │ └── Text Node: "Salut!" ``` In Adobe Express, **Groups must contain at least two elements** to be valid—either other Groups or nodes. It's not possible to nest a single element within a Group, like for example, in Adobe Photoshop with Layers and Layer Sets. ## Element order As you would intuitively expect, the order in which you append elements to a Group matters. The last element you append will be on top of the others, and the first one will be at the bottom. As follows, a simple Square factory function creates and append a shape to the page, passing an option object with the `size` and `color` (grayscale) and `offset` properties. We'll be using it to test element order in a Group. ```js // sandbox/code.js function squareFactory({ size, color, offset = 0 }) { const rectangle = editor.createRectangle(); // Define rectangle dimensions. rectangle.width = size; rectangle.height = size; const insertionParent = editor.context.currentPage; console.log(insertionParent.width, insertionParent.height); rectangle.setPositionInParent( { x: insertionParent.width / 2 + offset, y: insertionParent.height / 2 + offset, }, { x: rectangle.width / 2, y: rectangle.height / 2 } ); // Define rectangle color. rectangle.fill = editor.makeColorFill( colorUtils.fromRGB(color, color, color, 1) ); return rectangle; } ``` ### Example: last element on top Creating a Group with two squares; the lighter one is added last, and it will be on top of the darker one. ```js // sandbox/code.js const s1 = squareFactory({ size: 50, color: 0.5 }); const s2 = squareFactory({ size: 50, color: 0.7, offset: 10 }); const group = editor.createGroup(); group.children.append(s1, s2); editor.context.insertionParent.children.append(group); ``` ![Grouping elements](./images/groups_above.png) ### Example: re-ordering elements It's possible to re-order the elements in the Group by using the `children` property and the `moveAfter()` or `moveBefore()` method. ```js // sandbox/code.js const s1 = squareFactory({ size: 50, color: 0.5 }); const s2 = squareFactory({ size: 50, color: 0.7, offset: 10 }); const group = editor.createGroup(); group.children.append(s1, s2); // s2 is on top of s1, as it's been added last // Moves s1 after (on top of) s2 group.children.moveAfter(s1, s2); editor.context.insertionParent.children.append(group); ``` ![Grouping elements](./images/groups_below.png) ### Example: addding elements in a specific order Similarly, it's possible to determine the elements insertion order with `insertAfter()` and `insertBefore()`. ```js // sandbox/code.js const s1 = squareFactory({ size: 50, color: 0.5 }); const s2 = squareFactory({ size: 50, color: 0.7, offset: 10 }); const s3 = squareFactory({ size: 50, color: 0.9, offset: 20 }); const group = editor.createGroup(); group.children.append(s1, s2); // s2 is on top of s1, as it's been added last // Inserts s3 after s1 (in the middle) group.children.insertAfter(s3, s1); editor.context.insertionParent.children.append(group); ``` ![Grouping elements](./images/groups_middle.png) ## Move elements out of a Group To move an element out of one Group (the source) and into another (the target), you can use the `children.append()` method of the target, passing the source element you want to move. It's no different from appending a new element to a Group, you just need to reference the source element regardless of where it is. ### Example ```js // sandbox/code.js const s1 = squareFactory({ size: 50, color: 0.1 }); const s2 = squareFactory({ size: 50, color: 0.3, offset: 10 }); const s3 = squareFactory({ size: 50, color: 0.5, offset: 20 }); const s4 = squareFactory({ size: 50, color: 0.7, offset: 30 }); const s5 = squareFactory({ size: 50, color: 0.9, offset: 40 }); const group1 = editor.createGroup(); const group2 = editor.createGroup(); // Grouping elements in two different groups group1.children.append(s1, s2, s3); group2.children.append(s4, s5); editor.context.insertionParent.children.append(group1); editor.context.insertionParent.children.append(group2); // Moves s1 into group2 group2.children.append(s1); // 👆 target group 👆 source element ``` ## Remove elements To remove an element from a Group, you can use the `remove()` method on the `children` property, which effectively also deletes the element from the document. ### Example ```js // sandbox/code.js const s1 = squareFactory({ size: 50, color: 0.5 }); const s2 = squareFactory({ size: 50, color: 0.7, offset: 10 }); const s3 = squareFactory({ size: 50, color: 0.9, offset: 20 }); const group = editor.createGroup(); group.children.append(s1, s2, s3); editor.context.insertionParent.children.append(group); // Removes s2 from the Group and from the page! group.children.remove(s2); ``` --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Storage - clientStorage title: Store Data description: Store Data. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Store Data ## Use the clientStorage API Instead of relying solely on server-side data, you can use the **asynchronous** `clientStorage` API to store and retrieve data locally on the client-side. This can be useful for caching images, saving user preferences, or other scenarios where you want to avoid making repeated server requests. Each add-on can store up to **10MB of data as key-value pairs**; supported values are not limited to strings, but also include objects, arrays, numbers, booleans, `null`, `undefined` and `Uint8Array`. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; let store; addOnUISdk.ready.then(async () => { store = addOnUISdk.instance.clientStorage; } /** * Store item */ async function setItem(item: string, isComplete: boolean) { await store.setItem(item, isComplete); todoItemInput.value = ""; } /** * Log all storage item values */ async function displayAllItems() { const todoItems = await store.keys(); todoItems.forEach(async (item: string) => { const itemValue = await store.getItem(item); console.log("Key: " + item + " value: " + itemValue); }); } ``` ## Use Cases Local data storage can be useful in many scenarios, such as when you need to cache data from server requests, store user UI preferences, pre-populate fields on load, or save temporary data. The fact that `clientStorage` support multiple data types makes it a more versatile tool to use compared to the Browser's `localStorage`. Please, refer to the [SDK Reference section for clientStorage](/references/addonsdk/instance-clientStorage/) for a complete list of methods, and the [use-client-storage sample add-on](/samples.md#use-client-storage) for more details. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Dialogs - Modal dialogs - showModalDialog title: Use Modal Dialogs description: Use Modal Dialogs. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Use Modal Dialogs When you need to pop up a dialog to show a certain message, such as an informational, warning, or error message, you can use a modal dialog. Below are some examples of the different types. Also, check out the SDK references for details on how to [show](/references/addonsdk/addonsdk-app.md#showmodaldialog) or [programmatically close a dialog](/references/addonsdk/runtime-dialog.md#close), as well as the [dialog add-on sample](/samples.md#dialog-add-on) for more details. ## Simple Modal Dialog You can show a dialog with the [`addOnUISdk.app.showModalDialog()`](../../../references/addonsdk/addonsdk-app.md#showmodaldialog) method, which accepts an options object containing the `variant`, `title`, `description` and, optionally, `buttonLabels`. The returned result object from a dialog will contain the `buttonType` clicked. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(() => { // Utility function to show a confirmation dialog async function showConfirmDialog() { try { // Dialog Settings const dialogOptions = { // Available variants: // confirmation, information, warning, // destructive, error, input, custom variant: "confirmation", title: "Enable smart Filters", description: "Smart filters are editable filters.", // Available button labels: primary, secondary, cancel buttonLabels: { primary: "Enable", cancel: "Cancel" }, }; // Show the dialog const result = await addOnUISdk.app.showModalDialog(dialogOptions); // Log the button type clicked, return either "primary" or "cancel" console.log("Button type clicked " + result.buttonType); } catch (error) { console.log("Error showing modal dialog:", error); } } // Call the function to show the dialog showConfirmDialog(); }); ``` ### Input Modal Dialog A dialog of variant `input` allows you to accept input from the user. The construction of the dialog is similar to the previous example, but with an additional [`field`](../../../references/addonsdk/addonsdk-app.md#field) object that defines the input field and has a `label`, `placeholder` and `fieldType` properties. In addition to the `buttonType`, the `fieldValue` is returned in the result object of the [`addOnUISdk.app.showModalDialog()`](../../../references/addonsdk/addonsdk-app.md#showmodaldialog) method. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(() => { // Utility function to show an input dialog async function showInputDialog() { try { // Dialog Settings const inputDialogOptions = { variant: "input", // 👈 title: "Please enter your key", description: "Your API key", buttonLabels: { cancel: "Cancel" }, field: { // 👈 label: "API Key", placeholder: "Enter API key", fieldType: "text", }, }; // Show the dialog const inputDialogResult = await addOnUISdk.app.showModalDialog( inputDialogOptions ); if (inputDialogResultwi.buttonType === "primary") { // returns the input the user entered if they didn't cancel console.log("Field value", inputDialogResult.fieldValue); // 👈 } } catch (error) { console.log("Error showing modal dialog:", error); } } // Call the function to show the dialog showInputDialog(); }); ``` ## Custom Dialog If you need to show a dialog with custom content, you can use the `custom` variant. This allows you to define the content in a separate source file (e.g., a `dialog.html`) and specify the container's size and title. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(() => { // Utility function to show a custom dialog async function showCustomDialog() { try { // Dialog Settings const customDialogOptions = { variant: "custom", title: "Custom Modal", src: "dialog.html", // use content from this html file size: { width: 600, height: 400 }, }; // Show the dialog const customDialogResult = await addOnUISdk.app.showModalDialog( customDialogOptions ); // Log the result object console.log("Custom dialog result " + customDialogResult.result); } catch (error) { console.log("Error showing modal dialog:", error); } } // Call the function to show the dialog showCustomDialog(); }); ``` Inside the custom dialog's HTML file, you can use the [`addOnUISdk.instance.runtime.dialog`](../../../references/addonsdk/runtime-dialog.md) object, especially its `close()` method, to programmatically close the dialog and set an optional return value. ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; // Wait for the SDK to be ready await addOnUISdk.ready; closeButton.onsubmit = () => { // User canceled the operation, close the dialog with no result addOnUISdk.instance.runtime.dialog.close(); }; createButton.onsubmit = () => { // return an object, to be captured in the result object addOnUISdk.instance.runtime.dialog.close({ selectedDesign: "grid-layout", }); }; ``` ## Use Cases Modals are versatile tools suitable for a wide range of scenarios. They can display simple information or warning pop-ups when you need users to confirm an action or provide input. Additionally, modals can present more complex content, such as custom dialogs that initiate the payment process for accessing add-on's premium features. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Add-on Manifest - Oauth - Authentication - Authorization title: Use OAuth 2.0 description: Understand how to implement OAuth 2.0 authentication and authorization flows, including login, logout, and setup examples. contributors: - https://github.com/hollyschinsky - https://github.com/undavide --- # Use OAuth 2.0 Implementing an OAuth 2.0 authorization flow, enabling users to authenticate and log in using their existing accounts from third-party services. A typical use case would be to use assets stored in different services. Here, you will find instructions on how to set it up and an implementation example. Check also the [SDK Reference OAuth section](https://developer.adobe.com/express/add-ons/docs/references/addonsdk/app-oauth/) for more options and details and the [import-images-using-oauth](/samples/#import-images-using-oauth) sample add-on for more advanced usage. ### Login and Logout flows Both login and logout flows are equally important, and developers should ensure that the add-on's UI provides functionality for both actions. Authorization should persist across sessions so users don't have to log in with their credentials every time they use the add-on. The token's lifespan is at the discretion of the OAuth provider; the token itself can be stored remotely (e.g., mapping its UUID in the add-on's local storage) or directly in the local storage (easier but less secure). ### Setup The OAuth APIs can be used to obtain the authorization "code" from any OAuth 2.0 provider supporting the Code Exchange authorization workflow. You will need to go through some setup steps with the provider you want to use OAuth with first. Here are the steps to get started: 1. Log in to the OAuth provider's website and create an application (for example, Dropbox). This must be a web application, and if an option of SPA (Single Page Application) is listed, select it. 2. As an input to the "Redirect URIs" field, add: https://new.express.adobe.com/static/oauth-redirect.html. 3. Fill out other details as necessary and save the form. A client Id / application Id / application key (this differs on different OAuth providers) will be generated. Make note of it as you will need it in your add-on code. 4. Next, update your add-on `manifest.json` file with the hostname of the OAuth provider's authorization URL. **NOTE:** When using multiple providers, all hostnames must be provided. For example, if the add-on uses two OAuth providers (`"login.microsoftonline.com"` and `"www.dropbox.com"`), the `manifest.json` should contain both of them, as shown below: ```json { "id": "", "name": "", "version": "1.0.0", "manifestVersion": 1, "requirements": { "apps": ["Express"] }, "entryPoints": [ { "type": "panel", "id": "panel1", "label": { "default": "" }, "main": "index.html", "permissions": { "oauth": ["login.microsoftonline.com", "www.dropbox.com"] } } ] } ``` ### Example Once you complete the setup, you can use the following code snippet as an example of how to perform the OAuth exchange to retrieve an access token. The [code samples](/samples.md) also include several examples of implementing OAuth 2.0 workflows, which you can refer to. Additionally, you'll find the [OAuthUtils.js](https://github.com/AdobeDocs/express-add-on-samples/blob/main/samples/import-images-using-oauth/src/utils/OAuthUtils.js) module, referenced below, and we recommend utilizing this module to facilitate your own OAuth implementation. For further details on the OAuth workflows, be sure to explore the [SDK References](https://developer.adobe.com/express/add-ons/docs/references/addonsdk/app-oauth). ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; const DROPBOX_AUTHORIZATION_URL = "https://www.dropbox.com/oauth2/authorize"; const DROPBOX_TOKEN_URL = "https://api.dropboxapi.com/oauth2/token"; const DROPBOX_CLIENT_ID = ""; const DROPBOX_SCOPE = ""; const ONEDRIVE_AUTHORIZATION_URL = "https://login.microsoftonline.com//oauth2/v2.0/authorize"; const ONEDRIVE_TOKEN_URL = "https://login.microsoftonline.com//oauth2/v2.0/token"; const ONEDRIVE_CLIENT_ID = ""; const ONEDRIVE_SCOPE = ""; const OWN_REDIRECT_URI = ""; addOnUISdk.ready.then(() => { // 'oauthUtils' is a helper javascript module (included with // the OAuth sample) which provides utility functions to: // 1. Generate the 'code_challenge' and 'code_verifier' parameters // that are essential in the OAuth 2.0 workflow. // generateChallenge() // 2. Generate an 'access_token' and a 'refresh_token' using the // 'code' and 'redirectUri' received on successful authorization. // generateAccessToken() // Get an always valid 'access_token'. // 3. getAccessToken() const challenge = await oauthUtils.generateChallenge(); await authorize(challenge); }); function authorize(challenge) { // Trigger the OAuth 2.0 based authorization which opens up a // sign-in window for the user and returns an authorization code // which can be used to obtain an access_token. const { id, code, redirectUri, result } = await addOnUISdk.app.oauth.authorize({ authorizationUrl: DROPBOX_AUTHORIZATION_URL, clientId: DROPBOX_CLIENT_ID, scope: DROPBOX_SCOPE, codeChallenge: challenge.codeChallenge }); const { status, description } = result; if (status !== "SUCCESS") { throw new Error(`Status: ${status} | Description: ${description}`); } // Generate the access_token which can be used to verify the identity // of the user and grant them access to the requested resource. await oauthUtils.generateAccessToken({ id, clientId: DROPBOX_CLIENT_ID, codeVerifier: challenge.codeVerifier, code, tokenUrl: DROPBOX_TOKEN_URL, redirectUri }); const accessToken = await oauthUtils.getAccessToken(id); } ``` ## Use Cases OAuth is ideal for scenarios where users need to connect their accounts to access personalized features, sync data, or enable functionalities like cloud storage. You can also use it to authenticate users and authorize them to access your add-on's premium users, after they've subscribed to a paid plan. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Metadata - Page title: Page Metadata description: Page Metadata. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Page Metadata ## Get the Page Metadata If you want to retrieve metadata for pages in the document, use the [`getPagesMetadata()`](../../../references/addonsdk/app-document.md#getpagesmetadata) method in the `addOnUISdk.app.document` object. The method expects an object with a `range` and optional `pageIds` properties. The `range` property is one of the available [`Range`](../../../references/addonsdk/addonsdk-constants.md) enumerables, either `currentPage`, `entireDocument`, or `specificPages`. If you choose `specificPages`, you must provide an array of page IDs in the `pageIds` property. The returned value is always an array of [`PageMetadata`](../../../references/addonsdk/app-document.md#pagemetadata) objects. ### Single Page Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(() => { const page = await addOnUISdk.app.document.getPagesMetadata({ range: addOnUISdk.constants.Range.currentPage }); console.log("Current page metadata: ", page); // 👈 always returns an array // [ // { // "id": "01d7093d-96d1-4d6a-981b-dc365343e17c", // "size": { "width": 1080, "height": 1080 }, // "title": "First", // "hasPremiumContent": false, // "hasTemporalContent": false, // "pixelsPerInch": 96 // }, // ] }); ``` ### Page Range Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(() => { const pages = await addOnUISdk.app.document.getPagesMetadata({ range: addOnUISdk.constants.Range.specificPages, pageIds: [ // 👈 "7477a5e7-02b2-4b8d-9bf9-f09ef6f8b9fc", // 👈 "d45ba3fc-a3df-4a87-80a5-655e5f8f0f96" // 👈 ] // 👈 }); console.log("Current page metadata: ", pages); // [ // { // "id": "01d7093d-96d1-4d6a-981b-dc365343e17c", // "size": { "width": 1080, "height": 1080 }, // "title": "First", // "hasPremiumContent": false, // "hasTemporalContent": false, // "pixelsPerInch": 96 // }, // { // "id": "8d5b1f9a-7289-4590-9ee4-a15a731698ed", // "size": { "width": 1080, "height": 1080 }, // "title": "Second", // "hasPremiumContent": false, // "hasTemporalContent": false, // "pixelsPerInch": 96 // } // ] }); ``` ## Use Cases Page metadata can be used to determine the size of the page, the title, and whether it contains temporal content (videos and animations). Tge `hasPremiumContent` property is particularly helpful when dealing with the rendition of [premium content](./premium_content.md)—for instance, when the user is not authorized to export/download assets that are available only to paid subscribers. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Position - Translation - Rotation - Bounding Box - Bounds title: Position Elements description: Position Elements. contributors: - https://github.com/undavide --- # Position Elements ## Move and Rotate Elements Let's use this simple Rectangle to demonstrate how to move and rotate elements in Adobe Express. ```js // sandbox/code.js import { editor } from "express-document-sdk"; const rect = editor.createRectangle(); rect.width = 200; rect.height = 100; editor.context.insertionParent.children.append(rect); ``` ### Example: Translation Elements can be moved around by setting their `translation` property, which is an object with `x` and `y` properties defined in the element's parent coordinates. ```js // Move the rectangle 100px to the right and 50px down rect.translation = { x: 50, y: 100 }; ``` ![Bounding Box](./images/position_bounding-box.png) Mind the origin of the axes! The zero point of the coordinate system is at the top-left corner of the parent container, with the x-axis pointing to the right and the y-axis pointing down. So, to move a shape to the right, you increase its `x` value; to move it down, you increase its `y` value. A more advanced way to move shapes is by using the `setPositionInParent()` method, which takes two arguments: the **desired position** and the **reference point** of the shape. ```js // Move the rectangle to the center of the artboard const artboard = editor.context.currentPage.artboards.first; rect.setPositionInParent( // Where to move the shape: the artboard center { x: artboard.width / 2, y: artboard.height / 2 }, // Reference point of the shape { x: rect.width / 2, , y: rect.hwight / 2 } // the rectangle's center 👈 ); ``` ### Example: Rotation You cannot rotate shapes by setting their `rotation` property, though; it's read-only, like `rotationInScreen`, which takes into account any cumulative rotations from the node's parent container. To rotate a shape, you must use `setRotationInParent()` instead, passing the **desired angle** in degrees, and the **point to rotate around**, in the shape's local coordinates. The `{ x: 0, y: 0 }` point in the example below is the shape's top-left corner. ```js // sitting on the top-left corner rect.translation = { x: 50, y: 100 }; // rotate 15 degrees around the rectangle's top-left corner rect.setRotationInParent(15, { x: 0, y: 0 }); ``` ![Bounding Box](./images/position_rotate.png) Please note, rotation and translation are never additive, meaning that each time you set a new value, it replaces the previous one. ## Get Element Bounds By definition, the bounds of an element (or its _bounding box_) are the smallest rectangle that contains the element. The bounds are represented by a [Rect](../../../references/document-sandbox/document-apis/interfaces/Rect.md) object, which has a `x`, `y`, `width`, and `height` properties. There are two types of bounds, though, depending on the coordinate space in which they are calculated: - **Local bounds**: The bounding box of an element in its own coordinate space (which may be shifted or rotated relative to its parent). - **Parent bounds**: The bounding box of an element in its parent's coordinate space. ### Example: Local and Parent's Bounds Let's see how to get the bounds of a rotated rectangle in both local and parent coordinates; since the rectangle is rotated, the two bounding boxes will differ. ```js // sandbox/code.js import { editor } from "express-document-sdk"; const rect = editor.createRectangle(); rect.width = 200; rect.height = 100; rect.translation = { x: 50, y: 100 }; rect.setRotationInParent(15, { x: 0, y: 0 }); console.log(rect.boundsLocal); // {x: 0, y: 0, width: 200, height: 100} 👈 console.log("boundsInParent", rect.boundsInParent); // {x: 24.2, y: 100, width: 219.0, height: 148.3} 👈 editor.context.insertionParent.children.append(rect); ``` ![Rotated Bounding Box](./images/position_rotated-bounding-box.png) In case you need it, there's a handy `centerPointLocal` property of the element that returns the center point of the `boundsLocal` box. ## Account for Parent Transformations The one Rectangle on the canvas, as we've used here for demonstration purposes, is but a simplified example; when dealing with real-world scenarios, the element's parent container may have a different rotation or translation, which affects the element's position and global angle. The Document Sandbox API provides some handy features that help you account for the parent's transformations. ### Example: Converting between coordinate spaces In the following example, we'll create and group two rectangles; the group itself will be rotated and translated. We'll then find out the position of the second rectangle in the artboard's axis. ```js // sandbox/code.js import { editor } from "express-document-sdk"; const rect1 = editor.createRectangle(); rect1.width = 200; rect1.height = 200; rect1.fill = editor.makeColorFill(colorUtils.fromHex("#ED672F")); rect1.translation = { x: 0, y: 0 }; const rect2 = editor.createRectangle(); rect2.width = 200; rect2.height = 50; rect2.fill = editor.makeColorFill(colorUtils.fromHex("#F8CE94")); rect2.translation = { x: 150, y: 150 }; // Group the rectangles const group = editor.createGroup(); group.children.append(rect1, rect2); // Rotate the group group.setRotationInParent(10, { x: 0, y: 0 }); // Translate the group group.translation = { x: 50, y: 50 }; // Add the group to the artboard editor.context.currentPage.artboards.first.children.append(group); ``` ![](./images/position_parent.png) Where does the second rectangle sit in the artboard's coordinate system? To find out, we can use the [`localPointInNode()`](../../../references/document-sandbox/document-apis/classes/FillableNode.md#localpointinnode) method, which converts a point from the local coordinate space of the element to the parent's coordinate space. ```js //... console.log( rect2.localPointInNode( rect2.centerPointLocal, // the point to convert (the rectangle's center) editor.context.currentPage.artboards.first // the node to convert to (the artboard) ) ); // {x: 265.8, y: 265.8} 👈 ``` ### Example: Global Rotation Similarly, you can calculate the global rotation of an element with the `rotationInScreen` property. ```js //... console.log(rect2.rotationInScreen); // 10 👈 ``` --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Premium - Content - isPremiumUser - startPremiumUpgradeIfFreeUser title: Manage Premium Content description: Manage Premium Content. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- import '/src/styles.css' # Manage Premium Content When exporting Adobe Express documents, you should ensure proper handling of Premium content. Let's go through all the available options to manage the rendition of Premium content in case your add-on allows users to export or download it. ## Show a Premium Content error with the "Upgrade" option One way to handle premium content is to display a warning message when the user is not entitled to export or download it, and include a button to allow them to upgrade. Please note that you can detect in advance if the user is entitled to Premium content (via [`isPremiumUser()`](../../../references/addonsdk/app-currentUser.md#isPremiumUser)), and whether the page contains Premium content (via [`hasPremiumContent`](/references/addonsdk/app-document.md#pagemetadata)) in the first place. A try/catch block intercepting the `"USER_NOT_ENTITLED_TO_PREMIUM_CONTENT"` string in the error message as the primary way to deal with it is no longer recommended. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; const { app, constants } = addOnUISdk; const { ButtonType, Range, RenditionFormat } = constants; const showPremiumContentError = async () => { // Show a modal dialog with an error message const { buttonType } = await window.addOnUISdk.app.showModalDialog({ variant: "error", title: "Export failed", description: "Sorry, we were not able to export your design. " + "Some assets are only included in the Premium plan." + "Try replacing with something else " + "or upgrading Adobe Express to a Premium plan.", buttonLabels: { secondary: "Upgrade" }, }); // The User is still not premium if (buttonType === ButtonType.cancel) return false; if (buttonType === ButtonType.secondary) { // Original flow (don't use anymore) // ❌ window.open( // "https://www.adobe.com/go/express_addons_pricing", // "_blank" // ); // 👇 Use startPremiumUpgradeIfFreeUser() instead const hasUpgradedToPremium = await app.startPremiumUpgradeIfFreeUser(); return hasUpgradedToPremium; } }; // Check if the page range is safe to export const isRangeSafeToExport = async (range) => { const userIsPremium = await app.currentUser.isPremiumUser(); const pages = await app.document.getPagesMetadata({ range }); const containsPremiumContent = pages.some((page) => page.hasPremiumContent); return (containsPremiumContent && userIsPremium) || !containsPremiumContent; }; const exportDocument = async () => { // 👇 Testing purposes only! 👇 app.devFlags.simulateFreeUser = true; // 👈 Remove in production! let isSafeToExport = await isRangeSafeToExport(Range.entireDocument); if (!isSafeToExport) { const isNowPremiumUser = await showPremiumContentError(); isSafeToExport = isNowPremiumUser; } if (isSafeToExport) { try { const renditions = await app.document.createRenditions({ range: Range.entireDocument, format: RenditionFormat.png, }); renditions.forEach((rendition) => { // do your thing w/ the renditions }); } catch (err) { // did someone just add premium content in the split second // between our original check? did the user just downgrade? if (err.message?.includes("USER_NOT_ENTITLED_TO_PREMIUM_CONTENT")) { return await exportDocument(); // try again } } } }; document.querySelector("#export").onclick = exportDocument; ``` Please note that [`startPremiumUpgradeIfFreeUser()`](../../../references/addonsdk/addonsdk-app.md#startpremiumupgradeiffreeuser) allows a more streamlined user experience for upgrading to premium content, compared to the older method of redirecting to the Adobe Express pricing page, which is now deprecated. ## Provide visual cues in the UI Alternatively, you can provide visual cues directly in the add-on UI to show that users are not entitled to export/download premium content. This can be done in various ways, for instance, by disabling the export/download button, replacing it with an upgrade button, or appending a brief explanation, tooltip, or icon. This would inform users upfront that they are not entitled to export/download premium content, preventing them from facing the warning popup after attempting to do so. ## Allow only the preview of Premium Content As mentioned in [Creating Renditions](./create_renditions.md), you can allow users to preview Premium content within the iframe by setting the `renditionIntent` to the constant [`RenditionIntent.preview`](../../../references/addonsdk/addonsdk-constants.md) as the second parameter of the [`addOnUISdk.app.document.createRendition()`](../../../references/addonsdk/app-document.md#createrenditions) method. Remember to also add the [`"renditionPreview"`](./create_renditions.md#the-preview-intent) permission to your add-on's `manifest.json` file. Prevent previews download Your add-on must not allow these previewed images to be downloaded or persisted on a backend (for any longer than necessary to serve the result back to the user). To that end, be sure that users cannot: - **right-click -> save as**: To prevent this, reject the `contextmenu` event. - **drag the image off the panel**: To prevent this, you can reject the `dragstart` event. **Note:** These behaviors are enabled by default if you use an `` tag. If you apply the image using `background-image` CSS, these behaviors aren't added. ### Example #### JavaScript ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; // Wait for the SDK to be ready await addOnUISdk.ready; // Display preview of all pages in the AddOn UI async function displayPreview() { try { const renditionOptions = { range: addOnUISdk.constants.Range.entireDocument, format: addOnUISdk.constants.RenditionFormat.png, backgroundColor: 0x7faa77ff, }; const renditions = await addOnUISdk.app.document.createRenditions( renditionOptions, addOnUISdk.constants.RenditionIntent.preview ); renditions.forEach((rendition) => { const image = document.createElement("img"); image.src = URL.createObjectURL(rendition.blob); document.body.appendChild(image); }); } catch (error) { console.log("Failed to create renditions:", error); } } ``` #### TypeScript ```ts import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; // Wait for the SDK to be ready await addOnUISdk.ready; // Display preview of all pages in the AddOn UI async function displayPreview() { try { const renditionOptions: PngRenditionOptions = { range: addOnUISdk.constants.Range.entireDocument, format: addOnUISdk.constants.RenditionFormat.png, backgroundColor: 0x7faa77ff, }; const renditions = await addOnUISdk.app.document.createRenditions( renditionOptions, addOnUISdk.constants.RenditionIntent.preview ); renditions.forEach((rendition) => { const image = document.createElement("img"); image.src = URL.createObjectURL(rendition.blob); document.body.appendChild(image); }); } catch (error) { console.log("Failed to create renditions:", error); } } ``` --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Theme - Locale title: Theme & Locale description: Theme & Locale. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Theme & Locale ## Detecting Theme It can be useful to know what theme is currently set in Adobe Express, for instance to load a specific set of CSS in your add-on to keep its UI in sync, also in case the user changes it. Currently, Adobe Express supports a "light" theme only, although a "dark" theme is planned for future releases. The current theme is available in the [`addOnUISdk.app.ui.theme`](../../../references/addonsdk/app-ui.md#theme) property. Changes can be detected by listening to the `themechange` event on the [`addOnUISdk.app`](../../../references/addonsdk/addonsdk-app.md) object. The event will provide the new theme in the `data.theme` property. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { // Get the current theme console.log(addOnUISdk.app.ui.theme); // "light" // Listen to theme changes addOnUISdk.app.on("themechange", (data) => { // data theme will be either "light" or "dark" console.log("The theme is now", data.theme); // ... // Apply the new theme to your add-on UI }); }); ``` ## Detecting Locale, Supported Locales, and Format It's possible to retrieve the user's current locale, the list of supported locales, and detect when the locale changes (e.g., to set the language in your add-on accordingly). You can do so with the [`addOnUISdk.app.ui` object](/references/addonsdk/app-ui.md#locale) in the add-on SDK. Similarly, you can get and detect a change in the Format used display dates, times, numbers, etc. A simple example is shown below. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(() => { // Get the currently supported locales console.log(addOnUISdk.app.ui.locales); // ["bn-IN", "cy-GB", ...] // Get the current locale console.log(addOnUISdk.app.ui.locale); // "en-US" // Get the current format console.log(addOnUISdk.app.ui.format); // "en-US" // Listen to locale changes addOnUISdk.app.on("localechange", (data) => { console.log("The locale is now", data.locale); // "fr-FR" // ... }); // Listen to format changes addOnUISdk.app.on("formatchange", (data) => { console.log("The format is now", data.format); // "fr-FR" // ... }); }); ``` --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Audio - addAudio title: Use Audio description: Use Audio. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Use Audio ## Import audio into the page Similarly to Images and Video, you can add Audio to the page using the [`addAudio()`](../../../references/addonsdk/app-document.md#addaudio) method of the `addOnUISdk.app.document` object, which expects a `Blob` object as the first argument, and a [`MediaAttribute`](../../../references/addonsdk/app-document.md#mediaattributes) object with the audio's title (mandatory) and author (optional) as the second. ### Example ```js // sandbox/code.js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { try { const audioUrl = "https://www.nasa.gov/wp-content/uploads/static/history/alsj/a11/a11a1021133-3114.mp3"; const audio = await fetch(audioUrl); const audioBlob = await audio.blob(); await addOnUISdk.app.document.addAudio( audioBlob, // 👈 Blob object { title: "Apollo 11 - Lunar Landing", author: "NASA", } ); } catch (e) { console.error("Failed to add the audio", e); } }); ``` Please note that you can use `fetch()` also to get videos that are local to the add-on; in this case, you can use paths relative to the add-on's root. ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { try { // 👇 Local audio const audioUrl = "https://www.nasa.gov/wp-content/uploads/static/history/alsj/a11/a11a1021133-3114.mp3"; const audio = await fetch(audioUrl); // ... same as before ``` Audio file requirements Please refer to [this page](https://helpx.adobe.com/au/express/create-and-edit-videos/change-file-formats/video-quick-actions-requirements.html) to know more about the file formats support and size/length requirements for audio. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Color - Fill Color - Stroke Color - Text Color - HEX Color - RGB Color - Color Picker title: Use Color description: Use Color. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Use Color ## Create colors Colors in Adobe Express are created as instances of the [`Color`](../../../references/document-sandbox/document-apis/interfaces/Color.md) class: objects with `red`, `green`, `blue`, and `alpha` (optional) values in the range from 0 to 1. The `alpha` value represents the opacity of the color, with 0 being fully transparent and 1 fully opaque. The entrypoint for creating colors is the [`colorUtils`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md) class, imported from the `"express-document-sdk"`, so we're talking about [Document APIs](../../../references/document-sandbox/document-apis/index.md) here. Especially the static [`fromRGB()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#fromrgb) and [`fromHex()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#fromhex) methods. ```js // sandbox/code.js import { editor, colorUtils } from "express-document-sdk"; // Alpha is optional, defaults to 1 const red = colorUtils.fromRGB(1, 0, 0); const green = colorUtils.fromHex("#00FF00"); // With alpha const feldgrau = colorUtils.fromRGB(0.28, 0.32, 0.39, 0.5); // 50% opacity const heliotrope = colorUtils.fromHex("#C768F780"); // 50% opacity ``` In case you need it, you can also convert a color to a HEX string using the [`toHex()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#tohex) method. Please note that the alpha value is always included in the output string. ```js const red = colorUtils.fromRGB(1, 0, 0); const redHex = colorUtils.toHex(red); // #FF0000FF ``` ## Apply colors You can directly set the `color` property of a Text node via [`applyCharacterStyles()`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#applycharacterstyles): ### Example: Text color ```js // sandbox/code.js import { editor, colorUtils } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; // Apply character styles to the first three letters textNode.fullContent.applyCharacterStyles( { color: colorUtils.fromHex("#E1A141") }, // 👈 { start: 0, length: 3 } ); ``` See the [Use Text](./use_text.md) page for more examples. ### Example: Fill and Stroke colors Colors are not directly applied, instead, to shapes; more generally, they are used to create [`Fill`](../../../references/document-sandbox/document-apis/interfaces/Fill.md) and [`Stroke`](../../../references/document-sandbox/document-apis/interfaces/Stroke.md) objects with the [`editor.makeColorFill()`](../../../references/document-sandbox/document-apis/classes/Editor.md#makecolorfill) and [`editor.makeStroke()`](../../../references/document-sandbox/document-apis/classes/Editor.md#makestroke) methods, respectively, that you can then apply to [`Fillable`](../../../references/document-sandbox/document-apis/classes/FillableNode.md) and [`Strokable`](../../../references/document-sandbox/document-apis/classes/StrokableNode.md) nodes. If you're confused, worry not! This is the wondrous word of object oriented programming. The following example should clarify things: ```js // sandbox/code.js import { editor, colorUtils } from "express-document-sdk"; // Create the shape const ellipse = editor.createEllipse(); ellipse.width = 100; ellipse.height = 50; ellipse.translation = { x: 50, y: 50 }; // Generate the needed colors const innerColor = colorUtils.fromHex("#A38AF0"); const outerColor = colorUtils.fromHex("#2ACfA9"); // Make the colorFill and the Stroke const innerColorFill = editor.makeColorFill(innerColor); const outerColorStroke = editor.makeStroke({ color: outerColor, width: 20, }); // 👇 Apply the fill and stroke ellipse.fill = innerColorFill; ellipse.stroke = outerColorStroke; // Add the shape to the document editor.context.insertionParent.children.append(ellipse); ``` While the `fill` property is more straightforward to create, the `color` is just one of the possible properties of a `stroke`, as you can read in the [SolidColorStroke](../../../references/document-sandbox/document-apis/interfaces/SolidColorStroke.md) interface reference. Simplifying the example above: ```js // ... ellipse.fill = editor.makeColorFill(colorUtils.fromHex("#A38AF0")); ellipse.stroke = editor.makeStroke({ color: colorUtils.fromHex("#2ACfA9"), width: 20, }); // ... ``` Naming conventions Please note that Adobe Express uses the terms **make** and **create** to distinguish between plain objects and live document objects. You `makeColorFill()`, but `createEllipse()`. ## Use the Color Picker Adobe Express includes a native Color Picker, with special features such as Recommended Swatches, Eyedropper, Themes, Library and Brand colors. The Color Picker is available also to add-ons, you can invoke it using the [`addOnUISdk.app.showColorPicker()`](../../../references/addonsdk/addonsdk-app.md#showcolorpicker) method. **IMPORTANT:** This is currently **_experimental only_** and should not be used in any add-ons you will be distributing until it has been declared stable. To use it, you will first need to set the `experimentalApis` flag to `true` in the [`requirements`](../../../references/manifest/index.md#requirements) section of the `manifest.json`. #### Benefits - It simplifies the process of selecting a color, bypassing the Browser's color picker. - It's in sync with any swatches or Brand colors defined in Adobe Express. - It will evolve with Adobe Express, providing a consistent color picking experience across different parts of the application. The `showColorPicker()` method accepts a reference to an HTML element as its first argument, which will become the color picker's anchor element. The picker will be positioned relative to this element, based on the placement options available in the `ColorPickerPlacement` enum; additionally, the anchor will receive a custom `"colorpicker-color-change"` event when the color changes and a `"colorpicker-close"` event when it is closed. The `showColorPicker()` method requires an HTML element as its anchor point. Here's how it works: 1. **Anchor Element** - Pass an HTML element reference as the first argument. - The color picker will position itself relative to this element. - Use the `ColorPickerPlacement` enum to control positioning. 2. **Event Handling** - The anchor element receives two custom events: - `"colorpicker-color-change"`: Fires when a new color is selected. - `"colorpicker-close"`: Fires when the picker is closed. ### Example: Show the Color Picker #### ui/index.js ```js import addOnUISdk, { ColorPickerEvents, ColorPickerPlacement, } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { // Get the button element const colorPickerButton = document.getElementById("colorPicker"); // Add a click event listener to the button to show the color picker colorPickerButton.addEventListener("click", () => { addOnUISdk.app.showColorPicker(colorPickerButton, { // The title of the color picker title: "Awesome Color Picker", // The placement of the color picker placement: ColorPickerPlacement.left, // Whether the eyedropper hides the color picker eyedropperHidesPicker: true, // The initial color of the color picker initialColor: 0x0000ff, // Whether the alpha channel is disabled disableAlphaChannel: false, }); }); // Add a listener for the colorpicker-color-change event colorPickerButton.addEventListener(ColorPickerEvents.colorChange, (event) => { // Get the color from the event console.log(event.detail.color); // e.g., "#F0EDD8FF" in HEX (RRGGBBAA) format }); // Add a listener for the colorpicker-close event colorPickerButton.addEventListener(ColorPickerEvents.close, (event) => { console.log(event.type); // "colorpicker-close" }); }); ``` #### index.html ```html ``` Please note that the color returned by the `colorpicker-color-change` event is always a string in HEX format—with or without an alpha value, e.g., `#F0EDD8FF` or `#F0EDD8` depending on the `disableAlphaChannel` option. ### Example: Hide the Color Picker You can decide to hide picker UI e.g., after a certain amount of time. ```js colorPickerButton.addEventListener("click", () => { addOnUISdk.app.showColorPicker(colorPickerButton, { /* ... */ }); setTimeout(() => { console.log("Hiding the Color Picker after 10 seconds"); addOnUISdk.app.hideColorPicker(); }, 10000); }); ``` ### Example: Use the color You can use any HTML element as the color picker's anchor element; in the example below, we're using a `
` element to display a color swatch. #### index.html ```html
``` #### index.js ```js addOnUISdk.ready.then(async () => { const colorDisplay = document.getElementById("color-display"); colorDisplay.addEventListener("click", () => { addOnUISdk.app.showColorPicker(colorDisplay, { title: "Color Picker 1", placement: ColorPickerPlacement.left, eyedropperHidesPicker: true, }); }); colorDisplay.addEventListener(ColorPickerEvents.colorChange, (event) => { // Update the color swatch display in the UI colorDisplay.style.backgroundColor = event.detail.color; }); }); ``` To use the picked color in the Document Sandbox, you can use the [`colorUtils.fromHex()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#fromhex) method, which converts the HEX color string to a [`Color`](../../../references/document-sandbox/document-apis/interfaces/Color.md) object. ```js // sandbox/code.js const color = colorUtils.fromHex(event.detail.color); // 👈 A Color object // Use the color in the Document Sandbox, for example: let selection = editor.context.selection; if (selection.length === 1 && selection[0].type === "Text") { const textContentModel = selection[0].fullContent; textContentModel.applyCharacterStyles({ color }); // 👈 Using the color } ``` --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Shapes - Geometry - Rectangle - Ellipse - Path title: Use Geometry description: Use Geometry. contributors: - https://github.com/undavide --- # Use Geometry ## Create Shapes Adobe Express provides a set of geometric shapes that you can create and style programmatically. These shapes are instances of the [`RectangleNode`](../../../references/document-sandbox/document-apis/classes/RectangleNode.md) and [`EllipseNode`](../../../references/document-sandbox/document-apis/classes/EllipseNode.md) classes, and you can draw them using the [`editor.createRectangle()`](../../../references/document-sandbox/document-apis/classes/Editor.md#createrectangle) and [`editor.createEllipse()`](../../../references/document-sandbox/document-apis/classes/Editor.md#createellipse) methods, respectively. ### Example: Add a Rectangle ```js // sandbox/code.js import { editor } from "express-document-sdk"; const rect = editor.createRectangle(); // Define rectangle dimensions. rect.width = 100; rect.height = 100; // The current page, where the rectangle will be placed const currentPage = editor.context.currentPage; // Append the rectangle to the page. currentPage.artboards.first.children.append(rect); ``` Create vs. Add to the page Factory methods such as `createRectangle()` and `createEllipse()` don't automatically add the shape to the page; while is exists and you can manipulate its properties, it won't be visible until you append it to **a container** like an [Artboard](../../../references/document-sandbox/document-apis/classes/ArtboardNode.md), a [Group](./group_elements.md), or any other instance of a class that implements the [`ContainerNode`](../../../references/document-sandbox/document-apis/interfaces/ContainerNode.md) interface. You usually reference the container using [`editor.context`](../../../references/document-sandbox/document-apis/classes/Context.md), which provides access to the current page, selection, and other useful properties. Please note that you can append multiple shapes at once with the `append()` method: ```js const s1 = editor.createRectangle(); const s2 = editor.createEllipse(); // ... set all properties ... editor.context.currentPage.artboards.first.children.append(s1, s2); // 👈 ``` ### Example: Add an Ellipse Ellipses don't have a `width` and `height` properties, but a [`rx`](../../../references/document-sandbox/document-apis/classes/EllipseNode.md#rx) and [`ry`](../../../references/document-sandbox/document-apis/classes/EllipseNode.md#ry) (radius x, radius y) instead. An ellipse with a radius of 200 on the x-axis and 100 on the y-axis will result in a shape with 400 wide (`rx` times two) and a 200 tall (`ry` times two)! ```js // sandbox/code.js import { editor } from "express-document-sdk"; const ellipse = editor.createEllipse(); ellipse.rx = 200; // radius x 👈 ellipse.ry = 100; // radius y 👈 console.log(ellipse.boundsLocal); // { x: 0, y: 0, width: 400, height: 200 } 👈 mind the actual bounds! // The current page, where the rectangle will be placed const currentPage = editor.context.currentPage; // Append the rectangle to the page. currentPage.artboards.first.children.append(rect); ``` ### Example: Style Shapes Shapes have `fill` and `stroke` properties that you can use to style them. The following example demonstrates how to create a rectangle with a fill and a stroke. ```js // sandbox/code.js import { editor, colorUtils, constants } from "express-document-sdk"; // Create the shape const ellipse = editor.createEllipse(); ellipse.rx = 200; ellipse.ry = 100; // 👇 Apply the fill color ellipse.fill = editor.makeColorFill(colorUtils.fromHex("#F3D988")); // 👇 Create the stroke const stroke = editor.makeStroke({ color: colorUtils.fromHex("#E29E4E"), width: 20, position: constants.StrokePosition.inside, dashPattern: [50, 2], }); // 👇 Apply the stroke ellipse.stroke = stroke; // Add the shape to the document editor.context.insertionParent.children.append(ellipse); ``` ![Ellipse with fill and stroke](./images/shapes_ellipse.jpg) If you need a refresher on how to create and apply colors, check out [Using Colors](./use_color.md). ## Create Paths Paths are a versatile tool to create complex shapes in Adobe Express. The [`editor.createPath()`](../../../references/document-sandbox/document-apis/classes/Editor.md#createpath) method returns an instance of the [`PathNode`](../../../references/document-sandbox/document-apis/classes/PathNode.md) class, and accepts one [SVG string](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths) as the input. ### Example: Single path ```js // sandbox/code.js import { editor } from "express-document-sdk"; const p1 = editor.createPath( `M224,151L142,151C142,151 145.69,129.772 144,119C147.578,121.515 153.324,124.558 153,124C153.551,119.627 149,115 149,115C154.041,111.701 155.245,104.477 150,110C151.775,105.754 151.55,100.222 146,107C140.45,113.778 150.733,97.726 152,89C153.267,80.274 143.163,79.42 137,77C130.837,74.58 133.264,72.337 133,71C130.052,80.34 126.261,82.078 123,81C119.567,79.866 119.164,65.513 125,57C120.007,59.519 119,58 119,58C128.157,53.412 134.031,44.13 132,42C129.969,39.87 114.451,41.06 106,54C97.549,66.94 99.126,73.868 104,79C96.435,82.127 72,99 72,99C72,99 65.521,102.836 59,102C59.031,109.474 62.37,105.88 65,105C61.399,110.264 61.382,114.8 62,119C64.225,116.9 64,115 64,115C64,115 64.124,118.136 64,122C65.53,120.78 66,119 66,119C66,119 65.324,128.405 66.474,127.387C69.247,124.933 72.234,118.577 74,105C78.171,103.746 106,92 106,92C109.996,104.248 115.941,119.738 112,151L91,151` ); editor.context.insertionParent.children.append(p1); ``` ![Google "Osvaldo Cavandoli". You're welcome](./images/paths_linea.png) ### Example: Multiple paths Combining and grouping multiple paths, you can create complex shapes, like in the following example: ```js // sandbox/code.js import { editor } from "express-document-sdk"; const p1 = editor.createPath( `M310,222 A92,92 0 1,0 126,222 A92,92 0 1,0 310,222 Z` ); const p2 = editor.createPath( `M425.096,209.235 C425.096,209.235 520.492,413.969 554.392,486.722 C556.956,492.226 556.533,498.659 553.270,503.779 C550.007,508.900 544.355,512.000 538.283,512.000 C453.556,512.000 199.784,512.000 115.057,512.000 C108.985,512.000 103.333,508.900 100.070,503.779 C96.807,498.659 96.384,492.226 98.948,486.722 C133.289,413.023 230.896,203.545 230.896,203.545 L278.678,236.000 L331.559,195.000 L383.266,236.000 L425.096,209.235 Z` ); const p3 = editor.createPath( `M230.896,203.545 L278.041,102.365 C286.849,83.461 305.815,71.375 326.670,71.375 C347.525,71.375 366.491,83.461 375.299,102.365 L425.096,209.235 L383.266,236.000 L331.559,195.000 L278.678,236.000 L230.896,203.545 Z` ); const p4 = editor.createPath(`M218,106 L218,66`); const p5 = editor.createPath(`M160.074,121.590 L140.074,87.150`); const p6 = editor.createPath(`M117.878,164.193 L83.138,144.193`); const p7 = editor.createPath(`M101.831,222.119 L61.831,222.119`); const p8 = editor.createPath(`M118.076,280.194 L83.336,300.194`); const g = editor.createGroup(); g.children.append(p1, p2, p3, p4, p5, p6, p7, p8); editor.context.insertionParent.children.append(g); ``` ![Path](./images/paths_complex.png) ### Example: Add Fills and Strokes `PathNode` instances have `fill` and `stroke` properties that you can use to style the path, very much like, say, a [`RectangleNode`](../../../references/document-sandbox/document-apis/classes/RectangleNode.md). In the code snippet below, we're going to add some life to the path from the previous example. ```js // sandbox/code.js import { editor } from "express-document-sdk"; const p1 = editor.createPath(/* same as before... */); const p2 = editor.createPath(/* same as before... */); const p3 = editor.createPath(/* same as before... */); const p4 = editor.createPath(/* same as before... */); const p5 = editor.createPath(/* same as before... */); const p6 = editor.createPath(/* same as before... */); const p7 = editor.createPath(/* same as before... */); const p8 = editor.createPath(/* same as before... */); const g = editor.createGroup(); g.children.append(p1, p2, p3, p4, p5, p6, p7, p8); // Create a black stroke to apply to all paths const blackStroke = editor.makeStroke({ color: colorUtils.fromRGB(0, 0, 0, 1), width: 10, }); // Apply the stroke to all paths in the group for (const path of g.children) { path.stroke = blackStroke; } // Apply different fills to the paths p1.fill = editor.makeColorFill(colorUtils.fromHex("#f2af0d")); p2.fill = editor.makeColorFill(colorUtils.fromHex("#33cc7b")); p3.fill = editor.makeColorFill(colorUtils.fromHex("#ffffff")); editor.context.insertionParent.children.append(g); ``` ![Path](./images/paths_styled.png) --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Images - addImage title: Use Images description: Use Images. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Use Images ## Import Images into the page Add-ons are hosted in an iframe within the Adobe Express UI, and can load images as `` elements like any other web application. But in order to add images into an Adobe Express document, you need to use the [`addImage()`](../../../references/addonsdk/app-document.md#addimage) method of the `addOnUISdk.app.document` object. It expects a `Blob` object as the first argument, and an optional [`MediaAttribute`](../../../references/addonsdk/app-document.md#mediaattributes) object with the image's title and author. ### Example ```js // sandbox/code.js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { try { const imageUrl = "https://placehold.co/600x400.png"; const image = await fetch(imageUrl); const imageBlob = await image.blob(); await addOnUISdk.app.document.addImage( imageBlob, // 👈 Blob object { title: "Placeholder image", // 👈 Optional MediaAttributes author: "Adobe Developer", } ); } catch (e) { console.error("Failed to add the image", e); } }); ``` Please note that you can use `fetch()` also to get images that are local to the add-on; in this case, you can use paths relative to the add-on's root. ```js // sandbox/code.js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { try { const imageUrl = "./600x400.png"; // 👈 Local image const image = await fetch(imageUrl); // ... same as before ``` Image requirements Please refer to [this section](../../../references/addonsdk/app-document.md#image-requirements) to know more about the file formats support and size requirements for images. ## Import Animated images Importing a `GIF` via `addImage()` won't work as expected, as the method converts the animation into a static image before adding it to the document. You should use the [`addAnimatedImage()`](../../../references/addonsdk/app-document.md#addanimatedimage) method instead. ### Example ```js // sandbox/code.js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { try { const gifImageUrl = "https://path/to/a/file.gif"; // 👈 a GIF image const gifImage = await fetch(gifImageUrl); const gifImageBlob = await gifImage.blob(); await addOnUISdk.app.document.addAnimatedImage( // 👈 gifImageBlob, // 👈 Blob object { /* ... */ } // 👈 Optional MediaAttributes ); } catch (e) { console.error("Failed to add the image", e); } }); ``` GIF Image requirements All image formats are equal, but some formats are more equal than others. Please refer to [this FAQ](../../faq.md#are-animated-gifs-supported-when-importing-or-dragging-content-to-the-document) to learn more about specific GIF limitations in terms of size and weight. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - PDF Import - PowerPoint Import - importPdf - importPowerPoint title: Use PDF and PowerPoint description: Use PDF and PowerPoint. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Use PDF and PowerPoint ## Import PDF into the page You can add PDFs to the page using the [`importPdf()`](../../../references/addonsdk/app-document.md#importpdf) method of the `addOnUISdk.app.document` object, which expects a `Blob` object as an argument and a [`MediaAttribute`](../../../references/addonsdk/app-document.md#mediaattributes) object with a title (mandatory) and author (optional) as the second. PDF and PowerPoint imports will trigger a consent dialogue that asks the user to confirm the process; it's not possible to bypass it. As soon as the process starts, another dialogue will preview the PDF and track the operation progress. ![PDF Import dialogue](./images/pdf_import.png) Supported vector elements will be kept editable (e.g., shapes with rounded corners, text, etc.), and all pages will be imported. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { try { const pdfUrl = "https://url/to/your/file.pdf"; const pdf = await fetch(pdfUrl); const pdfBlob = await pdf.blob(); await addOnUISdk.app.document.importPdf( pdfBlob, // 👈 Blob object { title: "Official Launch Party", author: "Adobe", } ); } catch (e) { console.error("Failed to add the PDF", e); } }); ``` Please note that you can use `fetch()` also to get PDFs that are local to the add-on; in this case, you can use paths relative to the add-on's root. ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { try { // 👇 Local PDF const pdfUrl = "./OfficialLaunchParty.pdf"; const pdf = await fetch(pdfUrl); // ... same as before ``` ## Import PowerPoint into the page For PowerPoint files, the process is similar to the one for PDFs, but you need to use the [`importPowerPoint()`](../../../references/addonsdk/app-document.md#importpresentation) method instead. The method supports both `.pptx` and `.ppt` files, and shows the same consent and progress dialogues as seen above. ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { try { const powerPointUrl = "https://url/to/your/file.pptx"; // Or // const powerPointUrl = "./OfficialLaunchParty.pptx"; const powerPoint = await fetch(powerPointUrl); const powerPointBlob = await powerPoint.blob(); await addOnUISdk.app.document.importPowerPoint( powerPointBlob, // 👈 Blob object { title: "Official Launch Party", author: "Adobe", } ); } catch (e) { console.error("Failed to add the PDF", e); } }); ``` --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Text - Style Text - TextNode - Paragraph Styles - Character Styles - Fonts - Text Flow - Text Content - Text Styles - fullContent - applyCharacterStyles - applyParagraphStyles - fromPostscriptName - AvailableFont title: Use Text description: Use Text. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Use Text Text is an essential part of any design. Let's explore how to use all the available APIs to create and style it. ## Create Text The `editor.createText()` method doesn't accept any parameters and returns a brand new [`TextNode`](../../../references/document-sandbox/document-apis/classes/TextNode.md). The actual textual content starts as empty and is found in its [`fullContent.text`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#text) property. ### Example ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Create a new TextNode const textNode = editor.createText(); // Set the text content textNode.fullContent.text = "Hello,\nWorld!"; // Center the text on the page const insertionParent = editor.context.insertionParent; textNode.setPositionInParent( { x: insertionParent.width / 2, y: insertionParent.height / 2 }, { x: 0, y: 0 } ); // Add the TextNode to the document insertionParent.children.append(textNode); // Get the text content console.log("Text: ", textNode.fullContent.text); ``` The text is created with the default styles (Source Sans 3, 100pt, black). Use `\n` or `\r` to add a line break. ## Replace Text The text content of a `TextNode` can be replaced by setting the [`fullContent.text`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#text) property. ### Example ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Assuming the user has selected a text frame const selectedTextNode = editor.context.selection[0]; selectedTextNode.fullContent.text = "Something else"; ``` ## Apply Character Styles Text styles can be applied to a `TextNode` using the [`fullContent.applyCharacterStyles()`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#applycharacterstyles) method, which applies one or more styles to the characters in the given range, leaving any style properties that were not specified unchanged. The styles are defined by the [`CharacterStylesInput`](../../../references/document-sandbox/document-apis/interfaces/CharacterStylesInput.md) interface; the properties that can be set are: - color - font (please see the [Use Fonts](#use-fonts) section) - fontSize - letterSpacing - underline The range is an object with the `start` and `length` properties. Style Ranges and Text edits For the moment, replacing the `fullContent.text` will result in applying the style from the first range to the whole text. This behavior is subject to change in future releases. Please note that `applyCharacterStyles()` is only one way to set styles; you can also use the `characterStyleRanges` property, which supports both getting and setting styles, as described [here](#example-set-all-styles). ### Example: Set Styles in a range Let's change the styles for the first three characters of a TextNode. ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; // Apply character styles to the first three letters textNode.fullContent.applyCharacterStyles( { color: { red: 0, green: 0.4, blue: 0.8, alpha: 1 }, fontSize: 240, letterSpacing: 10, underline: true, }, { start: 0, length: 3, } ); ``` The `applyCharacterStyles()` method is not the only one that allows you to set styles; you can also use the `characterStyleRanges` property, which supports both getting and setting styles. ### Example: Get all Styles To get the complete list of text character styles, you can use the [`fullContent.characterStyleRanges`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#characterstyleranges) property, which returns an array of [`CharacterStylesRange`](../../../references/document-sandbox/document-apis/interfaces/CharacterStylesRange.md) elements. ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; const contentModel = textNode.fullContent; // Get the array of character styles const existingStyles = contentModel.characterStyleRanges; // Edit some properties existingStyles[0].fontSize = 10; // Reassign the array to apply the style changes contentModel.characterStyleRanges = existingStyles; ``` ### Example: Set all Styles You can also use the [`characterStyleRanges`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#characterstyleranges) property to set individual ranges or them all. It's always best to get the array, modify it, and then reassign it. ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; const contentModel = textNode.fullContent; // Get the array of character styles const existingStyles = contentModel.characterStyleRanges; // Edit some properties: the font size of all styles existingStyles.forEach((style) => { style.fontSize = 50; }); // Alternatively, you could set the properties for a specific style range // existingStyles[0].fontSize = 50; // Reassign the array to apply the style changes contentModel.characterStyleRanges = existingStyles; ``` ### Example: Reapply Styles In the current release, automatic preservation of the Character Style configuration is not available when editing a TextNode’s content via the `fullContent.text`. As a temporary solution, you can save the existing character style ranges before updating the text and reapply them afterward to maintain your custom styles. ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; const contentModel = textNode.fullContent; // Save existing character style ranges const savedStyles = contentModel.characterStyleRanges; // Replace the text content contentModel.text = "Updated text content\nwith preserved styles"; // Reapply the saved character styles contentModel.characterStyleRanges = savedStyles; ``` If the text content differs too much from the original, the character style ranges might not be reapplied correctly. This is a temporary solution until automatic preservation of character styles is available. ## Use Fonts In the Adobe Express Document API, Fonts are part of the Character Styles; we're treating them separately here for clarity. Similarly to the color and other properties, you can use individual [`CharacterStylesRange`](../../../references/document-sandbox/document-apis/interfaces/CharacterStylesRange.md) items from the [`CharacterStyleRanges`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#characterstyleranges) array as Font getters and setters, or use the [`applyCharacterStyles()`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#applycharacterstyles) method to apply a Font style to a specific range. The only caveat is that you cannot set the font as an Object literal, like, e.g., colors; fonts must be of type [`AvailableFont`](../../../references/document-sandbox/document-apis/classes/AvailableFont.md), and are instantiated from the `fonts` object (imported from the `"express-document-sdk"`) using the asynchronous `fromPostscriptName()` method. ```js // Always ✅ const font = await fonts.fromPostscriptName("SourceSans3-Bold"); // Won't work ❌ const font = { availableForEditing: true, isPremium: false, family: "Source Sans 3", postscriptName: "SourceSans3-Bold", style: "Bold", } ``` You can get PostScript names by setting different text fonts in the Adobe Express UI; then, log and inspec the `font` property of `characterStyleRange`, as seen [here](#example-get-all-styles). Remember that the `fromPostscriptName()` method is **asynchronous**. The promise resolves to an `AvailableFont` instance only for fonts that the user has permission to use for editing content; otherwise, it will resolve to `undefined`. ### Example: Set Fonts in a range. Let's now change the font of the first three characters in a TextNode. Please note that although you're allowed to set the font as the only style, the font object itself must contain all the properties, as the following code snippet demonstrates. ```js // sandbox/code.js import { editor, fonts } from "express-document-sdk"; // 👈 fonts import // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; // Getting a new font object const lato = await fonts.fromPostscriptName("Lato-Light"); if (!lato) return; // in case the user isn't entitled to use this font // ⚠️ Queueing the edit editor.queueAsyncEdit(() => { textNode.fullContent.applyCharacterStyles( { font: lato, fontSize: 24 }, { start: 0, length: 3 } ); }); ``` Asynchronous operations Queuing the `applyCharacterStyles()` method is necessary because `fromPostscriptName()` is asynchronous. This ensures the edit is properly tracked for saving and undo. You can read more about this in the [queueAsyncEdit()](../../../references/document-sandbox/document-apis/classes/Editor.md#queueasyncedit) reference. ### Example: Get all Fonts A font, regardless of whether accessed via `CharacterStylesRange` or executing `fromPostscriptName()`, exposes the following properties: - `isPremium`: boolean, indicating whether the font is a Premium Adobe font. - `availableForEditing`: boolean, indicating whether the user has access or licensing permissions to create or edit content with this font. - `family`: string, the font family name, as you would find in the Text panel's UI. - `postscriptName`: string, the PostScript name of the font. - `style`: string, the style of the font (e.g., "Regular", "Bold", "Italic"). You can log `font` and inspect it to find the actual PostScript name. ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; const contentModel = textNode.fullContent; // Get the array of character styles const existingStyles = contentModel.characterStyleRanges; // Log the font of the first style console.log(existingStyles[0].font); // { // isPremium: false // availableForEditing: true // family: "Source Sans 3" // postscriptName: "SourceSans3-Regular" // style: "Regular" // } ``` ### Example: Set all Fonts Similarly to what we've seen with [other styles](#example-set-all-styles), you can set the font in a range by reassigning the `characterStyleRanges` array. ```js // sandbox/code.js import { editor, fonts } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; const contentModel = textNode.fullContent; const sourceSansBold = await fonts.fromPostscriptName("SourceSans3-Bold"); if (!sourceSansBold) return; // Get the array of character styles const existingStyles = contentModel.characterStyleRanges; // Set the font for all styles existingStyles.forEach((style) => { style.font = sourceSansBold; }); // Alternatively, you could set the font for a specific style range // existingStyles[0].font = sourceSansBold; // Reassign the array to apply the style changes editor.queueAsyncEdit(() => { contentModel.characterStyleRanges = existingStyles; }); ``` Since we're dealing with asynchronous operations, we're queuing the edit to ensure it's properly tracked for saving and undo, as we did for [setting other styles](#example-set-all-styles) ## Apply Paragraph Styles Paragraph styles can be applied to a TextNode using the [`fullContent.applyParagraphStyles()`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#applyparagraphstyles) method. This method applies one or more style properties to entire paragraphs within the specified range, while leaving any style properties that are not provided unchanged. In contrast to directly setting the [`paragraphStyleRanges`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#paragraphstyleranges) property—which resets any unspecified properties to their defaults—using `applyParagraphStyles()` lets you update only the desired aspects of the style. **IMPORTANT:** This is currently **_experimental only_** and should not be used in any add-ons you will be distributing until it has been declared stable. To use it, you will first need to set the `experimentalApis` flag to `true` in the [`requirements`](../../../references/manifest/index.md#requirements) section of the `manifest.json`. The available properties are defined by the [`ParagraphStylesInput`](../../../references/document-sandbox/document-apis/interfaces/ParagraphStylesInput.md) interface and include: - **lineSpacing**: Specifies the spacing between lines (leading), expressed as a multiple of the font’s default spacing (e.g. 1.5 means 150% of normal). - **spaceBefore**: Sets the space (in points) before a paragraph. - **spaceAfter**: Sets the space (in points) after a paragraph. - **list**: Configures list styles (ordered or unordered) for the paragraph. When specifying list styles, you provide the settings via either the [`OrderedListStyleInput`](../../../references/document-sandbox/document-apis/interfaces/OrderedListStyleInput.md) or [`UnorderedListStyleInput`](../../../references/document-sandbox/document-apis/interfaces/UnorderedListStyleInput.md) interface. Paragraphs are defined by newline characters (`\n`), so the style ranges should align with these boundaries. The method accepts an optional range—an object with `start` and `length` properties—that determines which portion of the text content will be updated. If no range is provided, the styles will be applied to the entire text content flow. Style Ranges and Text Edits For the moment, replacing the `fullContent.text` will result in applying the style from the first range to the whole text. This behavior is subject to change in future releases. ### Example: Set Styles in a Range In this example, we modify the styles for a specific paragraph (the first 20 characters) by updating the line spacing and adding an ordered list style. ```js // sandbox/code.js import { editor, constants } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; // Apply paragraph styles to the specified range (e.g., the first paragraph) textNode.fullContent.applyParagraphStyles( { lineSpacing: 1.5, // 150% of normal line spacing spaceBefore: 12, // 12 points before the paragraph spaceAfter: 8, // 8 points after the paragraph list: { type: constants.ParagraphListType.ordered, numbering: constants.OrderedListNumbering.doubleZeroPrefixNumeric, prefix: "", postfix: ".", indentLevel: 2, // Indent level for the list }, }, { start: 0, length: 20, } ); ``` ### Example: Get All Styles To view the paragraph styles currently applied to a TextNode, you can access the [`fullContent.paragraphStyleRanges`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#paragraphstyleranges) property. This property returns an array of [`ParagraphStylesRange`](../../../references/document-sandbox/document-apis/interfaces/ParagraphStylesRange.md) objects, each representing the style configuration for a contiguous block of text (i.e. a paragraph). ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; const contentModel = textNode.fullContent; // Retrieve and log the paragraph style ranges const paragraphStyles = contentModel.paragraphStyleRanges; console.log("Paragraph Styles: ", paragraphStyles); ``` ### Example: Set All Styles You can also update paragraph styles for the entire text content by modifying the array returned by [`paragraphStyleRanges`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#paragraphstyleranges). In this example, we update the `spaceAfter` property for all paragraphs. ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; const contentModel = textNode.fullContent; // Get the current paragraph style ranges const existingStyles = contentModel.paragraphStyleRanges; // Update each range (for instance, set spaceAfter to 10 points) existingStyles.forEach((range) => { range.spaceAfter = 10; }); // Reassign the modified array to apply the changes contentModel.paragraphStyleRanges = existingStyles; ``` ### Example: Reapply Styles When you update the text content, paragraph boundaries may change. To preserve your custom paragraph styles, save the current style ranges, modify the text, and then reapply the saved styles. ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Assuming the user has selected a text frame const textNode = editor.context.selection[0]; const contentModel = textNode.fullContent; // Save the current paragraph style ranges const savedParagraphStyles = contentModel.paragraphStyleRanges; // Replace the text content contentModel.text = "New text content\nwith updated paragraphs"; // Reapply the saved paragraph styles contentModel.paragraphStyleRanges = savedParagraphStyles; ``` If the updated text does not match the original paragraph boundaries, some styles may not be reapplied as expected. This is a temporary limitation until automatic preservation of paragraph styles is implemented. ## Deal with Text Flow With the introduction of "Text Flow" in Adobe Express (allowing content to move freely between multiple text frames), the concept of a text node had to be separated from text content. The `fullContent` property _points to_ a [`TextContentModel`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md) object, which contains the actual text content that multiple `TextNode` instances can share. ### Example ```js // sandbox/code.js import { editor } from "express-document-sdk"; // Assuming the user has selected a text frame that contains // text spanning to multiple text nodes const selectedTextNode = editor.context.selection[0]; // Log all the text nodes that share the same TextContentModel for (const textNode of selectedTextNode.fullContent.allTextNodes) { console.log(textNode); } ``` --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Video - Media - mp4 - addVideo title: Use Videos description: Use Videos. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Use Videos ## Import videos into the page Similarly to Images and Audio, you can add Videos to the page using the [`addVideo()`](../../../references/addonsdk/app-document.md#addvideo) method of the `addOnUISdk.app.document` object, which expects a `Blob` object as an argument. ### Example ```js // sandbox/code.js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { try { const videoUrl = "https://www.nasa.gov/wp-content/uploads/static/history/alsj/a11/a11-16mm-mag-c.mp4"; const video = await fetch(videoUrl); const videoBlob = await video.blob(); await addOnUISdk.app.document.addVideo( videoBlob // 👈 Blob object ); } catch (e) { console.error("Failed to add the video", e); } }); ``` Please note that you can use `fetch()` also to get videos that are local to the add-on; in this case, you can use paths relative to the add-on's root. ```js // sandbox/code.js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { try { // 👇 Local video const videoUrl = "./7744218-uhd_2732_1440_25fps.mp4"; const video = await fetch(videoUrl); // ... same as before ``` Video file requirements Please refer to [this page](https://helpx.adobe.com/au/express/create-and-edit-videos/change-file-formats/video-quick-actions-requirements.html) to know more about the file formats support and size/length requirements for videos. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - User info - userId - isPremiumUser - currentUser title: Identify Users description: Identify Users. contributors: - https://github.com/undavide - https://github.com/hollyschinsky --- # Identify Users ## Get the User ID You can leverage the [`addOnUISdk.app.currentUser`](../../../references/addonsdk/app-currentUser.md) object to obtain the information for the currently logged-in user. Two asynchronous methods are available: `userId()` returns an anonymized ID that is unique to the user and persistent, and `isPremiumUser()` returns a boolean value indicating whether the user has a premium subscription with Adobe Express or not. ### Example ```js import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; addOnUISdk.ready.then(async () => { const userId = await addOnUISdk.app.currentUser.userId(); const isPremium = await addOnUISdk.app.currentUser.isPremiumUser(); console.log(`Current Userid:\n${userId}`); // Current Userid: // 3cda976828a4a90d13b0f38b1f8a59b1d6845cabfc48037fb30bb75d3ef67d36` console.log(`Is Premium User: ${isPremium}`); // Is Premium User: false }); ``` ## Use Cases The `userId()` serves as a unique identifier that you can use to track whether a user is a free or paid subscriber to your add-on. By storing this ID in your database, you can manage user-specific features and permissions, allowing your add-on to unlock premium functionalities or restrict access based on their subscription status. Similarly, `isPremiumUser()` return value can be used to tailor the user experience, for example suggesting Adobe Express premium features.. Please refer to the [`addOnUISdk.app.currentUser`](../../../references/addonsdk/app-currentUser.md) and the [licensed-addon code sample](/samples.md#licensed-addon), which shows how you can utilize the hash of the user ID to integrate your add-on with licensing and payment services. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Add-on Manifest title: About our How-to guides description: contributors: - https://github.com/hollyschinsky - https://github.com/undavide --- # About our How-to guides The following guides contain a set of common use cases and accompanying code snippets that will help you quickly implement a wide variety of features in your add-ons. You're encouraged to use the [Code Playground](../getting_started/code_playground.md) to test each one of them. We're constantly adding new how-tos, so make sure to check back often! If you're looking for more extensive examples, you can also look at our [code samples](https://developer.adobe.com/express/add-ons/docs/samples/). The [SDK References](https://developer.adobe.com/express/add-ons/docs/references/addonsdk/) can be used to find all of the objects, methods, properties and events supported for building add-ons. A list of the available topics can be found below: - Authentication & Authorization - [Use OAuth 2.0](./how_to/oauth2.md) - Data & Environment - [Store Data](./how_to/local_data_management.md) - [Theme & Locale](./how_to/theme_locale.md) - UI & Interaction - [Use Drag & Drop](./how_to/drag_and_drop.md) - [Use Modal Dialogs](./how_to/modal_dialogs.md) - Use Design Elements - [Use Text](./how_to/use_text.md) - [Use Geometry](./how_to/use_geometry.md) - [Use Color](./how_to/use_color.md) - [Use Images](./how_to/use_images.md) - [Use Videos](./how_to/use_videos.md) - [Use Audio](./how_to/use_audio.md) - [Use PDF and PowerPoint](./how_to/use_pdf_powerpoint.md) - [Group Elements](./how_to/group_elements.md) - [Position Elements](./how_to/position_elements.md) - Use Metadata - [Document Metadata](./how_to/document_metadata.md) - [Page Metadata](./how_to/page_metadata.md) - [Element Metadata](./how_to/element_metadata.md) - Exporting & Output - [Create Renditions](./how_to/create_renditions.md) - [Manage with Premium Content](./how_to/premium_content.md) - User Info - [Identify users](./how_to/user_info.md) Feel free to reach out and suggest new ones that you would find helpful! --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Add-on Manifest title: Guides description: Useful guides to aid in the development of Adobe Express add-ons, including common use case examples, CORS handling and other development-related resources. contributors: - https://github.com/hollyschinsky --- # Overview This section provides a set of guides to help you in the development stage of your add-on. ## Introduction In these guides, you'll find detailed information about [implementing common use cases](./how_to.md), [web frameworks, libraries and bundling](./frameworks-libraries-bundling.md), [performance tips](./performance.md), and more. Begin by watching this short video below which provides an introduction to some of the add-on features and APIs available for use in your add-ons.

# Performance Tips and Techniques This page covers a list of tips for optimizing your code to help you build high-performance add-ons. ## JavaScript Performance Tips ### Use asynchronous programming Use asynchronous programming techniques like callbacks, promises, and async/await to avoid blocking the main thread and improve performance. An example of each approach is given below: **1. Asynchronous programming using callbacks**
Callbacks are the simplest form of asynchronous programming in JavaScript. Instead of waiting for a function to complete, you pass a callback function to the function that will be called when the operation is complete. ```js function fetchData(callback) { // perform some asynchronous operation // ... // call the callback function with the result callback(result); } // call the function with a callback fetchData(function(result) { // handle the result }); ``` **2. Asynchronous programming using promises**
Promises are a more powerful form of asynchronous programming that allow you to chain operations and handle errors more easily. ```js function fetchData() { return new Promise(function(resolve, reject) { // perform some asynchronous operation // ... // resolve the promise with the result resolve(result); // or reject the promise with an error reject(error); }); } // call the function and handle the result with a promise fetchData().then(function(result) { // handle the result }).catch(function(error) { // handle the error }); ``` **3. Asynchronous programming using async/await**
Async/await is a newer feature in JavaScript that allows you to write asynchronous code that looks like synchronous code. For example: ```js async function fetchData() { // perform some asynchronous operation // ... // return the result return result; } // call the function and handle the result with async/await async function handleData() { try { var result = await fetchData(); // handle the result } catch (error) { // handle the error } } ``` Asynchronous programming can be more complex than synchronous programming, and requires careful handling of errors and callbacks. However, it can greatly improve the performance and responsiveness of your JavaScript code, and is the recommended practice for developers building add-ons. ### Cache frequently accessed data Cache frequently accessed data to reduce the number of times it needs to be computed. ### Use efficient data structures Use efficient data structures like `maps` and `sets` instead of arrays for faster access and better performance. ### Minimize DOM manipulation Accessing and manipulating the DOM is one of the slowest operations in JavaScript. Minimize DOM manipulation by grouping DOM changes together and using techniques like document fragments or virtual DOM. ### Use event delegation Instead of attaching an event listener to every element, use event delegation to attach a single event listener to a parent element and handle events for all its child elements. ### Optimize loops Optimize loops by minimizing the number of times you access an array's length property, using `while` loops instead of `for` loops, and breaking out of loops early when possible. ### Use lazy loading Use lazy loading to load resources only when they are needed, reducing initial page load time. ### Minimize the use of global variables Minimize the use of global variables to reduce the risk of naming collisions and improve performance. ### Reduce HTTP requests Minimize the number of HTTP requests by combining multiple files into one, using image sprites, or using data URLs for small images. ## React Performance Tips This section outlines some performance tips for writing React code. ### Use the virtual DOM React uses a virtual DOM to minimize the number of updates needed to render changes to the user interface. Make sure to use React's built-in components and lifecycle methods to take advantage of this feature. ### Minimize component updates Minimize component updates by using `shouldComponentUpdate()` or `React.memo()` to prevent unnecessary re-renders of components. ### Avoid unnecessary state changes Avoid unnecessary state changes by only updating state when necessary and using setState() correctly. ### Use `PureComponent` Use [`PureComponent`](https://react.dev/reference/react/PureComponent) instead of regular components to automatically implement `shouldComponentUpdate()` and improve performance. ### Use keys for lists Use keys for lists to help React identify which items have changed and minimize unnecessary re-renders. ### Use lazy loading Use lazy loading to load components or resources only when they are needed, reducing the initial page load time. ### Avoid excessive looping Avoid excessive looping by using techniques like `map`, `filter`, and `reduce`. ### Use memoization Use memoization to cache the results of expensive calculations or functions and avoid unnecessary re-computation. ### Use React Profiler Use [React Profiler](https://react.dev/reference/react/Profiler) to identify performance bottlenecks in your code and optimize them. ### Use code splitting Use code splitting to split your code into smaller chunks and load only the necessary code for a given page or component. ### Avoid unnecessary `props` Avoid unnecessary `props` by passing only those necessary to child components, and use destructuring to avoid passing unnecessary data. By following these tips, you can optimize your React code and improve its performance. Remember to test and measure the performance of your code to identify areas for optimization. ## Performance Testing Be sure to test your add-on code and measure its performance to identify areas for optimization and ensure that your changes have a positive impact on performance. There are several ways to measure the performance of your JavaScript code, some are listed below for reference: ### `console.time()` and `console.timeEnd()` Use the `console.time()` and `console.timeEnd()` methods to measure the time it takes for a block of code to execute. ```js console.time('myFunction'); myFunction(); console.timeEnd('myFunction'); ``` ### `performance.now()` Use the `performance.now()` method to measure the time it takes for a block of code to execute with high precision. ```js const t0 = performance.now(); myFunction(); const t1 = performance.now(); console.log(`myFunction took ${t1 - t0} milliseconds.`); ``` ### Chrome DevTools Use the performance profiler in Chrome DevTools to identify performance bottlenecks in your code. The profiler can show you a detailed breakdown of the time spent in each function, as well as information about memory consumption. #### Task Manager The Chrome Task Manager displays the CPU, memory, and network usage of each open tab and plug-in, and allows you to kill an individual process similar to the Windows Task Manager or MacOS Activity Monitor. To open the Task Manager, go to the Chrome triple dot menu -> **More Tools** -> **Task Manager**: ![Task Manager](img/menu_task_mgr.png) There you can locate the memory consumption of the OS process spawned specifically for the add-on iframe, like below: ![Task Manager](img/task-mgr.png) #### Memory Consumption Monitoring The Chrome **Memory** tab can be used to help solve memory issues, including debugging of JavaScript memory leaks. You can also use it to see the memory consumption of the JavaScript context created by the add-on iframe specifically, as shown below: ![Memory Consumption](img/memory-consumption.png) ### Lighthouse Use [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/), an open-source tool from Google, to audit performance and identify areas for optimization while your add-on is loaded. Lighthouse can provide suggestions for improving page load time, reducing the size of resources, and optimizing JavaScript code. ### Benchmark.js Use [Benchmark.js](https://benchmarkjs.com/) to benchmark different implementations of a function or compare the performance of different libraries. Benchmark.js provides a simple and powerful way to measure the performance of your code in a standardized way. By measuring the performance of your JavaScript code, you can identify areas for optimization and ensure that your code is running efficiently. Use these techniques to test and optimize your code to achieve the best possible performance. # Developer Brand Guidelines ## Overview The [Adobe Express Developer Program brand guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf) provide important information on branding, including: - How to brand your add-on - How and when to use Adobe logos and branding - How to refer to Adobe products and terms - Use of acronyms and abbreviations - Guidelines for social media Refer to these [Developer Brand Guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf) often when creating and designing your add-on. # Generative AI Guidelines This set of guidelines gives you the latest information on how to meet Adobe’s requirements around Generative AI. ## Introduction Our Generative AI guidelines include: - Listing guidelines - Content generation guidelines, including disclaimer guidelines - Recommendations for add-ons using generative AI technology We will edit and update this guide as our requirements and the technology evolve. ## Overview The rise of Generative AI offers potential benefits for add-ons, particularly in facilitating content creation and accelerating workflows. Adobe requires that users be able to choose whether they want to try add-ons that employ Generative AI technology. This means that add-ons must clearly state upfront when they use generative AI technologies. We created these guidelines to assist any developers who wish to take advantage of Generative AI in their add-ons, to make sure they do so in accordance with Adobe standards. We have split this section into: - [Requirements](./requirements.md) (which must be followed) - [Recommendations](./recommendations.md) (which are suggestions we encourage for best practice) **If we receive reports of abuse, we may elect to remove an add-on whether it is private or public.** # Recommendations Our best practices for Generative AI add-ons ## Overview The following recommendations are suggestions which we think will help you to optimize the user experience of your add-ons. However, we will not remove an add-on if we find these recommendations are not implemented. ## Create content quickly Users expect to be able to quickly add assets to their documents quickly and efficiently. While generative AI technologies often take several seconds to yield results, you should ensure that users can start to see results after a short period of time (**ideally less than fifteen seconds**). You should also display an appropriate spinner, shimmer, or other appropriate animation while results are being generated, so that users know that the process is still underway. You might want to consider quickly returning a single image before generating variations as well. Add-ons that take too long to generate content are not likely to see repeat use. ## Share information about your AI ethics position If your add-on leverage generative AI technologies, you should provide information to the user about how models are trained and used from an ethical perspective. While your ethics don’t have to match Adobe’s perfectly, users should be able to learn about your ethical standards and how these standards apply to your use of AI. ## Share information about how your add-on uses generative AI It’s pretty clear how an add-on that generates an image from a text prompt leverages generative AI. But there are other workflows where it might not be clear that generative AI is used, why it is used, and how it is used to facilitate a given workflow. Users should be provided with information about how and why your add-on leverages generative AI so they can understand its potential applications and limitations. ## Set clear expectations with your users about what is acceptable and what is not You might have additional requirements regarding acceptable content. Perhaps you want to introduce additional content restrictions beyond the Adobe requirements. Be sure to communicate these to your users. These requirements can be listed in any number of locations: - In the add-on itself - In the add-on’s description - As part of any of the links you provide when you submit an add-on to the marketplace (including terms of use or how your add-on uses AI). ## Provide a mechanism for reporting inappropriate content No content filter or classifier is perfect, and it’s possible that some restricted content might be generated unintentionally, even with a safe prompt. You may provide your users with a mechanism for alerting you when this happens, so that you can take appropriate action to ensure that similar content isn’t generated in the future. Users can also use the “Report Abuse” feature inside Adobe Express to report content, but this report will only be sent to Adobe. **If we receive a significant amount of abuse reports, we may remove your add-on for the safety of our users.** ## If you remove generated content or prevent content from being generated, be clear as to why Be sure to provide a notice to your users about why your add-on couldn’t generate content, or why an output asset might have been removed. You don’t need to go into details, but users shouldn’t be faced with blank content or terse, indecipherable error messages. You can use the Firefly web app or Text-to-image features in Adobe Express as a good guide on how to do this. ## Haven’t found what you’re looking for? Let us know These recommendations are a “living document” and will be updated over time, as our guidelines - and the technology itself - evolve. If you have any questions about a specific case or issue, or would like to learn more about our requirements, please [contact us](mailto:cc-extensibility-genai-integration-support@adobe.com). ## Listing requirements Your add-on's listing must be clear that generative AI is being used. During submission of your add-on, you'll be prompted to select if your add-on uses generative AI. If your add-on does so, in any capacity, you must answer in the affirmative. This will ensure that users are properly informated that your add-on utilized generative AI for some or all of its features. ## Content generation requirements Content created by your add-on must adhere to [Adobe’s General Terms of Use](https://www.adobe.com/legal/terms.html) and the [Developer Terms of Use](http://www.adobe.com/go/developer-terms). Specifically: * Your add-on must not generate illegal content. * Your add-on must leverage filtering technologies and must test your add-on to ensure that illegal content is not generated. * Before an add-on leveraging generative AI is approved for publication, you may be asked to certify that you have read these guidelines and agree to abide by them. If your add-on is found to be generating illegal content, your add-on will be removed. ### Disclaimer requirements If your add-on generates text or code, your add-on must remind the user that the content generated may be inaccurate. Users should always review the generated content with trusted sources before publishing the content or executing any code. Because every use of AI is different, Adobe doesn't provide a one-size-fits-all example, but there are several examples in the generative AI ecosystem that should provide good examples. ### Use cases that don't require certification Add-ons that meet the following criteria do not require the add-on's developer to submit a self-certification of compliance with our content generation requirements: * Your add-on is privately listed (**NOTE:** We may still elect to remove your privately listed add-on if we receive credible reports of abuse) * AI Models that the user installs and runs locally on their device (that is, models that are not included within the add-on bundle) * Text-to-speech using Generative AI (provided the text is supplied by the user) * Instrumental music or sound effects created using Generative AI (where there are no lyrics) ## Check back frequently! These requirements are a “living document” and will be updated over time, as our guidelines - and the technology itself - evolve. If you have any questions about a specific case or issue, or would like to learn more about our requirements, please [contact us](mailto:cc-extensibility-genai-integration-support@adobe.com). # Accessibility Adobe products and add-ons should be accessible to users with a range of needs. ## Overview We believe Adobe products and add-ons should be accessible to users with a range of needs. Follow the guidelines outlined below to make your add-on more accessible. For more information, check [Adobe’s Accessibility guidelines](https://www.adobe.com/accessibility.html). ## Accessibility standards Your add-on should meet accessibility guidelines to ensure it can be used by people with disabilities. This could be [WCAG 2.0](https://www.w3.org/TR/WCAG20/) or higher. ## Keyboard accessibility Your add-on’s user interface should provide proper mouse and keyboard focus indicators, and allow for easy navigation and interaction using only a keyboard. ## Alt-text Your add-on should provide alternative text for images and labels for form fields, as well as other accessibility features which assist users with visual impairments and other disabilities. ## Contrast and fonts Your colors, contrast and fonts should be easily readable by users with visual impairments, and meet accessibility standards. ## Readability Your text and commands should be simple and understandable. This is useful for users with dyslexia, as well as those who do not speak English as a first language, and those who are performing actions while multitasking, distracted or under pressure. # Authenticating Users Users must be able to log in and out of the add-on seamlessly. Ensure that all of these functions work effectively before submitting for review. ## Sign-up functionality Users must be given a seamless sign-up option, particularly when logging in is mandatory. They must be able to complete this process in a new window if they wish. ## Logout functionality Users must be able to logout easily, using a button or link that is simple to locate. ## Popups and blockers Sometimes users may think that the add-on is not responding properly when they try to sign in, but the sign-in popup is actually being blocked by the browser’s popup blocker. This is why add-ons that use OAuth flows must always indicate to users when a popup window has been blocked. This can be done by displaying a message in the add-on UI or on the webpage. **NOTE:** The API returns `POPUP_BLOCKED` when a popup window is blocked during the OAuth flow. This will help you detect when a popup has been blocked. ## Test credentials If a login or license key is required to access any paid features, you must provide test credentials to reviewers so that they can view all aspects of the add-on. # Compatibility Your add-on will help the user to make the most of their Adobe Express experience, so it’s important that it feels like part of their workflow. ## Conflicts Your add-on must not conflict with any other installed add-ons in Adobe Express. ## Different systems and browsers Your add-on must work on the same [browsers and operating systems supported by Adobe Express](https://helpx.adobe.com/express/system-requirements.html). Add-ons must perform well on machines of varying performance characteristics. Adobe Express is supported on Chromebooks and machines with low resolution and limited memory. Don't assume that your users have powerful computers. Add-ons are not currently supported on mobile devices. This will change in the future, so you should ensure that your add-on adheres to responsive design principles and can adapt to different panel sizes so that you can be prepared when mobile support is available. # Content Requirements In the interests of user safety and acceptable standards, add-ons must not: - Contain or generate illegal content (such as CSAM, content that encourages illegal drug use, etc). - Contain or generate adult content (such as sexual content, nudity, gore, intense violence, or strong language). - Contain or generate hate speech or speech that promotes violence or bullying or cruel behavior to anyone. - Generate content that promotes, automates, or relates to highly regulated activities (such as financial advice, medical advice or diagnosis, legal advice or documents, contracts). - Contain or generate code that could be malicious (such as viruses, malware, spyware, etc.). - Participate in misinformation/disinformation campaigns. - Violate intellectual property rights, including copyright, of other persons and companies. - Automatically perform actions determined by AI that would be destructive or that can’t be undone without explicit user consent.. # Testing for edge cases Edge cases may affect the performance of your add-on. ## Overview As well as checking for more expected errors, you must also take the time to search for edge cases that may affect the performance of your add-on. This includes checking for boundary values or extreme scenarios that may cause issues when your add-on is in general use. ## Handling inputs Ensure that your add-on can handle edge cases such as empty inputs, long inputs or inputs with special characters. ## Boundary values Your add-on should also be able to handle boundary values such as minimum or maximum allowed values without any unexpected behavior. ## Extreme scenarios Your add-on should be able to cope with extreme situations such as low system resources, poor network conditions, or unexpected interruptions. ## Tab navigation Users often use tab navigation to make their way through websites and applications. This is particularly true of users with disabilities who rely on keyboard navigation. Please ensure that add-ons do not interfere with - or override - tab navigation. # Features Some elements to bear in mind when testing the features of your add-on before submission. ## Stability Make sure the add-on does not crash Adobe Express, and operates consistently without stability issues. ## Storing user data Your add-on should only store necessary information on the user’s machine. If it stores a lot of data, it must be able to handle any errors that arise from exceeding storage quotas. ## Loading indicators When loading takes a noticeable amount of time, a loading indicator should be displayed to provide visual feedback. If an operation takes a considerable amount of time and blocks the use of the add-on or Adobe Express, the add-on should provide an affordance that allows the user to cancel the operation. ## Generating renditions Where the add-on generates renditions of user content, it must provide some sort of progress indicator. This reassures the user that the process is ongoing, and that the add-on has not simply stopped responding. ## Text boxes Any text boxes must support English characters, numbers and special characters as expected. ## Importing images and videos Your add-on must be able to smoothly import any media required for design or operation. ## “Drag and Drop” functionality If the add-on allows users to add content to the document, it should support drag and drop functionality. This allows users to select an image from the add-on and drop it in the desired location in a document. ## “Single-click to Add” functionality Any add-on where content can be added to a document should also support the option to add an image with a single click. This means that users can click on the desired location to integrate an image. ## Support for both options above You are encouraged to support both “Single-click to Add” and “Drag and Drop” functionality wherever possible. There may be some exceptions in cases where “Drag and Drop” is technically not feasible. If “Drag and Drop” is not supported, ensure that you communicate this to the user to avoid confusion. ## Navigation The user must be able to navigate through all menus and screens, without feeling “stuck”. If they find themselves on the wrong screen, they should be able to navigate out (for example, using a “back” or “menu” button). ## Online-offline-online If the user’s internet connection drops out during use, the add-on must be able to resume normal operation after reconnecting. This means: - The reconnection process should occur seamlessly and gracefully. - The transition from online to offline and online again should not break the add-on in any way. - If the add-on does fail to work for whatever reason, an appropriate error message should be displayed. - When the user is offline, the add-on should not affect the performance or stability of the application. # Overview This section provides a set of guides to help you in the distribution stage of your add-on. ## Introduction The general guidelines that can be found in this section cover a variety of areas including [acceptable content](./content.md), [user interface guidelines](./user_interface.md), [listing metadata](./listing.md), [feature functionality](./features.md) and testing guidelines (e.g., [compatibility](./compatibility.md), [usability](./usability.md), [accessibility](./accessibility.md), [performance](./performance.md)) and more. ## Testing Testing is an important part of the add-on creation process. It ensures that the add-on is free from bugs, works as intended, and performs as smoothly as possible. We strongly recommend that you test your add-on rigorously before submitting it for review, to avoid rejection or delays in submission ## Important Resources The following list of guidelines should also be carefully reviewed as they provide important information you'll need to know before submitting your add-on to our marketplace. - [Developer Brand Guidelines](../brand_guidelines.md/) - [Generative AI Guidelines](../genai/index.md) - [Monetization Guidelines](../monetization.md) # Listing Your Add-on Your listing should provide valuable information about what your add-on does, and why people should use it. Are you an existing developer? Action required: Add trader details to continue EU distribution. [Add trader details now.](https://new.express.adobe.com/add-ons?mode=submission) ## Listing metadata Your listing metadata provides Adobe and users with details about your add-on. Follow the guidelines below to ensure your listing clearly describes everything you want your users to know about your add-on. **NOTE:** Please avoid the use of emojis in your listing metadata as the user interface does not allow them. Refer to our [Developer Brand Guidelines](../brand_guidelines.md) for more information. ### Naming your add-on Consider using a simple name that is easy to spell, but distinctive enough to find using search. The name should give users an idea of what the add-on does. For example, using a company name may be clear to you, but not to other users. Try to avoid using a long name where possible. When listing an add-on, you must not use the word “Premium” to describe your upgrade options, as this should only be used in relation to the [Adobe Express Premium Plan](https://www.adobe.com/express/pricing). ### Your add-on description Let your users know clearly and concisely what your add-on does. Think of this as your elevator pitch. Use this space to concisely describe the functionality and benefits of your add-on, using tone and language which is appropriate to your add-on and your own personal or brand style. If your add-on generates assets via AI or imports assets from an asset repository, you should include accurate and up-to-date information about usage rights here. Consult our [Generative AI guidelines](../genai/) for more information. ### Your summary Use the summary section to concisely outline the features and functionality of the add-on. Think of this as the tagline to your add-on. ### Support email You must provide a valid email address so that we can contact you for support. End users will not see this email address. ### Screenshots Your screenshots should accurately represent the functionality and appearance of the add-on, such as key features and important user steps. It is essential to refrain from including images that could potentially mislead users or are deemed inappropriate. ### Your icon Use an original icon that complies with our [Developer Brand Guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf). Consider using a design that illustrates what the add-on does. Your icon should be simple and distinctive, and must be suitable for viewing on different devices and browsers. ### Help URL Include a link to a valid webpage that provides accurate and relevant information about the add-on’s usage and functionality. It is recommended to add support for common queries or user issues. ### Privacy Policy/License Agreement You must include details of your privacy policy and contractual terms applicable to the use of your add-on(s). Do not include text that violates our [Developer Brand Guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf). ### Keywords It is recommended to include keywords to categorize your add-on and make it easier to find. Multi-word keywords are allowed (but remember: spaces count toward the overall character limit for keywords). Try to include keywords which reflect the features and purpose of the add-on, which the user is likely to search for. Spell out keywords in full. You must not include any words that are abusive and/or derogatory. ### Release section Use this section to include details of any updates, fixes and improvements you have made to the add-on since launch. These notes must accurately describe the changes made, and be written in such a way that they make sense to the user. ## Publisher profile Completing your publisher profile can be helpful in making your app look more professional, interesting and trustworthy. Ensure that all content adheres to our [Developer Brand Guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf). ### Your publisher name Create a unique name that reflects your personality. It must not contain inappropriate words or phrases. ### Your description Use your description to explain a little more about you, including any areas you may specialize in. ### Your logo Your publisher logo must be original. You must not use the Adobe logo, or any product icons or images, without authorization. ### Your website URL Include a website so that people can find out more about you. This should successfully open your own website. Specifically, according to page 5 of the [Developer Brand Guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf) your “...domain name must not include any Adobe trademark or product name.” For example: **www.[Your Company name].com/Add-onforExpress** NOT: **www.[Your Company name]forAdobeExpress.com** ### Trader details In accordance with the [European Union Digital Services Act](https://eur-lex.europa.eu/legal-content/EN/ALL/?uri=CELEX:32022R2065) trader requirements, developers who wish to distribute their add-ons in the European Union (EU) must provide additional information in their publisher profile. Developer/Trader The terms **developers/traders** are used interchangeably in this guide. Any developer who wishes to publish their add-ons in the EU can be identified as a trader. Checkout our [Know your trader](https://developer.adobe.com/compliance/) guide to understand **why** you must provide these details to make your add-ons available in the EU. This information will be displayed to only users in the European Union (EU). Choose **Yes** if you wish to make your add-ons available for users in the EU. ![Trader details](../../img/trader-details.png) You must provide the following details: - Business email address​ - Country code and Business telephone number - Business street address or P.O. box - City - State/Province/Region - ZIP/Postal code - Country - Business D-U-N-S number [`optional`] You must complete and submit your publisher profile in order to submit your first add-on, however this is only a one time thing, unless you decide you need to update for your own reasons. Any changes to your publisher profile will need to be submitted for approval again. **Are you an existing developer?** Existing developers can easily update their trader details directly in their [publisher profile](https://new.express.adobe.com/add-ons?mode=submission). Note that only trader details can be added; other fields cannot be edited by developers. You must provide trader details by February 16, 2025, to keep your add-on visible and available in Adobe Express for users in the European Union as of February 17, 2025. This trader information will be displayed publicly on your listing detail pages when viewed from EU countries. # Performance If you want your add-on to become a key part of a user’s workflow, you must make sure it performs smoothly and efficiently. ## Overview Your add-on must meet acceptable standards in terms of loading time, response time and utilization of resources. You should also bear in mind that a user’s experience may differ, based on factors such as their network connection. Below are a few things to consider before submitting for review. ## Crashes Your add-on must not cause any slowdowns or crashes on Adobe Express. ## System resources You must ensure your add-on does not consume excessive system resources which may affect the performance of the user’s system, or Adobe Express itself. This includes resources such as CPU and memory. ## Network calls Your add-on must not make an excessive amount of network calls. You can monitor this by noting the number of network calls made over a certain period, and comparing it to a predetermined threshold. ## Network efficiency Make sure your add-on is network efficient and does not have any performance issues. You can do this by noting the time it takes to complete a network call and how much data is transferred. ## Network usage We recommend using network monitoring tools to identify any unexpected or excessive network activity caused by the add-on. ## Network security Your add-on must not pose any security risks, and network calls must be secure. We require that add-ons use secure protocols, encrypt sensitive data, and implement appropriate authentication and authorization mechanisms. ## Network call logs Review your network call logs to identify any anomalies or notable patterns in network usage. This helps you identify any issues related to network calls. ## Choosing your idea Before you start, do a little research on the marketplace to see what other developers are doing, and where you can make an impact. For example: - Does my add-on help users to do their work quicker and more effectively? - Does it streamline the user’s workflow(s)? - Does my add-on enable users to be more creative, or make their work look better? - Are there lots of add-ons already doing the same thing on the marketplace? ## Use of AI Does your add-on leverage AI to generate content or automate workflows? If so, be sure to check out the [Generative AI requirements](../genai/). # Security Users must be able to access an add-on without compromising their security or negatively affecting their systems. Use these guidelines to ensure the security of your add-on. ## Malicious code Your add-on must not include any malicious code, viruses or malware that could harm a user’s system, or compromise their data. ## User information Your add-on must not transmit any sensitive user information without prior authorization or consent. ## Vulnerabilities Your add-on must not feature any currently-known security vulnerabilities, such as unsecured communication channels, weak authentication mechanisms, or potential injection attacks. We expect developers to maintain their add-on after completion and listing. This includes fixing bugs and addressing any security issues. It is also good practice to be responsive to user feedback and requests. ## Secure coding Your add-on must follow secure coding practices, such as input validation, as well as proper error handling. # Usability Follow these guidelines to make sure that your add-on meets our usability standards before submitting. ## Blending with Adobe Express UI The add-on should integrate seamlessly with Adobe Express UI, incorporating commonly-used patterns within the Adobe Express UI to facilitate user navigation. We also suggest that you use the [Adobe Spectrum theme for Adobe Express](https://spectrum.adobe.com/page/theming/#Resources-for-Spectrum-for-Adobe-Express). It is not mandatory to use [Adobe’s Spectrum libraries](https://spectrum.adobe.com/) for your add-on, but it can significantly simplify the development process. ## Responsive interface You must make sure that your add-on’s user interface and controls are responsive, functional and usable across different browsers and screen sizes. ## User inputs The add-on must provide some sort of response or validation for user inputs, such as confirmation messages for successful actions or error messages for invalid inputs. ## Error messages Any error messages, warnings or notifications should be clear, informative and helpful to the user. ## Modal dialogs The add-on should not interrupt the user’s workflow unnecessarily with dialog boxes. These boxes should only be used for important messages. Messages such as “success” notifications should be rendered inside the add-on’s UI panel instead. # User Interface Your add-on's user interface should provide a great user experience. ## Your add-on design When it comes to design, create a layout that aligns with the functionality of your add-on. However, remember that your users will be accessing your add-on as part of their Adobe Express workflow - so try to make sure they can move seamlessly without coming across anything jarring or counter-intuitive. We highly suggest leveraging the Spectrum Design System and Spectrum Express theme to create user interfaces that feel similar to the Adobe Express user interface. We’ve included these guidelines here to help you create a great experience for users. Also refer to our [User interface design guide](../../../design/index.md), which includes a comprehensive set of [UX Guidelines](../../../design/ux_guidelines/introduction.md) to help with your add-on design. ## Display Items Here are some tips to assist with the layout of your add-on, from display to icons. ### Empty screens You should avoid including any empty screens in your design. All screens within your add-on should feature at least some information. ### Scroll bars If your content does not fully fit on a single screen, you should make it clear that the user can scroll to view the remaining content. ### Buttons All add-on buttons should be displayed correctly, and not overlap with each other. ### Changing between languages The user interface should still be clear and navigable when the user switches between languages. They should also be able to input special characters or alphabets from other languages without causing malfunctions. ### Modal dialog Avoid using dialog boxes that interrupt the user’s workflow, except in important situations. For example, use the in-panel UI to display things like success messages, rather than a dialog box. ### Icons Use unique icons to represent the add-on itself, and the features within it. Consider working with a designer to create icons that are clear and distinct. ## Using Adobe icons and phrasing Follow these guidelines to make sure you are referring to Adobe correctly in your add-on. For more information on using Adobe brand assets, check our [Developer Brand Guidelines](../brand_guidelines.md). ## Referring to add-ons Always use the spelling “add-on”, NOT “addon” or “plugin”. ## Referring to Adobe Express Always use the full name “Adobe Express” when referring to the tool. Do not shorten it to “Express”. ## Using Adobe Icons We allow and encourage you to use the workflow and UI icons in Spectrum Design within your add-on. However, you must not use any Adobe branding elements, such as the Adobe logo or the “Premium” crown icon for paid items. ## Referring to stock photos If your copy includes any mention of stock content or assets within Adobe Express, you must refer to it with a lower case “s” (ie. stock). This avoids confusion with Adobe Stock, which is a separate service. For example: ***“Discover a wide range of high-quality stock photos in Adobe Express”*** ## Localization All add-ons must provide support for the English language. In addition, you are welcome to create your add-on in any other supported language. ## Functionality for Multilingual UI You must test your add-on to make sure that the user interface remains intact and functional when you switch between any supported languages. ## Text display Ensure that changing the language does not result in add-on strings or text content being cut-off or truncated. All text should be visible and displayed appropriately on the add-on in all supported languages. ## Branding your add-ons for monetization When building your checkout experience to monetize your add-ons, you can use the approved colors, gradients, and iconography to communicate when content or features in your add-on require purchase and when content or features are paid and unlocked. Carefully review our [monetization brand guidelines](../monetization.md#branding-your-add-ons-for-monetization) to get the latest information on Adobe’s recommendations on branding your add-ons for monetization. # Introduction Thank you for joining the community of developers worldwide that are creating add-ons for Adobe Express. ## Guideline Categories Adobe's goal is to publish high-quality add-ons. With that goal in mind, we’ve created this set of guidelines to give you some tips on best practices, and ensure you take the right steps to get your add-on approved: - [General guidelines](./general/) - [Adobe Express Developer Program brand guidelines](./brand_guidelines.md) (aka: "Developer Brand Guidelines" throughout these guides) - [Monetization guidelines](./monetization.md) - [Generative AI guidelines](./genai/) # Monetization Guidelines Developers who submit to the marketplace can charge users for using their add-ons. ## Overview Consider whether you want to monetize your add-on at the outset, as it helps you plan your strategy more effectively. Remember to research the marketplace carefully before you start creating. This will help you learn whether there is a substantial market for a paid add-on and what other developers (if any) are charging for similar solutions. We’ve created these standards to ensure monetized add-ons provide users with a consistent and reliable experience. Our monetization guidelines include: - [General](#general-guidelines) - [Requirements](#requirements-for-monetizing-your-add-ons) - [Recommendations](#recommendations-for-monetizing-your-add-ons) - [Branding your add-ons](#branding-your-add-ons-for-monetization) ## General Guidelines The sections below provide details on the guidelines developers should be following when monetizing their add-ons. ### Transparency All add-on developers must be transparent about their pricing and monetization methods. This includes being honest about the price and any recurring fees or additional costs. Users must be able to: - Locate clear instructions on requesting a refund (please place a refund policy on your site). - Find a way to cancel any recurring payments, including subscriptions. Manage their payments and update their payment method - see how much an add-on will cost them, with no hidden fees or surprise charges. ### Support You must offer a clear and simple support process for all publicly-listed add-ons. This includes responding to any queries from users regarding access and payments. ### Compliance You must comply with all applicable laws and regulations. This includes payment regulations, taxation, data privacy, and security. ### Third-party ads Your add-ons must not contain any third-party advertising, including any ads from Google Ads, Facebook Ads, or any other ad networks. This applies to the advertising of products and services within the add-on description, and the use of banners in the add-on itself. ### Complying with Express monetization rules When listing an Adobe Express add-on, you should not use “Premium” to describe your upgrade options. The word “Premium” should only be used in relation to the Adobe Express Premium Plan, which provides users with extra content, increased storage and more. ### Exporting premium content Adobe Express allows users that aren't subscribed to a premium version of Adobe Express to experiment with premium content in their documents. When users download or share their document, they'll be prompted to upgrade to a premium plan. Add-ons must not allow users on a free plan to download, share, or export premium content provided by Adobe Express. When a user on a free plan tries to share or export premium content using your add-on, you must notify the user that they need to upgrade, and inform the user how they can do so. See our [documentation](/guides/develop/use_cases/#premium-content) for more information on how to do this. ## Requirements for monetizing your add-ons When listing your add-ons for monetization to the public marketplace, you must indicate your add-on's purchase offerings in [the public listing](../public-dist.md#step-8-enter-the-monetization-details). The following are the supported monetization details currently: - Free - One-time payment - Recurring subscription - Micro transactions - Other In all these examples, checkout is handled by the developer outside of Adobe Express. Here are examples for each payment model based on a Brush Pack add-on in Adobe Express: | Your selection | End-user view: add-on listing Payment details | Example | |------------------------|------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Free | This add-on does not require any payment. | Explore our selection of free brush packs in Adobe Express. Access a range of essential brushes at no cost, perfect for getting started on your creative journey without any upfront payment. | | One time payment | Upgrade is available for a one-time purchase. | Upgrade to premium brushes with our one-time Brush Pack purchase in Adobe Express. Unlock a diverse range of high-quality brushes for a single payment, granting you unlimited creative potential without any recurring fees. | | Recurring subscription | Upgrade is available with a recurring subscription. | Subscribe to Premium and enjoy continuous access to our extensive library of Brush Packs. For a small monthly fee, access exclusive brushes and receive regular updates with new additions to fuel your creativity. | | Micro-transactions | You can purchase assets or features individually or in packages. | Customize your creative toolkit with individual brush packs available for purchase in Adobe Express. Choose from a variety of sets or opt for bundled packages to save. Pay only for what you need, empowering you to create without limits. | ## Recommendations for monetizing your add-ons We recommend following these tips to make your add-on more user-friendly and to avoid potential confusion: ### Offer a choice We recommend giving users payment options when signing up or upgrading. This includes: - A choice of payment methods, such as credit cards, PayPal and other popular alternatives. - A choice of payment terms. When signing up for a subscription or pay-as-you-go, provide short and long-term payment options, such as monthly and annual terms. - Giving users the chance to try before they buy. Free trials and freemium options can improve sign-up numbers. - Avoid using "Pro". - Adobe Express caters to non-professional and professional users, so avoid using "pro" in your language. - If "pro" refers to a pricing model, make this clear (for example, by saying "Pro Plan"). ### Consider user action Instead of using words such as "premium" or "gold" in your copy, you should try using words that make it clear what the user needs to do. Words such as "upgrade" or "subscribe" are clearer and more user-friendly. For example, instead of: ❌ *"You're using the trial version. Go Premium"* Try: ✅ *"You're using the trial version. Upgrade today"* Also, consider adjusting your language to make the benefits of upgrading clearer. For example, instead of: ❌ *"This item is premium"* Try: ✅ *"Subscribe to access unlimited illustrations like these"* ## Branding your add-ons for monetization When building your checkout experience to monetize your add-ons, you can use the approved colors, gradients, and iconography to communicate when content or features in your add-on require purchase and when content or features are paid and unlocked. ### Best practices Here are some guidelines for effectively communicating purchasing options and upgrades within your add-ons: - Use visual cues like the "plus" gradient badge to indicate that certain features require a purchase. The "plus" icon visually suggests that users can upgrade their experience. - Provide textual cues through tooltips to inform users about in-app purchase options. Consider using phrases like "add" alongside the plus badge to convey that users can access additional features or content by upgrading. - Use the "paid" green checkmark badge to signify when a feature or asset has been successfully added after purchase. - Use terms like "upgrade," "add," "Pro," and "Plus," for example: - Add this [feature] by upgrading the add-on. - Add more when you upgrade. - Upgrade to Pro. - Upgrade to [Add-on name] Plus. - At the beginning of the upgrade flow, let users know that they will have to navigate to an external payment processor to complete their upgrade purchase, for example: - "You'll need to pay to upgrade outside of Adobe." - Include a disclaimer in your checkout flow to remind users that upgrading the add-on does not change their access to Adobe Express Premium, for example: - "This upgrade only applies to the [Add-on name] add-on and does not grant or remove access to Adobe Express Premium." ### Patterns to avoid While our monetization best practices are recommendations for your add-on, implementing the following patterns to avoid them may result in a rejected submission. - 🚫 Don't use any crown icons, colors, or gradients associated with Adobe Express Premium. - 🚫 Don't use "Premium" for your add-on's upgrade experience. - 🚫 Don't use a lock icon because it conflicts with the "lock layer" feature in Express. If you previously implemented this pattern, we recommend updating to the "plus badge" in your next submission. ### Branding assets for monetization #### Plus badge Use the plus gradient icon below to indicate when content or features require purchase: | Badge | Size(px) | Download link | |--------|----------|-------------:| | | 20x20 |
Download | #### Paid badge Use the checkmark badge below to indicate when content or features are paid and unlocked: | Badge | Size(px) | Download link | |--------|----------|-------------:| | | 20x20 | Download | The most common size of badges used within Express panels is typically 20x20px, so we've included an easy download of that size specifically above. However, since it's an `.svg` icon, you could simply scale it to another size as needed. #### General badge recommendations - As a general rule, badges should be placed on the bottom right or right side of the paywalled content or feature. - The badge size should typically be 18px or 20px, but can be adjusted depending on the size of the content it is paired with. - We highly recommend adding a tooltip to let your users know what the badge indicates, i.e. *This content is available when you upgrade the add-on*. - Using the "paid" badge is optional. The paid badge makes the most sense in situations where you want to highlight specific content is now unlocked, in particular "micro-transactions". - If a user has paid for full access to an add-on through a one-time payment or subscription, then the "paid" badge is not really necessary.

| Sample badge usage | | | |----------|------|-------------:| | | | | For other examples of badge sizes and placement used in Adobe Express, check the **Templates** or **Media** panel for instance, where you'll see content marked with a crown icon used to denote Adobe Express Premium content. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Add-on Manifest title: Submission and Review - Overview description: This is the submission and review overview page contributors: - https://github.com/hollyschinsky --- # Distribution Overview Congratulations! You've built a great add-on and you're ready to release it to the community. To publish and share your add-on, it must first go through a review process. By reviewing every add-on, Adobe aims to help you get your add-ons ready for prime time and helps to ensures your users have great experiences with the add-ons they consume. Our goal is to balance providing you with the best possible developer experience during the review process, while also ensuring the approved add-ons offer a great user experience for our mutual customers. This set of guides is meant to provide you with an idea of what types of information you will need for the submission process and how to best prepare for review. Working through the guides provided in this section will help you make sure you’ve accounted for all of our review requirements, so you can avoid having to fix things and resubmit before being published. Check out this short video below on how to share and publish your add-on to help you get started more quickly.

# Add-on Listing Guide ### Listing Details Your listing metadata provides Adobe and users with details about the add-on you are currently submitting. See the [add-on version details below](#add-on-version-details) for the metadata that is submitted for each add-on version. The information you add in the following tabs will be made public to users via Adobe's Marketplace surfaces once your listing is published. #### General Tab - Public add-on name - Subtitle - Support email - Help URL - Description #### Localizations Tab Localized versions of: - Public add-on name - Subtitle - Description #### Media Tab - 3 add-on icon sizes #### Tags Tab - Categories - Custom Tags #### Services Tab - Privacy policy - Terms of service - Commerce: purchase method (paid or free) ### add-on Version Details Here you will provide add-on level details for each add-on version submitted. The information you add in the following tabs will be made public to users via Adobe's Marketplace surfaces once your version is published. #### General Tab - add-on package file (see the [section below](#add-on-package)) - If your add-on requires another application - If your add-on requires a 3rd party service - add-on UI supported languages - Release notes #### Localizations Tab Localized versions of: - Release notes #### Media Tab - Screenshots - Videos #### Add-on package As part of your submission, you will upload your add-on package. Take the following steps to create your add-on package. 1. Compress your add-on files as a **.zip** file - Select all files within your add-on's parent folder. On both macOS and Windows you can right-click to compress: **macOS**: Right-click > Compress items **Windows**: Right-click > Send to > Compressed (zipped) folder **Note:** You should _not_ compress the add-on's parent folder. Instead, compress the contents of the parent folder. Failure to do so will likely cause a rejection when submitting. #### Trader information This information will be displayed to only users in the European Union (EU) region in accordance with the [European Union Digital Services Act](https://eur-lex.europa.eu/legal-content/EN/ALL/?uri=CELEX:32022R2065) trader requirements. - Email - Telephone - Address --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Add-on Manifest title: Create a Private Distribution Link description: A guide to creating a private distribution link. contributors: - https://github.com/hollyschinsky - https://github.com/undavide --- # Create a Private Distribution Link ## Overview You can choose to create a private link to share your add-on with others to use or test by following the instructions outlined in this section. ## Prepare your add-on package In the process of creating a private link, you will be required to upload a zip of your add-on package. The CLI contains a handy script to help with this step. Before you proceed, open your terminal and navigate to the root of your add-on project, then run the following command. ```bash npm run package ``` The result will be a distributable zip of your add-on package with the name `dist.zip`, and can be uploaded in step 3 below. This add-on package contains the **production-ready built content** in the *root* of the zip file, similar to what's built into the `/dist` folder. ## Step 1: Create a new Add-on Listing In order to get a private distribution link, you will need to create a new add-on listing first; provided that you've enabled Add-on Development in your user's settings as described [here](../getting_started/quickstart.md#step-3-enable-add-on-development-mode-first-time-only), you can do so in two ways, which will invoke the same in-app distribution experience. 1. From the Adobe Express home page, click the Add-ons link in the left-hand navigation. ![Add-ons from home page](./img/add-ons-from-home-v2.png) 2. While loading a local add-on, click the **Manage add-ons** link in the Add-on Testing section. ![Manage link in launchpad](./img/manage-v2.png) In case you haven't created any listings for your add-ons yet, you will see the following. ![First add-on submission modal](./img/distrib-first-v2.png) If you have existing listings, instead, your first screen will display them, alongside the possibility of adding a new one. ![First screen of submission modal with existing listings](./img/distrib-existing-v2.png) Select **Create new** from either, and type the add-on name in the following modal dialog (25 characters max). Your add-on name will be validated when you tab out (or the field loses focus) before you will be allowed to move to the next step. You will know that it's verified by a green checkmark shown, or receive an error that it exists, and you need to choose another. ![Add-on name modal](./img/create-new-v2.png) ## Step 2: Add-on Listing Settings Your add-on container will be created and a settings panel like the one shown below will be presented. Please note the unique subdomain URL from where your add-on will be hosted, and a button to delete the listing if needed. ![](./img/subdomain-v2.png) ## Step 3: Create a new private link Navigate to the **Private link** tab, and click the **Create private link** button to proceed. ![Create Private link](./img/create-private-link-v2.png) ## Step 4: Upload your add-on package The next step is to upload your package. Either drag and drop the add-on package `.zip` file, or click the **browse** link to select the file from your computer's filesystem. In case you missed it, the [top section on preparing your add-on package](#prepare-your-add-on-package) can be used to help you create the zip file needed for this step. ![Empty upload modal](./img/create-private-link-package-v2.png) The package will go through a verification process which may take a few seconds, so please be patient. In case you receive an error, please review the following warning notes. **1.** If you receive a `MANIFEST_NOT_FOUND_ERROR`, instead of zipping the folder containing the add-on files, please zip only the contents. For example, manifest file would be at the **root** level of the extracted package. **2.** Your add-on package file size must not exceed 50 MB. **3.** In places where you are referring to paths, please ensure you are only using relative paths. **4.** Hidden files should not be present in your package zip. You can use this command on MAC to zip your add-on and to ensure unnecessary files are not included: `zip -r your_addon_name.zip . -x '**/.*' -x '**/__MACOSX' -x '*.DS_Store'`. The `package` script [described earlier](#prepare-your-add-on-package) takes care of this for you. ## Step 5: Enter add-on details If the `zip` validation is successful, you will see a green checkmark next to the **Add-on package verified** text, and you can add some Release Notes (1000 characters max) and a 144 x 144px icon. ![Verified](./img/create-private-link-details-v2.png) Once you've entered the required fields, the **Save and create private link** button will be enabled. The button will only be enabled if you have entered all of the required data. Also, upon clicking, it may take a moment to send the package and details to the backend server to generate the link, so please be patient. ![Submit data to create private link](./img/save-create-private-link-v2.png) When the process is finished, you'll be greeted by the following popup. Click the **Copy link and close** to copy your private link for sharing. ![Successfully created link](./img/create-private-link-success-v2.png) ## Post-Submission details and insights You can choose to revisit your submission details now if you want to copy, delete, update your link, or choose to create a public listing from it. It's possible also at a later time: in case, choose **Manage add-ons** from the add-on launchpad again, and then select your add-on submission. You will see the details and options available as shown in the screenshot below. ![private listing details](./img/create-private-link-review-v2.png) If you select the **Insights** tab, you'll be able to get analytics for your add-on, via the **Download** buttons. ![listing insights](./img/add-on-insights-v2.png) The insights come as `.csv` files named like your add-on, and appended with `_public` or `_private` depending on the listing type (e.g., `AFineAddOn_private.csv`). The insights data currently includes the number of installs, uninstalls and invocations of your add-on per week. A sample is shown below for reference: ![sample insights](./img/sample-insights.png) --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Add-on Manifest - DSA title: Public add-on distribution description: A guide to public distribution of your add-on. contributors: - https://github.com/hollyschinsky - https://github.com/undavide - https://github.com/nimithajalal --- # Public add-on Distribution ## Overview This guide is provided to help ensure your add-on distribution process goes as smoothly as possible. We've provided a list of all of the things you can prepare in advance, as well as the steps to follow to actually submit your add-on for public distribution. Please note that public distribution is subject to a quality review by our team according to our [Guidelines](./guidelines/index.md). ## Preparing for Submission This section outlines everything you'll need to be prepared for submitting your add-on for public distribution. ### 1. Prepare your metadata **\* Indicates Required** | Name | Character Length | Description | | -------------: | ------------------| -----------: | | **\* Add-on name** | 25 | A unique name for your add-on.| | **\* Summary** | 50 | A short description of what your add-on does.| | **\* Full Description** | 1000 | Full context and description of your add-on and its features | | **\* Help URL** | 1000 | URL for your users to get help (ie: https://www.example.com/) | | **\* Support email address** | 1000 | An email address that users of your add-on can contact for support | | **\*Trader information** | NA | Provide the trader information as per the [listing guidelines](./guidelines/general/listing.md#trader-details) in the publisher profile if you want to make your addons available in the EU | | **Privacy Notice** | 1000 | URL of your privacy notice (ie: https://www.example.com/) | | **End User License Agreement(EULA)**| 1000 | End User License Agreement URL (ie: https://www.example.com/) | | **Keywords** | 100 | Keywords to help users find your add-on (comma-separated) | | **Release notes** | 1000 | Provide information specific to this version of the add-on | ### 2. Prepare your assets | Type | Format | Description | | -------------: | ------------| -----------: | | * 144x144 icon | `.jpg/png` | a 144x144 sized icon representing your add-on | | * Screenshot | `.jpg/png` | a 1360x800 sized screenshot to show users how to use your add-on | | Additional screenshots | `.jpg/png` | 4 more optional 1360x800 sized screenshots for your add-on | | ** Publisher logo | `.jpg/png` | 250x250 sized logo to represent you or your company | A **publisher logo** is only required the first time you submit for distribution, and if you've never created a publisher profile. ### 3. Prepare your add-on package The CLI contains a handy script to help with this step. Before you proceed, open your terminal and navigate to the root of your add-on project, then run the following command. ```bash npm run package ``` The result will be a distributable zip of your add-on package with the name `dist.zip`, and can be uploaded in step 3 below. This add-on package contains the **production-ready built content** in the *root* of the zip file, similar to what's built into the `/dist` folder. ### 4. Carefully [review our set of guidelines](./guidelines/index.md) ## Submission Steps This set of steps can be followed when you have everything prepared, have diligently reviewed the guidelines, and are ready to submit your add-on for review via the Adobe Express in-app distribution experience. ### Step 1: Create a new Add-on Listing To distribute your add-on, you must create an add-on listing. If you have already performed the listing creation steps, e.g. to create a Private Link as outlined here, feel free to skip to [Step 3](#step-3-create-a-new-public-listing). Provided that you've enabled Add-on Development in your user's settings as described [here](../getting_started/quickstart.md#step-3-enable-add-on-development-mode-first-time-only), you can do so in two ways, which will invoke the same in-app distribution experience. **1.** From the Adobe Express home page, click the Add-ons link in the left-hand navigation. ![Add-ons from home page](./img/add-ons-from-home-v2.png) **2.** While loading a local add-on, click the **Manage add-ons** link in the Add-on Testing section. ![Manage link in launchpad](./img/manage-v2.png) In case you haven't created any listings for your add-ons yet, you will see the following. ![First add-on submission modal](./img/distrib-first-v2.png) If you have existing listings, instead, your first screen will display them, alongside the possibility of adding a new one. ![First screen of submission modal with existing listings](./img/distrib-existing-v2.png) Select **Create new** from either, and type the add-on name in the following modal dialog (25 characters max). Your add-on name will be validated when you tab out (or the field loses focus) before you will be allowed to move to the next step. You will know that it's verified by a green checkmark shown, or receive an error that it exists, and you need to choose another. ![Add-on name modal](./img/create-new-v2.png) ### Step 2: Add-on Listing Settings Your add-on container will be created and a settings panel like the one shown below will be presented. Please note the unique subdomain URL from where your add-on will be hosted, and a button to delete the listing if needed. ![](./img/subdomain-v2.png) ### Step 3: Create a new public listing Navigate to the **Public listing** tab, and click the **Create public listing** button to proceed. ![Public listing creation](./img/create-public-listing-v2.png) ### Step 4: Enter listing details The **"Create a public listing"** page contains a number of form inputs, grouped into logical sections that start as blank. ![Public listing blank](./img/public-listing-blank-v2.png) Fill the details with the requested information. The **Add-on name** must be unique, 25 characters max. It will be validated when you tab out (or the field loses focus) before you can move to the next step. You will know that it's verified by a green checkmark, or you'll receive an error, in which case you'll need to choose another. The **icon** must be of the size, 144 px. Once you upload an icon, it will be auto-resized into **Minimized add-on module icon(36 px)**, **Panel header icon (64 px)** and **Launchpad icon (144 px)**. ![Public listing blank](./img/public-listing-icon-resize.png) All the other **textual fields** have a character count that update with the remaining amount as you're typing into them. Please ensure your URLs and email addresses are properly formed to avoid unnecessary errors. The `*` indicates required fields. Note that you can skip entering these required fields if you are only planning to save a draft with your current edit, though you will not be able to submit it until they are completed. Please note the dropdown checklist below the **"Jump to"** label, in the top-left corner: you can use it to scroll to the relevant part of the document—complete sections are marked in green. Also note a Progress bar, indicating how far you are in the listing compilation. ![Public listing details jump to](./img/public-listing-jump-to-v2.png) ### Step 5: Upload screenshots In the next section, you should upload 1-5 screenshots to show off your add-on and what it's all about. Please note, at least one screenshot is required. Sometimes it may take a moment to upload the images to the back-end server, please be patient. ### Step 6: Upload your add-on package It's time to upload your package. Either drag and drop the add-on package `.zip` file, or click the **browse** link to select the file from your computer's filesystem. In case you missed it, the [top section on preparing your add-on package](#3-prepare-your-add-on-package) can be used to help you create the zip file needed for this step. The package will go through a verification process which may take a few seconds, so please be patient. In case you receive an error, please review the following warning notes. **1.** If you receive a `MANIFEST_NOT_FOUND_ERROR`, instead of zipping the folder containing the add-on files, please zip only the contents. For example, manifest file would be at the **root** level of the extracted package. **2.** Your add-on package file size must not exceed 50 MB. **3.** In places where you are referring to paths, please ensure you are only using relative paths. **4.** Hidden files should not be present in your package zip. You can use this command on MAC to zip your add-on and to ensure unnecessary files are not included: `zip -r your_addon_name.zip . -x '**/.*' -x '**/__MACOSX' -x '*.DS_Store'`. The `package` script [described earlier](#3-prepare-your-add-on-package) takes care of this for you. If the `zip` validation is successful, you will see a green checkmark next to the **Add-on package verified** text; you can then add some Release Notes (1000 characters max) and check the add-on's supported languages. ### Step 7: Enter the AI usage details The rise of Generative AI offers significant benefits for add-ons and streamlines content creation and workflows. Adobe encourages user choice regarding add-ons using Generative AI, but transparency is paramount. Your AI-powered add-on must not generate illegal content, and it must be clear and transparent about how generative AI is used in your add-on. In this section, you'll have to answer a variety of questions, depending on the type of AI-based content your add-on generates, the input it accepts, whether you test the output, etc. Carefully review our [AI usage guidelines](./guidelines/genai/index.md) to get the latest information on Adobe’s requirements and recommendations to try add-ons that employ Generative AI technology. ### Step 8: Enter the monetization details The **Monetization details** section allows developers to declare the payment option they support for their add-on. A selection is required for any new add-on submitted, and existing add-ons can be updated to include or change the selection. The monetization details entered can be seen in the preview of the listing (on the right) before submission, and in the add-on details once published. ![Monetization details](./img/public-listing-monetization-v2.png) Developers can choose from various payment options, including **free**, **one-time payments**, **recurring subscriptions**, **micro-transactions**, and more. Select the monetization options that suit your preferences best. Use the [examples](./guidelines/monetization.md#requirements-for-monetizing-your-add-ons) outlined in the guidelines for monetizing add-ons to help you make informed decisions about which options to choose. - The *Other* option is provided for developers to choose when their current setup does not fit the provided options. - The final *additional details* text area allows developers to provide additional payment terms like *"7 day free trial"* or *"$9.99/month"* and is optional for all payment choices except *Other*. We encourage the use of this field to clearly state any specific payment details. Do check in the live preview how the listing will appear to users. Depending on the payment selection, different details will automatically be displayed in the add-on listing. If the payment choice selected is not free, an **Upgrade available** badge will be displayed in the details along with specific default text describing the choice selected (ie: "*...for a one-time purchase*", "*...with a recurring subscription*", "*...purchase assets or features individually or in packages*"), and **Checkout is handled by the developer outside of Adobe Express**—as shown in the previous screenshot. Any additional custom details entered by the developer are then shown below the default checkout message, as well as a timestamp indicating when the listing was last updated. In the case of the **free** payment selection, the following text simply be shown: "This add-on does not require any payment". Carefully review our [monetization guidelines](./guidelines/monetization.md) to get the latest information on Adobe’s requirements and recommendations for monetizing your add-ons. ### Step 9: Create a publisher profile You will only see this step the first time you submit an add-on and if you've never created a publisher profile before to this submission. - Fill your publisher profile details. - Upload a 250x250 logo. - Add your trader details: In accordance with the European Union Digital Services Act trader requirements, developers who wish to distribute their listings in the EU must provide additional information in their publisher profile. [Learn more](./guidelines/general/listing.md#trader-details) about adding trader details. #### Edit publisher profile The existing developers can now edit their publisher profile to add trader details. Choose **Yes** if you wish to make your add-ons available for users in the EU. **Are you an existing developer?** You must provide trader details by February 16, 2025, to keep your add-on visible and available in Adobe Express for users in the European Union as of February 17, 2025. This trader information will be displayed publicly on your listing detail pages when viewed from EU countries. [Add trader details now.](https://new.express.adobe.com/add-ons?mode=submission) ![Publisher Profile](./img/pub-profile2.png) ### Step 10: Final submission - Enter your **Notes to reviewer** - Add there any relevant information for the vetting team, including coupon codes that may allow them to test premium features for free. Carefully review all the information entered. - Click the **Submit for review** button in the top-right corner. ![Submitting the listing](./img/public-listing-submission-v2.png) The **Submit for review** button will only be enabled if you have entered all of the required data. In case there are any errors, a message will be displayed at the bottom of the page. Follow the instructions to fix them, and try submitting again. Eventually, the submission will be successful. Congratulations! Click **View submission details** to see the details of your add-on submission. You can choose to revisit your submission details later if you need to update it, or if you want to download insights for your add-on. To do so, choose **Manage add-ons** from the add-on launchpad again, and then select your add-on listing. Add-on visbility for EU users If an EU user has a deep link to your add-on, and you are not compliant with the [European Union Digital Services Act](https://eur-lex.europa.eu/legal-content/EN/ALL/?uri=CELEX:32022R2065) trader requirements, they will not be able to install the add-on. However, if they have already installed it, they will still be able to use it. In both cases, they will see a banner with the following message: *This listing is not currently available in the EU. This developer has not submitted the trader information required by the EU Digital Services Act.* ## Post-Submission details and insights When the add-on will be published, you will see the details, as shown in the screenshot below. ![public listing details](./img/public-listing-published-v2.png) If you select the **Insights** tab, you'll be able to get analytics for your add-on, via the **Download** buttons. ![listing insights](./img/add-on-insights-v2.png) The insights come as `.csv` files named like your add-on, and appended with `_public` or `_private` depending on the listing type (e.g., `AFineAddOn_private.csv`). The insights data currently includes the number of installs, uninstalls and invocations of your add-on per week. A sample is shown below for reference: ![sample insights](./img/sample-insights.png) --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Add-on Manifest title: Common rejection causes description: A guide to Common add-on's rejection causes contributors: - https://github.com/undavide --- # Common Rejection Causes and How To Avoid Them ## 📋 Overview When you submit an add-on for distribution, the review team *thoroughly* examines it to ensure it meets the [guidelines](./guidelines/index.md) and is safe for public distribution. If something goes wrong, you will receive an email with the reasons that led to a rejection. You shouldn't be discouraged, though! Usually, the fixes are relatively straightforward, and the detailed feedback from the reviewers is an opportunity to improve and learn. That said, being rejected is annoying and, most importantly, time-consuming, as multiple re-submissions will impact the time it takes to get your add-on published. To help you avoid it, we've compiled a list of the most common reasons for review failures and how to address them. ## 🔧 Functional issues As trivial as it may sound, the add-on not working as expected is the **most common cause** of trouble. This can be anything from a simple failure to the inability to log in. Here are some of the most common functional issues the reviewers have reported and our suggestions to prevent them. ### Broken features Problems can be directly related to the **add-on's core functionality**, e.g., failures in exporting from and importing assets into the document or performing server-side processing. Sometimes, bugs are sneaky and only appear under specific conditions, which our reviewers have proved *exceptionally talented* in finding—just so you know. Test your add-on thoroughly and make sure it works in all scenarios, edge cases included. ![](./img/rejections-functional-issues.png) ### Minor bugs The reviewers will also reject your add-on if they find less critical problems that still **affect the user experience**. Issues could vary from missing event handlers to 404 links and broken icons. Do your best to catch them all before submitting your add-on. ![](./img/rejections-404-error.png) ### Authentication problems In case your product requires users to log in, make sure the **authentication workflow is operating properly**, and don't forget to *always* add a logout option. If present, reviewers should also be provided with working **credentials to test premium features**, a key requirement for add-ons that offer paid services. ![](./img/rejections-authentication-failed.png) ### Browser compatibility Adobe Express [officially supports](https://helpx.adobe.com/express/system-requirements.html#system-requirements-web) the four most popular browsers: **Chrome, Safari, Edge, and Firefox**. Your add-on must be tested and work seamlessly across all of them. If any problems discussed so far are found in even one of these browsers, the reviewers are bound to reject it. ![](./img/rejections-browser-compatibility.png) ## 📐 UI/UX Issues Both the User Interface and Experience—i.e., how information is presented to users and how they interact with it—are crucial elements for the success of your products. Here are some common pitfalls that can prevent your add-on from being approved. ### Navigation problems The UI should always provide an intuitive and **functional way to navigate the add-on's screens**. It should also offer a method to return to the previous screen or home page, especially if a PDF or webpage is opened in the iframe and overrides its entire content. If users can't find a clear path to follow and get stuck, the reviewers will throw the ball back in your court and ask you to revise your code. ![](./img/rejections-navigation-problems.png) ### Error handling Every **user interaction should always return clear feedback**. For instance, progress indicators or text notices should be displayed when the add-on runs a process in the background to signal that it is actually doing something and is not idle or frozen. Input fields should have proper validation to avoid errors with out-of-bounds values in your routines, and displayed errors should be informative and actionable. Additional information should be provided as tooltips or text to clarify why elements are disabled or actions are unavailable. It's relatively common to forget about these details, but they are a typical cause of review failures; make sure to remember them. ![](./img/rejections-error-feedback.png) ## 📝 Recent changes to review criteria Due to changes in the testing and reviewing processes, some issues that used to cause rejections aren't considered blocking anymore. Please find below some of the most recent ones. ### Relaxed UI Requirements Using the [Spectrum Design System](../design/implementation_guide.md#spectrum-design-system) is **no longer mandatory**, provided that the add-on's UI follows the best practices outlined in the [UX Guidelines](../design/ux_guidelines/introduction.md) and is well-crafted. Spectrum Web Components and the Spectrum Express theme are still the recommended options, as they reliably provide a native look and feel. Mind you, malfunctioning or poorly designed UIs will always be rejected regardless of the design system used. ### COEP issues The reviewing team **no longer tests** for [COEP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy) (Cross-Origin Embedder Policy), which used to cause, e.g., broken images. [CORS](../develop/context.md#cors) (Cross-Origin Resource Sharing) policies are still enforced, though, so make sure your add-on doesn't break due to them. # Our Review Process ## Overview We’ve created the Adobe Express Add-ons marketplace so that Adobe users can benefit from amazing ideas from our developer community - including you. Whether you’re creating new features or speeding up user workflows, we want you to use your imagination. But we want every add-on to perform as thorough as possible, which is why each submission goes through a thorough review process before being accepted. ## Review Criteria Our marketplace reviewers will assess your submission based on a variety of factors, including: - Branding - Performance - Accessibility - Harmful or unacceptable content - User Experience - Transparency - Presence of bugs or harmful code - Compatibility with operating systems, browsers, devices and other add-ons **We aim to review your add-on within 10 business days of submission, and will let you know if it is accepted, or if any changes need to be made.** ## Submission Checklist To make sure your review process goes smoothly, check off the tasks in this list before submitting. If you have any questions, feel free to contact us at [ccintrev@adobe.com](mailto:ccintrev@adobe.com) or reach out on our [Adobe Express Add-on Developer’s Discord channel](http://discord.gg/nc3QDyFeb4). ### 1. Make sure you’ve included all required files in your add-on submission, as per the [Creating a public listing documentation](../public-dist.md), including: - Files - Assets - Release notes - Testing information ### 2. Provide accurate and up-to-date information, including: - Add-on name - Version - Author - Contact information - Trader infromation in the publisher profile if you wish to distribute your add-ons in the EU region. ### 3. Ensure your add-on meets legal and licensing requirements, including: - Attribution - Copyright - Intellectual property rights - Share testing credentials with the review team so they can validate functionality ### 4. Make sure the add-on meets our [Developer Brand Guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf) ### 5. Ensure that you have read and followed all Adobe guidelines relating to your add-on, including: - Our [Developer Brand Guidelines](../guidelines/brand_guidelines.md) - Our [Monetization Guidelines](../guidelines/monetization.md) (if you are monetizing your add-on) - Our [Generative AI Guidelines](../guidelines/genai/) (if you are using Generative AI) # Submission Checklist To make sure your review process goes smoothly, check off the tasks in this list before submitting. If you have any questions, feel free to contact us at [ccintrev@adobe.com](mailto:ccintrev@adobe.com) or on our [Adobe Express Add-on Developer’s Discord channel](http://discord.gg/nc3QDyFeb4). ## Make sure you’ve included all required files in your add-on submission, as per the [submission guidelines](../public-dist.md#preparing-for-submission), including: - Files - Assets - Release notes - Testing information ## Provide accurate and up-to-date information, including: - Add-on name - Version - Author - Contact information - Trader infromation in the publisher profile if you wish to distribute your add-ons in the EU region. - Business D-U-N-S number [`optional`] ## Ensure your add-on meets legal and licensing requirements, including: - Attribution - Copyright - Intellectual property rights - Share testing credentials with the review team so they can validate functionality ## Make sure the add-on meets the [Developer Brand Guidelines](https://developer.adobe.com/express/embed-sdk/docs/assets/34359598a6bd85d69f1f09839ec43e12/Adobe_Express_Partner_Program_brand_guide.pdf) ## Check your add-on and resources to make sure it is NOT described as a “plugin” anywhere ## Ensure that you have read and followed all Adobe guidelines relating to your add-on, including: - [Developer Brand Guidelines](../guidelines/brand_guidelines.md) - Our [Monetization Guidelines](../guidelines/monetization.md) (if you are monetizing your add-on) - Our [Generative AI Guidelines](../guidelines/genai/) (if you are using Generative AI) --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Add-on Manifest - DSA - Adobe DSA requirements - Compliance title: FAQ description: A list of frequently asked questions and answers. contributors: - https://github.com/hollyschinsky - https://github.com/undavide - https://github.com/nimithajalal --- # Frequently Asked Questions ## Questions - [How do I run on a different port than the default (ie: 8080 for example)?](#how-do-i-run-on-a-different-port-than-the-default-ie-8080-for-example) - [Is `yarn` supported with the CLI, or only `npm`?](#is-yarn-supported-with-the-cli-or-only-npm) - [How do I save the state of my add-on?](#how-do-i-save-the-state-of-my-add-on) - [How do I use top level `await` while using webpack?](#how-do-i-use-top-level-await-while-using-webpack) - [How do I setup webpack to copy new files or folders into `dist`?](#how-do-i-setup-webpack-to-copy-new-files-or-folders-into-dist) - [My form submission doesn't work and the devtools console shows the error: "Blocked form submission to " " because the form's frame is sandboxed and the 'allow-forms' permission is not set." What's wrong?"](#my-form-submission-doesnt-work-and-the-devtools-console-shows-the-error-blocked-form-submission-to---because-the-forms-frame-is-sandboxed-and-the-allow-forms-permission-is-not-set-whats-wrong) - [How do I enable CORS for a service that blocks my add-on requests due to the origin?](#how-do-i-enable-cors-for-a-service-that-blocks-my-add-on-requests-due-to-the-origin) - [How do I prevent my iframe content from being blocked due to cross-origin issues?](#how-do-i-prevent-my-iframe-content-from-being-blocked-due-to-cross-origin-issues) - [The `Window.showOpenFilePicker()` API is not working from within my add-on, why not?](#the-windowshowopenfilepicker-api-is-not-working-from-within-my-add-on-why-not) - [I’m not able to load the add-on in the browser anymore. When I click on "Connect”, I get an error `ERR_CERT_AUTHORITY_INVALID`.](#im-not-able-to-load-the-add-on-in-the-browser-anymore-when-i-click-on-connect-i-get-an-error-err_cert_authority_invalid) - [I receive this error when trying to run my add-on: `Error: EISDIR: illegal operation on a directory`.](#i-receive-this-error-when-trying-to-run-my-add-on-error-eisdir-illegal-operation-on-a-directory) - [I receive a `MANIFEST_NOT_FOUND_ERROR` during the package verification when trying to upload my plugin package for distribution.](#i-receive-a-manifest_not_found_error-during-the-package-verification-when-trying-to-upload-my-plugin-package-for-distribution) - [How can I monetize my add-on?](#how-can-i-monetize-my-add-on) - [What does it mean when an API is considered **experimental**?](#what-does-it-mean-when-an-api-is-considered-experimental) - [What are the supported mime types/file formats for exported content?](#what-are-the-supported-mime-typesfile-formats-for-exported-content) - [What are the supported file formats for imported content in Adobe Express?](#what-are-the-supported-file-formats-for-imported-content-in-adobe-express) - [Are animated GIF's supported when importing or dragging content to the document?](#are-animated-gifs-supported-when-importing-or-dragging-content-to-the-document) - [Why do I receive a "No 'Access-Control-Allow-Origin' header is present on the requested resource" error?](#why-do-i-receive-a-no-access-control-allow-origin-header-is-present-on-the-requested-resource-error) - [Is `SharedArrayBuffer` supported?](#is-sharedarraybuffer-supported) - [Which browsers and operating systems are currently supported?](#which-browsers-and-operating-systems-are-currently-supported) - [How does Adobe use my add-on’s data?](#how-does-adobe-use-my-add-ons-data) - [Where can I request new add-on features or suggest ideas?](#where-can-i-request-new-add-on-features-or-suggest-ideas) - [Why does the CLI return the error: "Login failed. Please try again.", though I didn't have a chance to login because the browser never opened?](#why-does-the-cli-return-the-error-login-failed-please-try-again-though-i-didnt-have-a-chance-to-login-because-the-browser-never-opened) - [What mime type is returned from a PDF that was exported with the `createRenditions` method?](#what-mime-type-is-returned-from-a-pdf-that-was-exported-with-the-createrenditions-method) - [The latest version of the CLI is not automatically installing when I run the `npx` command to create a new add-on.](#the-latest-version-of-the-cli-is-not-automatically-installing-when-i-run-the-npx-command-to-create-a-new-add-on) - [I'm trying to use a newly released feature, but it seems to be unavailable?](#im-trying-to-use-a-newly-released-feature-but-it-seems-to-be-unavailable) - [Why is my add-on not visible in the EU region?](#why-is-my-add-on-not-visible-in-the-eu-region) - [How can I update my trader details in the publisher profile after submission?](#how-can-i-update-my-trader-details-in-the-publisher-profile-after-submission) - [What happens if an EU user has a deep link to my add-on and I am not compliant with the European Union Digital Services Act (DSA) trader requirements?](#what-happens-if-an-eu-user-has-a-deep-link-to-my-add-on-and-i-am-not-compliant-with-the-european-union-digital-services-act-dsa-trader-requirements) - [Can an EU user still use my add-on if they have already installed it, but I am not compliant with the DSA trader requirements?](#can-an-eu-user-still-use-my-add-on-if-they-have-already-installed-it-but-i-am-not-compliant-with-the-dsa-trader-requirements) - [Why is the CLI failing with an Invalid URL error when creating a new add-on on Windows?](#why-is-the-cli-failing-with-an-invalid-url-error-when-creating-a-new-add-on-on-windows) ## Answers ### How do I run on a different port than the default (ie: 8080 for example)? Use the following syntax: ```bash npm run start -- --port 8080 ``` ### Is `yarn` supported with the CLI, or only `npm`? We recommend using `npm` for running the CLI scripts. Note that while there might be workarounds to get `yarn` working, we do not recommend it, or support any issues that may arise using `yarn`. ### How do I save the state of my add-on? The add-on's state is reset quite frequently (changing panels, changing viewport widths etc), so one may want to save state to [ClientStorage](../references/addonsdk/instance-clientStorage.md) and use that to restore state when the add-on loads. For example, if the user has to navigate into a deep folder hierarchy, they may not want to repeat that again just because they clicked the media panel to add a shape. Or if they are editing a form (e.g., an AI prompt), they may not want to lose that content when they navigated to another panel for a moment. When it makes sense to store a lot of UI state (and when it doesn't) is highly dependent upon the add-on's use case. ### How do I use top level `await` while using webpack? Set `experiments: { topLevelAwait: true}` in the webpack config file (otherwise you'll get a build error). ### How do I setup webpack to copy new files or folders into `dist`? If you add any folders, (like images for example), to your `src`, you can update the `webpack.config.js` `CopyWebpackPlugin` section within to ensure those new resources added are copied into the `dist` folder. For instance, in the following, the 3rd line was added to ensure any `.jpg` files in the `src/images` folder get copied over: ```js new CopyWebpackPlugin({ patterns: [ { from: "src/*.json", to: "[name][ext]" }, { from: "src/*.png", to: "[name][ext]" }, { from: "src/images/*.jpg", to: "images/[name][ext]" }, ], }); ``` ### My form submission doesn't work and the devtools console shows the error: "Blocked form submission to " " because the form's frame is sandboxed and the 'allow-forms' permission is not set." What's wrong?" You can call `preventDefault` on the submit event to prevent the browser from trying to complete the full form submission process and avoid this error, such as: ```js
{ evt.preventDefault(); }} /> ``` **NOTE:** If the above does not work for you, you can also handle this by adding click handler to the submit button itself instead, and in that call `event.preventDefault` on the event, such as: ```javascript e.preventDefault()}> e.preventDefault()} />
``` ### How do I enable CORS for a service that blocks my add-on requests due to the origin? To help enable a smoother experience for developers dealing with CORS, we provide each add-on with a unique [subdomain](../guides/develop/context.md#subdomain) which can be supplied in the list of [allowed origins](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) that can make requests to a given service. See the section on [CORS](../guides/develop/context.md#cors) for more details on determining your unique subdomain and using it to enable CORS. ### How do I prevent my iframe content from being blocked due to cross-origin issues? If your iframe is being blocked by the browser, it's likely due to CORS issues. To resolve this, you need to set the appropriate HTTP headers on the server that hosts the content being loaded within the iframe. Specifically, you need to set the [`Cross-Origin-Embedder-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy) header to `require-corp`. ### The `Window.showOpenFilePicker()` API is not working from within my add-on, why not? You can open the file picker using the `input` element with a `type` set to `file` to get around this. ### I’m not able to load the add-on in the browser anymore. When I click on "Connect”, I get an error `ERR_CERT_AUTHORITY_INVALID`. This usually indicates an issue with invalid SSL credentials. Locate the `devcert` folder which can be found at `/Users/{your_username}/Library/Application\ Support/devcert` on MAC or `C:\Users\{your_username}\AppData\Local\`, delete it, and create an add-on again. You should get the option to create an SSL certificate again when you create the new add-on, which should resolve your problem. ### I receive this error when trying to run my add-on: `Error: EISDIR: illegal operation on a directory`. This usually indicates you do not have SSL configured correctly. You can fix it by clearing the configurations from the configuration file. - In Windows, you can locate this file at: `C:\Users\{your_username}\AppData\Local\Adobe\CCWebAddOn\add-on-preferences.json`. - On MAC, you can locate this file at: `/Users/{user}/Library/Application Support/Adobe/CCWebAddOn\add-on-preferences.json` Once you find config file, delete the two properties defined for `sslCertPath` and `sslKeyPath` there. After they've been deleted, you can run the commands to create a new add-on where you will be prompted to set up SSL again and then be sure to specify the correct paths to your certificate and key file. ### I receive a `MANIFEST_NOT_FOUND_ERROR` during the package verification when trying to upload my plugin package for distribution. Instead of zipping the folder containing the add-on files, please zip only the contents. In other words, manifest file should be at **root** level of the extracted package. ### How can I monetize my add-on? At this time, the only way to monetize is by using a third party provider, and ensuring you choose one that provides safety measures, security and proper payment processing. Some options you may want to consider include **Gumroad**, **Stripe**, **Paddle** and **FastSpring**. Find out more about how you can communicate your monetization details to users in our [monetization guidelines](../guides/distribute/guidelines/monetization.md#branding-your-add-ons-for-monetization). ### What does it mean when an API is considered **experimental**? Experimental APIs are those which have not been declared stable yet, and to try them, first need to set the `experimentalApis` flag to `true` in the [`requirements`](../references/manifest/index.md#requirements) section of the [`manifest.json`](../references/manifest/index.md). The `experimentalApis` flag is **only allowed during development** and needs to be removed during submission. Experimental APIs should never be used in any add-ons you will be distributing. ### What are the supported mime types/file formats for exported content? The supported file types for exported content are **"image/jpeg" (jpg format), "image/png" (png format), "video/mp4" (mp4 format)** and **"application/pdf" (pdf format)**. ### What are the supported file formats for imported content in Adobe Express? The supported file types for imported audio content are **aac, adts, ai, avi, crm, f4v, gif, jpeg, jpg, m1v, m2p, m2t, m2ts, m4a, m4v, mov, mp3, mp4, mpeg, mpg, msvideo, mts, png, psd, psdt, quicktime, ts, tts, wav, webm, webp, wmv, xm4a, xwav, 264, 3gp**. ### Are animated GIF's supported when importing or dragging content to the document? Yes, however, there are [technical requirements](https://helpx.adobe.com/express/create-and-edit-videos/change-file-formats/import-gif-limits.html) and certain handling for each scenario. The requirements are summarized below for reference, and the handling that will take place when importing vs drag and drop follow: - **Maximum resolution:** 1080px - **Maximum size:** 10 MB - **Maximum GIFs per scene:** 7 **Importing gifs:** You should use the [`addAnimatedImage()`](../references/addonsdk/app-document.md#addanimatedimage) method when you want to import an animated GIF by default. It will be added as an animated GIF to the document as long as it fits [the size criteria for animated GIF's](https://helpx.adobe.com/express/create-and-edit-videos/change-file-formats/import-gif-limits.html). In the event that it does not fit the criteria, only the first frame will be added. **Note:** Though [`addImage()`](../references/addonsdk/app-document.md#addaudio) supports the `gif` file type, if an animated GIF is passed in, only the first frame will be added. **Drag and drop:** If the content being dragged is an animated GIF, it will be added as an animated GIF to the document, as long as it fits [the size criteria for animated GIF's](https://helpx.adobe.com/express/create-and-edit-videos/change-file-formats/import-gif-limits.html). In the event that it doesn't fit the size criteria, an error toast will be shown to the user. ### Why do I receive a "No 'Access-Control-Allow-Origin' header is present on the requested resource" error? This error message indicates that the server that the JavaScript code is making a request to did not include the proper CORS (Cross-Origin Resource Sharing) headers in its response. Please see [this section on CORS](../guides/develop/context.md#cors) for more details on handling CORS with your add-on. ### Is [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) supported? No, `SharedArrayBuffer` is not currently available to use with your add-ons. ### Which browsers and operating systems are currently supported? Please see the [Adobe Express System requirements](https://helpx.adobe.com/express/system-requirements.html) for what's currently supported. ### How does Adobe use my add-on’s data? Adobe only instruments and reports on user behaviors related to discovering and managing their add-on, including searching for, installing, running, sharing (via link), and uninstalling an add-on. Adobe does not instrument, store, or report on any action that the user takes within the add-on’s UI. Therefore, Adobe does not capture any user data related to 3rd party services, licenses, or user accounts, including personal information, except that which is already made directly available to Adobe through general usage of our products. ### Where can I request new add-on features or suggest ideas? You can head over to the [Adobe Express UserVoice forum](https://adobeexpress.uservoice.com/forums/951181-adobe-express) to request features, suggest integration ideas and more. ### Why does the CLI return the error: "Login failed. Please try again.", though I didn't have a chance to login because the browser never opened? This can happen due to a permissions issue, and the `~/Library/Application Support/Adobe/CCWebAddOn` doesn't get created. This can be fixed by creating the folder and modifying the permissions to allow write. ### What mime type is returned from a PDF that was exported with the `createRenditions` method? The mime type returned from a PDF generated using the [`createRenditions`](../references/addonsdk/app-document.md#createrenditions) API is `application/pdf`. ### The latest version of the CLI is not automatically installing when I run the `npx` command to create a new add-on. You can force it to install by clearing the npx cache first with `npx clear-npx-cache`, or by specifying the version in the command, i.e.: `npx @adobe/create-ccweb-add-on@1.1.1 my-add-on`. ### I'm trying to use a newly released feature, but it seems to be unavailable? If you are trying out a newly released feature in your add-on and have an instance of Adobe Express still open in a browser tab from before, you will need to refresh the page to ensure the latest release is loaded before trying out a new feature. ### Why is my add-on not visible in the EU region? This could be due to incomplete or outdated trader information in your [publisher profile](https://new.express.adobe.com/add-ons?mode=submission). Please make sure all required details are updated and accurate. ### How can I update my trader details in the publisher profile after submission? To update only your trader details in the publisher profile after submission, please contact our team at [ccintrev@adobe.com](mailto:ccintrev@adobe.com). At this time, we are not processing change requests for other fields in the publisher profile. ### What happens if an EU user has a deep link to my add-on and I am not compliant with the European Union Digital Services Act (DSA) trader requirements? If you are not compliant with the European Union Digital Services Act trader requirements, an EU user with a deep link to your add-on will not be able to install it. They will see a banner with a message indicating the compliance issue. ### Can an EU user still use my add-on if they have already installed it, but I am not compliant with the DSA trader requirements? Yes, if an EU user has already installed your add-on, they will still be able to use it even if you are not compliant with the DSA trader requirements. However, they will see a banner with a message indicating the compliance issue. ### Why is the CLI failing with an Invalid URL error when creating a new add-on on Windows? There's a known issue with running certain versions of Node.js on Windows with the CLI currently. Specifically, `v22.14.0` and `v18.20.7` have been found to fail with the following error: ```bash Creating a new Add-on ... This may take a minute ... √ Please select a template which you want to scaffold the Add-on project with » [javascript]: Get started with Add-on development using JavaScript √ Do you want to include document sandbox runtime? » Yes err:: TypeError: Invalid URL at new URL (node:internal/url:818:25) at Object.fileURLToPath (node:internal/url:1505:12) at WxpAddOnFactory._copyTemplateFiles (file:///C:/Temp/abc/node_modules/@adobe/create-ccweb-add-on/dist/app/WxpAddOnFactory.js:131:39) at WxpAddOnFactory.create (file:///C:/Temp/abc/node_modules/@adobe/create-ccweb-add-on/dist/app/WxpAddOnFactory.js:90:18) at process.processTicksAndRejections (node:internal/process/task_queues:105:5) at async CreateCCWebAddOn.run (file:///C:/Temp/abc/node_modules/@adobe/create-ccweb-add-on/dist/commands/create.js:90:9) at async CreateCCWebAddOn._run (C:\Temp\abc\node_modules\@oclif\core\lib\command.js:108:22) at async Config.runCommand (C:\Temp\abc\node_modules\@oclif\core\lib\config\config.js:328:25) at async Object.run (C:\Temp\abc\node_modules\@oclif\core\lib\main.js:89:16) { code: 'ERR_INVALID_URL', input: '.\\file:\\C:\\Temp\\abc\\node_modules\\@adobe\\create-ccweb-add-on\\dist\\templates\\javascript-with-document-sandbox' } Aborting installation. Unexpected error. Please report it as a bug: TypeError: Invalid URL ``` If you encounter this issue, please update your Node.js version to `v20.11.0` and try again. --- keywords: - Adobe Express - Express Add-on SDK - Adobe Express Add-on Development - Express Editor - Code Playground - In-app editor - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Add-on Manifest - Add-on dev tool - Express Document title: Code Playground description: A guide to using the Code Playground in Adobe Express. contributors: - https://github.com/padmkris123 - https://github.com/hollyschinsky - https://github.com/ErinFinnegan - https://github.com/undavide --- # Code Playground The Code Playground is an in-app lightweight code editor for fast and effortless prototyping. ## What is Code Playground? Code Playground provides developers with a low-barrier entry point for add-on development, allowing you to experiment and iterate on ideas directly without any setup, from within Adobe Express. From learning the basics to rapidly prototyping advanced concepts, Code Playground accommodates all stages of add-on development. ## Who Should Use Code Playground? The Code Playground is designed for: - **Beginners**: New developers who want to experiment with Adobe Express add-on development without setting up a full development environment. - **Prototypers**: Developers who need to quickly test concepts or ideas before implementing them in a full add-on project. - **Learners**: Those who are learning the Document APIs and want to see immediate results of their code. - **Experienced Developers**: Seasoned developers who want to test specific API functionality or debug isolated code snippets. - **Designers**: UX/UI designers who want to experiment with add-on interfaces without extensive coding setup. ## Features
| Feature | Description | |---------|-------------| | **Real-Time Preview** | See your changes as you code, allowing for immediate feedback and faster adjustments. | | **Effortless Prototyping** | Quickly turn ideas into add-ons with minimal setup. | | **Rapid Implementation** | Fast-track your prototype to a product by directly pasting your code into an add-on template. | | **Script Mode** | An easy way to interact with the Document APIs quickly. | | **Programming Assistance** | Typed definitions and auto-completion. | | **Default Boilerplate Code** | Default boilerplate code for each tab helps you get started quickly. | | **Local Persistence** | Save your work to your browser's local storage and resume where you left off, preventing accidental loss. | | **Keyboard Shortcuts** | Use keyboard shortcuts to save, run, and reset your code quickly. | ## Development Workflow Use Cases The Code Playground is designed to support the following development workflow use cases: - **Experiment First**: Test your ideas and API interactions before committing to full add-on development. - **Learn as You Go**: Master the basics of the Document APIs and [Add-on SDK](../../references/addonsdk/index.md) without complex setup requirements. - **Prototype Quickly**: Build and test features in minutes instead of hours with instant feedback. - **Bridge to Production**: Develop core functionality in Playground before moving to a complete project environment. - **Debug with Ease**: Isolate and fix specific issues by testing API calls outside your production code. ## How to Access Code Playground ### Step 1: Enable Add-on Development Mode - Click the avatar icon in the top right corner of Adobe Express, then the gear icon to open the "Settings". - Enable **Add-on Development** if it's not already enabled: ![Adobe Express Settings](./img/settings_alt.png) ### Step 2: Open Code Playground - With any document open, click the **Add-ons** button in the left rail. - Select the **Your add-ons** tab. - Toggle on **Code Playground** at the bottom of the panel: ![Adobe Express Code Playground Toggle](./img/toggle-playground.png) - Once enabled, the playground window will open, allowing you to begin coding immediately: ![Adobe Express Code Playground Open](./img/playground-open.png) ## Choose Your Development Mode The playground offers two distinct development modes: - [**Script Mode**](#script-mode): Experiment with the Adobe Express [Document Sandbox](../../references/document-sandbox/index.md). This mode is equivalent to writing code in the `sandbox/code.js` file in an add-on project running locally, but allows you to rapidly test in Express directly. - [**Add-on Mode**](#add-on-mode): Test and iterate on your [Add-on UI](../../references/addonsdk/) and functionality with no setup required.
| Comparison Factor | Script Mode | Add-on Mode | |--------------------|-------------|-------------| | **Purpose** | Quick document manipulation tests | Complete add-on UI and functionality | | **Environment** | Document Sandbox only | Both iframe and Document Sandbox | | **API Access** | [Document APIs](../../references/document-sandbox/document-apis/index.md) | [Document APIs](../../references/document-sandbox/document-apis/index.md) + [Add-on UI SDK](../../references/addonsdk/index.md) | | **Global Await** | Yes | No | | **Automatic Imports** | Yes | No | | **UI Components** | No UI building | Full HTML/CSS/JS interface creation | | **Best For** | Testing document operations | Building complete add-ons | ## Script Mode ### When to Use Script Mode - To learn how the Document APIs work - To quickly experiment with Document API calls without UI considerations **Note:** The code you write in this mode is equivalent to the code you would write and use in the `sandbox/code.js` file in an add-on project running locally. ### How to Use Script Mode 1. Select the **Script** button in the top left corner of the playground window. 2. Enter your [Document API](../../references/document-sandbox/document-apis/index.md) code in the editor. Manipulate the document directly, add shapes or text, change styles, and more using the automatically available [`editor`](../../references/document-sandbox/document-apis/classes/Editor.md) object. 3. Execute your script by clicking the **Run Code** button in the right corner of the playground window to see changes in the current document. ![Code Playground Script Mode](./img/script-mode.png) 4. If you want to use Document APIs that are currently marked experimental, click on the properties icon to open the [Manifest JSON](../../references/manifest/index.md#requirements) editing modal and toggle **experimentalApis**: ![Script Mode Manifest Settings](./img/manifest-props-script.png) 5. Head over to our [How-to guides](../develop/how_to.md) to see some examples of using the Document APIs with example code snippets. For instance, the guides: - [How to Use Geometry](../develop/how_to/use_geometry.md) - [How to Use Color](../develop/how_to/use_color.md) - [How to Use Text](../develop/how_to/use_text.md) #### Key Considerations - **No UI**: Script mode is focused on Document API interactions and does not support building a user interface. If you want to create a UI, switch to [Add-on mode](#add-on-mode). - **Global Await**: The script runtime provides a global `async` wrapper, allowing you to use `await` directly when executing asynchronous code, without needing to wrap it in an `async` function. This is particularly useful for API calls that return promises, where an `await` is needed to pause the execution of an `async` function until the Promise is resolved or rejected. For example, loading a font is an asynchronous operation, but in Script mode you can use `await` directly to pause the execution of the script until the font is loaded, ie: ```js // The script runtime provides an async wrapper to allow this: const textNode = editor.context.selection[0]; const lato = await fonts.fromPostscriptName("Lato-Light"); ``` In contrast, in [**Add-on mode**](#add-on-mode) you will need to manually wrap the code in an `async` function and use `await` in it, ie: ```js //sandbox.code.js or Document JS tab loadFont: async () => { const textNode = editor.context.selection[0]; const lato = await fonts.fromPostscriptName("Lato-Light"); } ``` - **Automatic Imports**: Script mode automatically imports the `express-document-sdk` modules, so you don't need to add import statements for the [Document APIs](../../references/document-sandbox/document-apis/index.md). However, if you do add import statements, it wont harm anything. Once you switch to the [Add-on mode](#add-on-mode) or to your local add-on development environment, you will need to make sure to handle your `async` functions and `import` statements manually. ## Add-on Mode ### When to Use Add-on Mode - To develop and test an add-on directly in Adobe Express, without having to set up a full development environment. - To prototype an add-on before building a full project. - To iterate quickly on your add-on's UI and logic. ### How to Use Add-on Mode 1. Click on the **Add-on** button (next to the **Script** button in the top left corner of the playground window). 2. Write code for your add-on in each of the supplied tabs (described below). This includes HTML, CSS, and JavaScript code that will run in the iframe UI or in the Document Sandbox to interact directly with the Express document (optionally). 3. Click **Run Code** to execute your add-on. Your add-on should open in an iframe on the right side of the Adobe Express window, ie: ![Code Playground Add-on Mode](./img/addon-mode.png) 4. If you need to set [manifest properties](../../references/manifest/index.md) for your add-on (ie: if you want to use APIs that are currently marked experimental, set permissions, OAuth domains etc), click on the properties icon to open the Manifest JSON editing modal: ![Add-on Mode Manifest Settings](./img/manifest-props-addon.png) ### Add-on Mode Tabs The Add-on mode features four tabs for organizing your code: 1. **HTML Tab** This tab is for writing HTML code that defines the structure of your add-on's user interface. You can create elements like buttons, text fields, and layout containers here. Functionally, this tab mirrors the role of the `index.html` file you'd use in a typical add-on project. 2. **CSS Tab** Style your add-on's HTML elements in this tab. Create a visually appealing interface consistent with Adobe Express design patterns. This section corresponds to the `styles.css` file in a standard add-on project. 3. **Iframe JS Tab** This tab is for writing JavaScript code that runs in the iframe context of your add-on. Here, you can interact with: - The [Add-on UI SDK (`addOnUISdk`)](../../references/addonsdk/index.md) - The DOM elements in your HTML - Event handlers for your UI components This environment corresponds to the code you would typically write in your `index.js` or UI JavaScript files in a full add-on project. 4. **Document JS Tab** This tab is where you write JavaScript code that interacts directly with the Adobe Express document. It runs in the [Document Sandbox](../../references/document-sandbox/index.md) environment and gives you access to: - Document manipulation capabilities with the [Document APIs](../../references/document-sandbox/document-apis/index.md) - [Communication APIs](../../references/document-sandbox/communication/index.md) to facilitate interaction between the iframe context and the Document Sandbox. The Document JS tab corresponds to the code typically found in the `code.js` file of a complete add-on project. ## Transitioning from Script Mode to Add-on Mode Once you've tested your code in Script mode, you can easily transition it into the [Add-on mode](#add-on-mode) to build a user interface around your new functionality. Here's how: 1. Use the **Copy** button in the right corner to quickly copy your code to the clipboard. 2. Click the **Add-on** button to enter [Add-on mode](#add-on-mode). 3. Paste the code into the [**Document JS**](#add-on-mode-tabs) tab. **Note:** Don't forget you'll need to add the `import` statements for the Document APIs and handle your `async` functions manually in this mode. 4. Modify your script code to be used in the add-on context along with your front-end logic in the [**HTML**](#add-on-mode-tabs), [**Iframe JS**](#add-on-mode-tabs), and [**CSS**](#add-on-mode-tabs) tabs. Use the initial sample code provided as a reference. 5. If you set any manifest properties (ie: **experimentalApis**) while in [Script mode](#how-to-use-script-mode), make sure to set the same in the [Add-ons mode - Edit Manifest JSON Modal](#how-to-use-add-on-mode) as well. These settings only apply to the context of the development mode you're in. 6. Click the **Run Code** button to execute your code within the context of your add-on. ## Workflow Tips Keyboard Shortcuts, local save and session management are all designed to help you get the most out of the Code Playground. ### Keyboard Shortcuts
| Action | Windows/Linux | macOS | |--------|---------------|-------| | **Save** | Ctrl + Shift + S | Cmd + Shift + S | | **Run** | Ctrl + Shift + Return/Enter | Cmd + Shift + Return/Enter | | **Reset** | Ctrl + Shift + X | Cmd + Shift + X | | **Increase font size** | Ctrl + Shift + Plus (+) | Cmd + Shift + Plus (+) | | **Decrease font size** | Ctrl + Shift + Minus (-) | Cmd + Shift + Minus (-) | | **Switch between tabs** | Ctrl + 1, 2, 3, 4 | Cmd + 1, 2, 3, 4 | | **View the typings suggestions** | Ctrl + space | Cmd + space | #### TIP Use the "**...**" button in the top right corner of the playground window to reference the available keyboard shortcuts, start a new session, link to documentation and more. ### Saving Your Work The Code Playground features local persistence to help prevent the loss of your work. This functionality ensures that your code is stored in your browser's local storage, providing a safeguard against accidental data loss. Code in the playground is ***not saved automatically***. To ensure it's saved, you need to take one of the following steps: 1. Save your work using the [keyboard shortcut for Save](#keyboard-shortcuts). 2. Run the code via the **Run Code** button or with the [keyboard shortcut for Run](#keyboard-shortcuts). 3. Exit the playground (with the **X** in the upper right corner). If you don't want to save your work at any time, use the [keyboard shortcut to Reset](#keyboard-shortcuts). #### IMPORTANT - Only your most recent session is saved. - Storage is browser-specific (not synced across devices). - Code is not saved in incognito/private browsing modes. - Clearing browser data will delete saved code. ### Resuming Sessions There are two ways to resume working on your last saved session: 1. **Via the Add-ons Panel:** - With any document open, click the **Add-ons** button in the left rail. - Select the **Your add-ons** tab. - Toggle on **Code Playground** at the bottom of the panel. ![Code Playground Add-on Mode](./img/playground-on.png) 2. **Via the Your add-ons Page:** - The **Your add-ons** page where you manage your add-ons now features a dedicated section for the playground, allowing you to quickly access your last session or create a new one. - Find the **Playground Sessions** section in the **Your add-ons** page. - Access your last session or create a new one with one click. ![Manage Your add-ons page](./img/playground-sessions.png) #### Accessing "Your add-ons" Page - **Without a document open:** Click the **Add-ons** button in the left rail, then click the **Add-on development** toggle in the top right. - **With a document open:** Click the **Add-ons** button in the left rail, select the **Your add-ons** tab, then click the "Manage add-ons" link in the Add-on Testing section. ## Resources - **How-To Guides:** Begin by experimenting with the code snippets found in our [how-to guides](../develop/how_to.md) to kickstart your development. - **SDK/API References:** Discover more about what you can do in your add-on by exploring our [SDK References](../../references/index.md). - **Code Samples:** Get inspired by checking out [our code samples](../../samples.md) to see what's possible. - **Ask Questions:** Chat with fellow developers on [Discord](http://discord.gg/nc3QDyFeb4). ## Next Steps: Build Your Add-on Locally After experimenting with the Code Playground and when you're ready to build out a full-blown add-on in a local development environment: 1. Follow our [Quickstart Guide](../getting_started/quickstart.md) to get your environment set up and your first add-on project created quickly. 2. Copy the code from the Code Playground [Add-on mode tabs](#add-on-mode-tabs) to the corresponding files in your new project. **Note:** Don't forget, if you're copying code from Script mode into your `sandbox/code.js` file, you'll need to add the `import` statements for the Document APIs and handle your `async` functions manually. 3. Copy the JSON from the [Manifest JSON Editor](#how-to-use-add-on-mode) in the Code Playground into the `src/manifest.json` file in your new project. 4. Run your add-on locally using the [Adobe Express CLI](../getting_started/quickstart.md) to test and see your changes in real-time. ## FAQs ### What is the Adobe Express Code Playground? The Adobe Express Code Playground is a lightweight code editor designed for fast and effortless prototyping. It allows you to experiment with simple code snippets to build and refine add-ons, quickly turning ideas into functional features. ### Is it free to use? Yes, the Code Playground is free to use. You can access all its features without any cost and start prototyping and creating add-ons right away. ### Do I need coding experience? While some basic coding knowledge is helpful, Playground is designed to be beginner-friendly and accessible. Its intuitive interface and simple code snippets make it easier for both experienced developers and those newer to coding to create and test add-ons. ### How do I start creating add-ons? Getting started is simple. activate the playground, experiment with code snippets, and start building your add-ons. Use the real-time preview feature to see your changes instantly and iterate on your ideas with ease. ### Where can I go for help? [Join our Discord](http://discord.gg/nc3QDyFeb4) to chat with the add-on developer community. --- keywords: - Adobe Express - Express Add-on SDK - Express Editor - Adobe Express - Add-on SDK - SDK - JavaScript - Extend - Extensibility - API - Add-on Manifest title: Quickstart description: This is the Quickstart page contributors: - https://github.com/hollyschinsky - https://github.com/undavide --- # Development Tools ## Using the CLI The add-on CLI (Command Line Interface) is the main tool that enables you to develop, test, and package add-ons for our platform. With the add-on CLI, you can create a new add-on project, build and test your add-on locally, and package your add-on for distribution. Here are some key features of the add-on CLI: - **Project creation:** The add-on CLI provides a command to create a new add-on project with a basic file structure and configuration. - **Local development:** The add-on CLI includes a built-in server that allows you to test your add-on locally before deploying it to our platform. - **Live reloading:** The add-on CLI watches your project files for changes and automatically reloads the server when a change is detected. - **Packaging:** The add-on CLI provides a command to package your add-on for distribution, including creating a ZIP file that can be uploaded to our platform. ### CLI `create` options The table below shows the list of arguments that can be specified with the CLI create command (ie: `npx @adobe/create-ccweb-add-on`): | Argument | Optional | Default Value | Description | | ------------- | -------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `add-on-name` | No | | Name of the add-on. A new add-on project with this argument will be created in the user's current working directory. | | `template` | Yes | none, you will
be prompted from the CLI | The template to use for creating the add-on. | | `verbose` | Yes | false | Setting this argument enables the verbose flag on the underlying operations. | For instance, the following command would specify all possible arguments: ```bash npx @adobe/create-ccweb-add-on my-addon --template react-typescript --verbose ``` #### CLI troubleshooting See the [templates](#templates) section for the currently supported template values. `npx` is an `npm` package runner that can execute packages without installing them explicitly. If needed, please run this command to clear the `npx` cache to ensure the latest version of the CLI is invoked. ```bash npx clear-npx-cache npx @adobe/create-ccweb-add-on my-addon ``` The above may prove useful when updated versions of the CLI are released. If you want to read each individual CLI command manual page, run them via `npx` with the `--help` flag, for example: ```bash npx @adobe/ccweb-add-on-scripts start --help ``` ### `start` script options The table below shows a list of arguments that can be specified with the `start` script on your add-on project, which starts up the add-on in a local server: | Argument | Optional | Default Value | Description | | --------- | -------- | ------------- | ---------------------------------------------------------------------------- | | `src` | Yes | `src` | Directory where the source code and assets for the add-on is present. | | `use` | Yes | | Transpiler/bundler to be used. For example, webpack. | | `port` | Yes | `5241` | Local development server port. | | `verbose` | Yes | false | Setting this argument enables the verbose flag on the underlying operations. | For instance, to specify a port of `8080` instead, use the following command: ```bash npm run start -- --port 8080 ``` To specify you want to use `webpack` AND port `8080`: ```bash npm run start -- --use webpack --port 8080 ``` The extra arguments are unnecessary unless you do not want to use a transpiler/bundler or use the default port of `5241`. Also, note that all of the templates other than the `javascript` template are pre-configured to use webpack by default and the `--use webpack` is automatically added when you run the `build` and `start` commands. Take a look at the `scripts` property in the `package.json` of those templates and you will see the following: ```json "scripts": { "clean": "ccweb-add-on-scripts clean", "build": "ccweb-add-on-scripts build --use webpack", "start": "ccweb-add-on-scripts start --use webpack" } ``` ## Templates The add-on CLI contains built-in, pre-configured templates to allow you to create an add-on project based on your favorite development stack in the quickest possible manner. There are currently five base template options based on popular web development trends. The table below summarizes the templates and their associated frameworks.
| Template | Framework | | ---------------- | ---------------- | | `javascript` | JavaScript | | `swc-javascript` | JavaScript with Spectrum Web Components support | | `swc-typescript` | TypeScript with Spectrum Web Components support | | `react-javascript` | React with JavaScript | | `react-typescript` | React with TypeScript | As well as the following five template options, which include support for the [Document Sandbox APIs](../../references/document-sandbox/): | Template | Description | | ---------------- | ---------------- | | `javascript-with-document-sandbox` | JavaScript with Document Sandbox support. | | `swc-javascript-with-document-sandbox` | JavaScript and Spectrum Web Components with Document Sandbox support. | | `swc-typescript-with-document-sandbox` | TypeScript and Spectrum Web Components with Document Sandbox support. | | `react-javascript-with-document-sandbox` | React and JavaScript with Document Sandbox support.| | `react-typescript-with-document-sandbox` | React and TypeScript with Document Sandbox support.| You can supply any of the above template names after the `--template` parameter: ```bash npx @adobe/create-ccweb-add-on --template