--- url: https://plugin.stripo.email/introduction.md --- # Introduction In today’s world, where security, data processing speed, and effective collaboration play critical roles in business success, Stripo presents an updated email editor. Our goal is to provide users with not just a tool but a comprehensive solution that meets modern design and interaction requirements. The [first version](https://stripo.email/plugin-api/) of our editor was highly praised for its intuitiveness and ease of use. However, considering user feedback, we identified the need to expand its capabilities for more comprehensive collaboration and interface customization as well as to eliminate technical limitations that slowed down the handling of large data volumes and caused issues with maintaining style consistency between the editor and users’ applications. ::: image-wrap ![](/img/plugin/new/image1.webp){height=1268 width=1922} ::: The updated Stripo editor addresses these challenges. Innovative and flexible, it supports real-time co-editing and commenting by multiple users, enabling effective teamwork. With flexible interface settings, everyone can adapt the workspace to their needs. Built-in artificial intelligence and a personal assistant enhance productivity by automating routine processes and improving content quality. This editor is not just a tool for creating emails — it’s an intelligent component that ensures secure and reliable data storage and flexibly regulates user access rights to different parts of an email while maintaining high processing speed, even for very large emails. It revolutionizes the approach to email code editing, allowing multiple users to make changes in real time without the risk of conflicts or data loss. This makes it an ideal tool for businesses of all sizes seeking flexibility, speed, and efficiency in their work. --- --- url: https://plugin.stripo.email/architecture-and-integration.md --- # Architecture and Integration ## Integration Methods The new Stripo editor is built on a microservices architecture, effectively combining front-end and back-end components. ::: image-wrap ![](/img/plugin/new/image2.webp){height=511 width=901} ::: Understanding the diverse needs of our users, we offer two main ways to use the editor: 1. **Standard mode:** The entire editor infrastructure is deployed on Stripo’s servers. This is an ideal choice for those who value quick implementation and convenience without additional costs. **Advantages:** * ease of implementation; * cost-effective solution. **Disadvantages:** * data storage on Stripo's side, which may not comply with the data retention policies of some organizations; however, the data is hosted in a [secure manner](https://trust.stripo.email/) as we follow GDPR and are SOC2 compliant. Our servers are hosted with AWS in the Ireland region. * limited opportunities for customizing infrastructure capabilities. 2. **Extended mode:** The ability to deploy the editor's infrastructure on the client's servers, providing full control and flexible management. This option is possible only with Enterprise plan. **Advantages:** * data privacy due to storage on the client's side; * full control and flexible management of the infrastructure. **Disadvantages:** * higher solution cost; * the need for the client's DevOps team to set up and maintain the [infrastructure](https://github.com/stripoinc/stripo-plugins-helm-example/) (Stripo is ready to provide engineers to assist in deployment or consult the client's team). These integration methods offer clients flexibility in choosing based on their technical requirements and business needs, allowing for the optimal use of the editor. ## Email Storage and Synchronization The new Stripo editor is designed for reliable data storage, effective conflict resolution during simultaneous editing by multiple users, and flexible user rights management for working with an email. Unlike the old editor, where emails were transferred to the client’s environment via webhook, the decision was made to store emails in Plugins’s database. This ensures that editor always maintains a “reference email” that can be edited simultaneously by multiple users. ::: image-wrap ![](/img/plugin/new/image3.webp){height=232 width=350} ::: Stripo retains the ability to receive information about changes to an email via [webhook](editor-configuration/server-webhooks). However, from now on, this will only include data about the time of the change and the user identifier. Once the webhook is received, the plugin customer may send a request from their server to the Stripo server [here](/reference/editor-api-retrieving-html) to get the HTML and CSS to securely store on their end the most updated version of the template. **Key aspects of working with the editor:** * if a new email is created, the provided HTML and CSS parameters will create a “reference email” in Stripo’s database, which users will then begin to work on; * if a user joins an email that is already being edited by others, the provided HTML and CSS parameters are ignored, and the user will be connected to editing the “reference email”; * replacing the “reference email” can only be done when no one is working on the email in the editor. During editor initialization, besides the HTML and CSS parameters, a special additional parameter `forceRecreate: true` must be provided (see the [table of all parameters](editor-configuration/initialization-settings)). * The compiled version of email can be retrieved with GET request to Stripo server [here](/reference/editor-api-compiling-email-templates). --- --- url: https://plugin.stripo.email/getting-started/creating-an-application.md --- # Creating an Application To create the plugin application, please follow the steps below: 1. Open the site: 2. Click on the “Get started for Free” button; ::: image-wrap ![](/img/plugin/new/image4.webp){height=385 width=450} ::: 3. Create your own Stripo account or open the current one; 4. Go to Settings > Plugin and press “Create application” button; ::: image-wrap ![](/img/plugin/new/image5.webp){height=788 width=1999} ::: 5. Add the Name of the plugin and company website (\*required); 6. Done. You have created the plugin. The code sample that displays the editor in your environment can be found in the Quick Start section. You can download or copy it from the repository. **Please notice, you have to add your own Plugin ID and Secret key from the app to the code.** ::: image-wrap ![](/img/plugin/new/image6.webp){height=1244 width=1816} ::: More configuration options can be found in the [Initialization Settings](/editor-configuration/initialization-settings) section. --- --- url: https://plugin.stripo.email/getting-started/connecting-the-editor.md --- # Connecting the Editor Connecting the editor is a straightforward process consisting of several steps: 1. ## Preparing the editor space Determine where the editor will be placed in the HTML structure of your application. Use a container with a unique identifier for this purpose. For example: ```html
``` 2. ## Adding the editor script Add a ` ``` **For a specific version of the editor:** ```html ``` To check the available versions of Stripo Plugin, please refer to [Release Notes](https://stripo.email/releases/?page=1\&subtype=new\&type=plugin) (make sure you’re viewing the versions of the new Plugin). If you wish to host the front-end part of the editor on your server to improve loading times, please note that this is possible under any pricing plan. Follow this [guide](/hosting-stripo-editor-files-on-your-own-cdn) to learn how to host the front-end assets of the editor on your CDN. 3. ## Initializing the editor Use JavaScript code to initialize the editor with your chosen configuration in the specified container on the webpage. ```js const domContainer = document.querySelector('#stripoEditorContainer'); const stripoConfig = {...}; window.UIEditor.initEditor(domContainer, stripoConfig); ``` ::: info-clear To initialise the editor inside an existing Angular application, you need to wrap the init method, as in the example ```js constructor( private readonly zone: NgZone // provide NgZone ) {} ngOnInit(): void { //... YOUR CODE HERE // wrap Editor initialisation this.zone.runOutsideAngular(() => { const domContainer = document.querySelector('#stripoEditorContainer'); const stripoConfig = {...}; window.UIEditor.initEditor(domContainer, stripoConfig); }); //... YOUR CODE HERE } ``` ::: You can find all supported parameters for the plugin configuration in the [Initialization Settings](/editor-configuration/initialization-settings) section. Once the `initEditor` function is triggered, the user will see the editor displayed on the UI as shown in the image below. ::: image-wrap ![](/img/plugin/new/image7.webp){height=1051 width=1999} ::: Please be advised that you can control the default position of the Settings panel and Blocks and Modules panel by passing `panelPosition` parameter while plugin initialization. It may have two possible states: * ”BLOCKS\_SETTINGS” — blocks and modules panel on the left, settings panel on the right; * ”SETTINGS\_BLOCKS” — settings panel on the left, blocks and modules panel on the right --- --- url: https://plugin.stripo.email/getting-started/authentication.md --- # Authentication Authentication enables us to understand the details of your account, including the opportunities and limitations you have. For users, it ensures a real-time connection with our server, providing confidence that everything is functioning correctly. The Stripo Plugin requires an authentication token to perform any operations. To authenticate your instance of the Stripo Plugin, call the endpoint shown in the sample code below with your Plugin ID and Secret Key, which you can find on the Plugin details page. The Plugin will call this function each time a token expires to obtain a new one. It is recommended to build authentication as shown in the diagram below: ::: image-wrap ![](/img/plugin/new/image8.webp){height=798 width=1999} ::: ## Authentication Flow 1. **Initialization of the Editor:** During the initialization of the editor, pass a function as the `onTokenRefreshRequest` parameter. This function will be called whenever the authentication token needs to be updated. ```js init({ ..., onTokenRefreshRequest: function(callback) { /* Send request to Customer Application Backend, for example https://your_domail/stripo/token */ const token = ... callback(token); } }); ``` 2. **Implement an Endpoint in CAB:** The customer application backend (CAB) must implement an endpoint to handle the request for obtaining a token. 3. **Retrieve pluginId and secretKey:** During request processing, the CAB must retrieve the `pluginId` and `secretKey` from storage. 4. **Send Request to Plugin Backend:** Along with the required `userId` parameter (user identifier) and `role`, the CAB must send a request to the plugin backend to obtain a token. 5. **Receive Generated Token:** In the response from the plugin backend, a generated token will be returned. 6. **Return Token to Customer Application UI:** The CAB returns the token to the customer application UI. 7. **Pass Token to Editor:** The customer application UI must call the callback to pass the token to the editor. ## OpenAPI Specification ```yaml openapi: 3.0.1 info: title: Stripo Authentication API description: | The Stripo Plugin requires an authentication token to perform any operations. To authenticate your instance of the Stripo Plugin, call the provided endpoint with your Plugin ID and Secret Key. Ensure you include the userId of the user who will be working within the plugin, along with their role. The list of supported roles can be found [here](/getting-started/authentication#default-roles) version: 1.0.0 servers: - url: https://plugins.stripo.email paths: /api/v1/auth: post: tags: - Methods summary: Get authentication token description: | [Recommendations for use](/getting-started/authentication) operationId: getAuthToken requestBody: content: application/json: schema: $ref: '#/components/schemas/AuthRequest' required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/AuthResponse' components: schemas: AuthRequest: type: object required: - pluginId - secretKey - userId - role properties: pluginId: type: string description: The value from your plugin configuration page example: PID123456 secretKey: type: string description: The value from your plugin configuration page example: SK654321 userId: type: string description: String value of a user identifier example: "1" role: type: string description: String value of a user role in the editor. For example, "ADMIN", "USER" or "API". Use "API" value for backend-to-backend requests example: USER enum: - ADMIN - USER - API AuthResponse: type: object required: - token properties: token: type: string description: Authentication token for the editor example: TK123456 ``` ## Roles You can use these roles to configure access levels to folders in the Image gallery and the Library of modules. * admin * user To enable your users to write data to specific folders, pass the appropriate roles during token generation. This will allow you to control user access to the [Image gallery](../editor-configuration/image-gallery) and the [Library of modules](../editor-configuration/modules-library) effectively. In addition, there is a separate **api** role intended **exclusively for server-to-server communication** between your backend and Stripo’s backend endpoints described in the Backend API section. This role must **not be used in tokens generated on the client side**. Otherwise, such tokens could be obtained from the user’s browser and used to perform unauthorized requests to access other users’ resources within your plugin. We **strongly recommend** using the `api` role for all authorization processes during backend requests. Requests authenticated with `admin` or `user` roles may be **rejected by our servers** for security reasons when used for backend API calls. --- --- url: https://plugin.stripo.email/getting-started/template-requirements.md --- # Template Requirements ## Stripo Markup in HTML Each email template must include Stripo-specific classes in the HTML to enable the drag-and-drop feature. These classes allow users to customize templates easily using the Stripo editor. Templates without these classes will only support basic editing options like image replacement, text changes, and link management. This ensures that the templates are fully compatible with Stripo's advanced editing capabilities. For detailed information, refer to the [Reference Article](https://stripo.email/blog/advanced-option-email-templates-adaptation-stripo-builder/). Below are examples of required classes and their uses:\ **Examples:** * **Stripe:** * `
...
`: This class defines a major section or row in the template, allowing users to manage large segments of content. * **Structure:** * `...
`: Used to create columns within a stripe, providing a framework for organizing content. * **Container:** * `...`: This class holds content blocks within a structure, ensuring proper alignment and spacing. * **Basic Blocks:** * **Image Block:** `
...
`: Enables users to add and customize images. * **Text Block:** `
...
`: For adding and editing text content. * **Button Block:** `
...
`: Used for call-to-action buttons. * **Spacer Block:** `
...
`: Adds space between elements. * **Social Block:** `
...
`: Includes social media icons and links. * **HTML Block:** `
...
`: Allows for the insertion of custom HTML code. * **Menu Block:** `
...
`: Creates navigation menus. * **Banner Block:** `
...
`: Used for adding a banner image. * **Timer Block:** `
...
`: Used to display a countdown timer. * **Video Block:** `
...
`: Used for embedding videos. * **AMP Carousel Block:** ``: Used for adding an AMP carousel.. * **AMP Accordion Block:** `
...
`: Used to create an AMP accordion. * **AMP Form Block:** `
...
`: Used for embedding an AMP form. **Usage Notes:** * **Stripe:** Defines a horizontal section that spans the width of the email, helping to segment the email into distinct areas. * **Structure:** Creates columns within a stripe, facilitating organized content placement. * **Container:** Holds the actual content blocks within each structure, maintaining the layout and design integrity. * **Basic Blocks:** Different types of content that can be dragged and dropped into containers, enabling a modular approach to email design. ## Migration from Old Plugin Previously, HTML and CSS were stored and passed separately during plugin initialization. ::: image-wrap ![](/img/plugin/new/image36.webp){height=380 width=1280} ::: For migration to the new editor, HTML and CSS can still be passed separately the first time the template is opened. Subsequent initializations will ignore these separate parameters, relying instead on the referenced email according to the [Email Storage and Synchronization section](../architecture-and-integration#email-storage-and-synchronization). ## Access to Stripo Templates Plugin customers can retrieve Stripo templates for use within their applications. For detailed information, refer to the [Access to Stripo Templates](../plugin-invocations/backend-api#access-to-stripo-templates) section. --- --- url: https://plugin.stripo.email/getting-started/v2-migration-guide.md --- # Stripo Editor v2 Migration Guide This guide is primarily for users with prior experience using Stripo Editor v1, and it aims to clarify the changes and improvements introduced in v2. The guide will help you seamlessly transition from v1 to v2 by explaining the key differences in syntax, initialization, and functionality. ## Editor Space **v1 Syntax** In Stripo Editor v1, two containers were required for the settings panel and the preview area: ```html
``` **v2 Syntax** In Stripo Editor v2, this has been simplified to a single container for the entire editor: ```html
``` This change reduces the complexity of the HTML structure and streamlines the initialization process. ## Editor Script **v1 Syntax** For the latest version: ``` https://plugins.stripo.email/static/latest/stripo.js ``` For a specific version: ``` https://plugins.stripo.email/static/rev/[version]/stripo.js ``` **v2 Syntax** For the latest version: ``` https://plugins.stripo.email/resources/uieditor/latest/UIEditor.js ``` For a specific version: ``` https://plugins.stripo.email/resources/uieditor/rev/[version]/UIEditor.js ``` ## Editor Initialization **v1 Syntax** Initialization required specifying the IDs of the settings and preview containers, along with calling the `window.Stripo.init` function: ```js const stripoConfig = { settingsId: '[put here ID of your settings container]', previewId: '[put here ID of your preview container]', [put here your plugin JSON configuration] } window.Stripo.init(stripoConfig); ``` **v2 Syntax** Initialization now involves directly passing the DOM element of the editor container to the `window.UIEditor.initEditor` function: ```js const domContainer = document.querySelector('#stripoEditorContainer'); const stripoConfig = {...}; window.UIEditor.initEditor(domContainer, stripoConfig); ``` This change simplifies the initialization process and provides more flexibility by allowing the direct use of DOM elements. ## Initialization Parameters **v1 Syntax** The v1 initialization required multiple specific parameters: * `settingsId` * `previewId` * `html` * `css` * `apiRequestData -> emailId` * `getAuthToken` ```js const stripoConfig = { settingsId: '[put here ID of your settings container]', previewId: '[put here ID of your preview container]', html: '[put here HTML code of your email]', css: '[put here CSS code of your email]', apiRequestData: { emailId: '[put here ID of email in your external application]' }, getAuthToken: function(callback) { const token = // get token from backend callback(token); } } ``` **v2 Syntax** In v2, the initialization is more streamlined with fewer required parameters and additional optional ones for enhanced functionality: * `html` (only for new emails opened for the first time) * `css` (only for new emails opened for the first time) * `metadata -> emailId` * `onTokenRefreshRequest` Optional, but recommended for co-editing mode: * `username` * `avatarUrl` * `onUserListChange` ```js const stripoConfig = { metadata: { emailId: '[put here ID of email in your external application]', username: '[put here full user name]', avatarUrl: '[put here URL of user avatar image]', }, html: '...', css: '...', ​​onTokenRefreshRequest: function(callback) { const token = // get token from backend callback(token); }, onUserListChange: function(usersList) { // Update user avatars in custom header logic } } ``` These changes provide greater flexibility and support new features like real-time co-editing. ## Editor Termination **v1 Syntax** To stop all processes when the editor is closed: ```js window.StripoApi.stop(); ``` **v2 Syntax** This has been updated in v2 to: ```js window.UIEditor.removeEditor(); ``` The new method ensures that all editor resources are properly released, improving overall application performance. ## Authentication **v1 Syntax** The POST request body for authentication in v1: ```json { "pluginId": "YOUR_PLUGIN_ID", "secretKey": "YOUR_SECRET_KEY", "role" : "PLUGIN_EDITOR_USER_ROLE" } ``` **v2 Syntax** In v2, an additional `userId` parameter is required to enhance security and user-specific interactions: ```json { "pluginId": "YOUR_PLUGIN_ID", "secretKey": "YOUR_SECRET_KEY", "role" : "PLUGIN_EDITOR_USER_ROLE", "userId": "ID_OF_CURRENT_USER" } ``` This update provides better tracking and management of individual users within the editor. ## External Header Panel **v1 Syntax** Initialization parameters used to configure external header panels: * `codeEditorButtonId` * `undoButtonId` * `redoButtonId` **v2 Syntax** IDs were replaced with CSS selectors for more flexibility, and additional button selectors were introduced: * `codeEditorButtonSelector` * `undoButtonSelector` * `redoButtonSelector` * `mobileViewButtonSelector` * `desktopViewButtonSelector` * `versionHistoryButtonSelector` The `onUserListChange` callback was also added to update user avatars during co-editing sessions. ## Initialization Parameters * Some initialization parameters changed their names or were replaced with new ones with extended meaning. * All parameters that are responsible for external application notification about something and require functions to be provided are now started from the “on” prefix. Example: `onSaveStarted()` * All parameters that are responsible for external decisions and require functions to be provided are now started from the “should” prefix. Example: `shouldBeSavedToLibrary()` ```js metadata: { username } ``` ```js draft: { showAutoSaveLoader: function() { console.log('Auto save in process') }, hideAutoSaveLoader: function(error) { console.log('Auto save completed') } } ``` ```js onSaveStarted: function() { console.log('Auto save in process'); }, onSaveCompleted: function(error) { console.log('Auto save completed'); } ``` ``` https://plugins.stripo.email/static/latest/assets/i18n/en.json ``` ``` https://github.com/stripoinc/stripo-plugin-releases/blob/main/static/assets/i18n/en.json ``` ```js blocks: { moveBlockAvailability: true } ``` ```js modules: { syncModulesEnabled: true } ``` ```js imageLibrary: { needHideImageUrlFunc: function(url) { return url.includes('test.com'); } } ``` ```js shouldHideImagePath: function(url) { return url.includes('test.com'); } ``` ## JavaScript API **v1 Syntax** ```js const stripoEditorApi = window.StripoApi; ``` **v2 Syntax** ```js const stripoEditorApi = window.StripoEditorApi; ``` --- --- url: https://plugin.stripo.email/upgrading-plugin-subscription-plan.md --- # Upgrading Plugin Subscription Plan We are glad that you decided to go further and upgrade your Plugin subscription plan! To do so, please open the Plugin tab in your Stripo account. ::: image-wrap true ![](/img/plugin/new/image9.webp){width=350 height=836} ::: then choose a necessary application and click the "Change Plan" link. ::: image-wrap ![](/img/plugin/new/image10.webp){width=1998 height=614} ::: In the new pop-up window, you will get the opportunity to upgrade the subscription on a monthly or annual basis. ::: image-wrap ![](/img/plugin/new/image11.webp){width=1790 height=648} ::: If you want to get the invoice with the annual subscription, please email us at . --- --- url: https://plugin.stripo.email/editor-configuration/initialization-settings.md --- # Initialization Settings The new Stripo editor accepts a series of configuration parameters upon initialization, allowing you to customize the editor to meet your specific needs. A minimal configuration looks like this: ```js const stripoConfig = { metadata: { emailId: '...' }, html: '...', css: '...', onTokenRefreshRequest: function(callback) { /* This function is called whenever the authentication token needs to be refreshed. You should send a request to your backend to obtain a new token. Example: Send a request to your backend (e.g., https://your_domain/stripo/token) to retrieve the new token. */ const token = ... // Replace with the logic to obtain the token callback(token); // Pass the retrieved token to the callback function } } ``` You can configure various parameters to customize its behavior and functionality. Below is a list of possible parameters ## Supported Parameters ### metadata ### onTokenRefreshRequest ### html ### css ### entityType Defines what the editor opens for editing: `email` (default) or `module`. When set to `module`, the editor launches in **Module Editing Mode**, which lets you create or edit a reusable module instead of an email. Available on the **Enterprise** plan only — on any other plan the editor does not initialize and shows a toaster notification. See [Module Editing Mode](/editor-configuration/module-editing-mode). ### moduleId **Module Editing Mode only.** The ID of an existing module to open for editing. When provided, any passed `html` and `css` are ignored and the module is loaded from the plugin database. If the module is missing, deleted, or does not belong to the current plugin, the editor does not initialize and shows a toaster notification. See [Editing an existing module](/editor-configuration/module-editing-mode#editing-an-existing-module). ### moduleType Module Editing Mode only. Required when creating a new module (i.e. when `moduleId` is **not** provided). One of `STRIPE`, `STRUCTURE`, `CONTAINER`. If \``moduleId` is also provided, `moduleType` is ignored. If it is missing or invalid in create mode, the editor does not initialize and shows a toaster notification. See [Creating a new module](/editor-configuration/module-editing-mode#creating-a-new-module). ### key Module Editing Mode only, optional. The storage key of the module folder the new module is saved to (the same **Storage Key ID** configured for module folders in your Plugin settings). If omitted, the first folder the user can write to is used. See [Module Editing Mode](/editor-configuration/module-editing-mode). ### moduleName Module Editing Mode only, applies when **creating** a new module (ignored if `moduleId` is provided). Initial name of the new module. Maximum 200 characters; longer values are trimmed. Default: `New module`. See [Initial metadata for a new module](/editor-configuration/module-editing-mode#initial-metadata-for-a-new-module). ### moduleDescription Module Editing Mode only, applies when **creating** a new module (ignored if `moduleId` is provided). Initial description of the new module. Maximum 500 characters; longer values are trimmed. Default: empty. See [Initial metadata for a new module](/editor-configuration/module-editing-mode#initial-metadata-for-a-new-module). ### moduleCategoryId Module Editing Mode only, applies when **creating** a new module (ignored if `moduleId` is provided). Numeric ID of the category to assign to the new module. If the category does not exist or is unavailable for this plugin, it is silently ignored (no error, no toaster). Default: `Uncategorized`. See [Initial metadata for a new module](/editor-configuration/module-editing-mode#initial-metadata-for-a-new-module). ### moduleTags Module Editing Mode only, applies when **creating** a new module (ignored if `moduleId` is provided). Array of tag strings applied to the new module. Default: no tags. See [Initial metadata for a new module](/editor-configuration/module-editing-mode#initial-metadata-for-a-new-module). ### name ### utm ### locale The locale setting for the editor, determining the language and regional settings. **Supported locales:**\ `bg` — Bulgarian\ `cs` — Czech\ `de` — German\ `en` — English\ `es` — Spanish\ `fr` — French\ `it` — Italian\ `ja` — Japanese\ `ko` — Korean\ `nl` — Dutch\ `pl` — Polish\ `pt` — Portuguese\ `pt-br` — Portuguese (Brazilian)\ `ro` — Romanian\ `ru` — Russian\ `sl` — Slovenian\ `tr` — Turkish\ `uk` — Ukrainian\ `zh` — Chinese (Traditional)\ `zh-cn` — Chinese (Simplified) ### forceRecreate ### ignoreClickOutsideSelectors ### panelPosition ### notifications ### textEditorAllowedPasteContent ### imageGalleryViewMode The `imageGalleryViewMode` parameter sets how images are displayed when users open the image selection dialog (Image Gallery). This affects the visual layout and browsing experience: * `grid` - Traditional grid layout with uniform-sized image tiles arranged in rows * `masonry` - Pinterest-style masonry layout where images maintain aspect ratio with varied heights * `list` - Compact list view showing images as rows with thumbnails on the left **Sample:** ```js "imageGalleryViewMode": 'masonry' ``` **Behavior:**\ When the editor initializes: * If `imageGalleryViewMode` is set to one of the allowed values, the image gallery will open in that view mode. * If the parameter is omitted or set to an invalid value, the gallery defaults to `grid` mode. * When the user manually switches view modes in the gallery, the editor triggers the `onImageGalleryViewModeChange` event ([see below](#onimagegalleryviewmodechange)). ### preserveAltOnImageReplace When `false` (default), ALT text is cleared when an image is replaced, requiring users to manually enter new ALT text for the replacement image. **Sample:** ```js "preserveAltOnImageReplace": true // User replaces image_1.jpg with image_2.jpg // ALT text: "Product screenshot" is preserved ``` **Use Cases:**\ When the editor initializes: * Enable for templates where image descriptions apply across multiple image variations. * Use for workflows where maintaining consistent ALT text is critical for accessibility. * Disable for scenarios where different images require different descriptions. ### bankImagesDefaultSearchString ### codeEditor ### codeEditorButtonSelector ### mergeTags ### socialNetworks ### specialLinks ### hiddenShareItems An array of share-item keys that should be hidden from the **Share** tab in the link-type selector. Accepts a subset of the supported keys: `facebook`, `x`, `linkedin`, `pinterest`. For a detailed description and usage instructions, refer to the [Share Link Configuration](#share-link-configuration) section. ### hideShareTab A boolean flag that hides the entire Share tab from the link-type selector in the Link control. When set to `true`, the Share tab and all its contents are removed from the UI. For a detailed description and usage instructions, refer to the [Share Link Configuration](#share-link-configuration) section. ### viewOptions ### templateThemeMode ### editorFonts ### conditionsEnabled ### undoButtonSelector ### redoButtonSelector ### mobileViewButtonSelector ### desktopViewButtonSelector ### versionHistoryButtonSelector ### localePatch ```js { "localePatch": { "en": { "settingsPanel.accordion.structures": "Available Structures", "settingsPanel.block.timer": "Clock" } } } ``` Deprecated Format (Deprecated): ```js { "localePatch": { "settingsPanel.accordion.structures": { "en": "Available Structures" }, "settingsPanel.block.timer": { "en": "Clock" } } } ``` ::: warning ⚠️ Note: The previous format is now deprecated but remains functional for backward compatibility. It is recommended to use the new format to ensure future compatibility. ::: For a list of all editor phrases, refer to the editor phrases JSON file. ### defaultMenuItems ```js "defaultMenuItems": [ { "name": "Item 1", "href": "https://google.com" }, { "name": "Item 2", "href": "https://test.com" } ] ``` ### syncModulesEnabled ```js "syncModulesEnabled": true ``` ### disableAdaptDesign ```js "disableAdaptDesign": true ``` ### disableRemoteCursors ```js "disableRemoteCursors": true ``` ### moveBlockAvailability ```js "moveBlockAvailability": true ``` ### enableNativeSpellChecker ### enableTextEmojis ### enableXSSSecurity ```js "enableXSSSecurity": true ``` ### allowedScriptSourceDomains ```js "allowedScriptSourceDomains": "https://domain1.com http://domain2.net" ``` ### supportOutlookButtonsByDefault ```js "supportOutlookButtonsByDefault": true ``` ### sameFontSizeForOutlook This feature controls automatic font size optimization for button elements in Outlook. **Usage:**\ Set the `sameFontSizeForOutlook` parameter to `true` to enable the built-in font size optimization logic for Outlook. The editor automatically calculates appropriate font sizes based on button dimensions and spacing. If you prefer to disable this optimization and use standard font sizing instead, set the parameter to `false`. **Sample:** ```js "sameFontSizeForOutlook": true ``` **How It Works:**\ The editor includes a built-in optimization algorithm that adjusts button font sizes while considering: * Button height and overall dimensions * Font size settings * Padding and internal spacing This logic ensures that button text renders correctly in Outlook without overflow or unwanted wrapping. If this automatic optimization doesn't match your design preferences or causes unexpected font sizing, you can disable it by setting the parameter to false to use standard, unmodified font sizes. **Details:**\ When enabled, the optimization improves visual consistency between Outlook and modern email clients. When disabled, buttons use the exact font sizes specified without any Outlook-specific adjustments. Test both settings with your specific button designs across different Outlook versions (Outlook 2016, 2019, Outlook Online, Outlook for Mac) to determine which approach works best for your templates. ### shouldBeSavedToLibrary ```js { ... "shouldBeSavedToLibrary": function() { return false; }, ... } ``` ```js { ... shouldBeSavedToLibrary: function(moduleHtml) { // Your logic to determine if the module can be saved return true; // or false }, ... } ``` ### validateModuleSave ```js "validateModuleSave": function(data) { // Your validation logic here return { allowed: true }; } ``` ### validateModuleDelete ```js "validateModuleDelete": function(data) { // Your validation logic here return { ok: true }; } ``` ### shouldHideImagePath ```js shouldHideImagePath: function (url) { // hide images already hosted on Stripo CDN return url.includes('stripocdn.email') || url.includes('assets.mycompany.com'); } ``` ```js "shouldHideImagePath": (url) => url.startsWith("https://assets.mycompany.com/") ``` ### disableImageUnadapt ```js "disableImageUnadapt": true ``` ### youtubeApiKey ```js "youtubeApiKey": "YOUR_YOUTUBE_API_KEY_HERE" ``` ### keepModuleStylesEnabled ```js "keepModuleStylesEnabled": true ``` In this configuration, the Keep Module Styles control becomes available in the module saving panel, allowing users to inline appearance styles into the module HTML when saving the module. ### modulesDisabled ```js "modulesDisabled": true ``` If this parameter is not specified, users will have the default ability to manage modules. Note that this parameter will be ignored if you deactivate Modules within the plugin configuration page. Disabling module management can be useful for maintaining control over the modules and ensuring a consistent user experience. ### showModuleUid The `showModuleUid` parameter controls whether the module's unique identifier (uid) is displayed in the editor interface. By default, this field is hidden because it is primarily intended for advanced scenarios such as internal workflows, integrations, or companion applications that require access to the module identifier. When enabled, the module uid becomes visible in the module management interface, allowing users to view and reference it when saving, editing, or browsing modules in the library. **Usage:**\ Include the `showModuleUid` parameter during plugin initialization. Set the value to `true` if you want to display the module UID in the module save form, edit form, and module list tooltip. **Sample:** ```js "showModuleUid": true ``` If this parameter is not specified or set to `false`, the module UID field will remain hidden. When `showModuleUid` is enabled: * the **Id (uid)** field is displayed in the **module save form**; * the **Id (uid)** field is displayed in the **module edit form**; * the **Id (uid)** is shown in the **tooltip/module information** inside the Modules Library. ### selectBlockAfterDropFromSettingsPanel ```js "selectBlockAfterDropFromSettingsPanel": true ``` ### selectElementAfterDrop This feature allows any element dropped into the editor — whether it is a **block**, **extension**, **stripe**, **structure**, or **module** — to be automatically selected right after being placed in the editor. Usage: Set the selectElementAfterDrop parameter to true during the initialization of the Stripo editor to enable this functionality. Sample: ```js "selectElementAfterDrop": true ``` With this parameter enabled, as soon as the user drags and drops any element into the email layout, that element becomes instantly selected in the editor for immediate customization. This improves workflow consistency by eliminating the need to manually click the dropped element before editing it — useful for all element types, including blocks, structures, stripes, modules, and extensions. ### modulesExcludedCategories ```js "modulesExcludedCategories": [1, 2] ``` ### messageSettingsEnabled ```js "messageSettingsEnabled": true ``` ### calendarDateTimeFormat Defines the date and time display format for the Timer block in the editor.\ By default, Stripo uses the `MM DD YYYY` format (month–day–year).\ With this parameter, you can override the default and choose how the date should be displayed in the Timer. **Supported Date Formats**\ The Timer block fully supports the following formats: | Format | Example | Description | | ------------------ | ----------------------- | ----------------------------- | | `DD.MM.YYYY` | `25.12.2024` | Day.Month.Year | | `DD/MM/YYYY` | `25/12/2024` | Day/Month/Year | | `MM/DD/YYYY` | `12/25/2024` | US format | | `YYYY-MM-DD` | `2024-12-25` | ISO format | | `D MMMM YYYY` | `25 December 2024` | Day + full month name + year | | `DD MMM YYYY` | `25 Dec 2024` | Day + short month name + year | | `dddd, DD.MM.YYYY` | `Wednesday, 25.12.2024` | Weekday + date | | `DD.MM.YY` | `25.12.24` | Short year | *💡 Month and weekday names are displayed according to the editor’s selected locale.* **Usage:** ```js "calendarDateTimeFormat": "DD.MM.YYYY" ``` **Example:** ```js { ... "calendarDateTimeFormat": "MM/DD/YYYY", ... } ``` This configuration will render a date such as **12/31/2025** in the Timer block. **Notes** * Only the formats listed above are supported. * The formatting patterns follow a predefined internal rule set and do not use moment.js or similar libraries. * If an unsupported format is passed, the editor will fall back to the default `DD MM YYYY`. ### displayTitle ```js "displayTitle": false ``` ### displayHiddenPreheader ```js "displayHiddenPreheader": false ``` ### displayUTM ```js "displayUTM": false ``` ### displayGmailAnnotations ```js "displayGmailAnnotations": false ``` ### previewIframeAttributes ```js previewIframeAttributes: { foo: "bar", withoutValue: "" } ``` ### customViewStyles ```js // turn custom styles ON window.StripoEditorApi.actionsApi.activateCustomViewStyles(true); // turn custom styles OFF window.StripoEditorApi.actionsApi.activateCustomViewStyles(false); ``` Usage: Add the parameter to your initEditor call and pass CSS as plain text. ```js "customViewStyles": ".esd-block-button { border: 2px solid red; }"; ``` Sample: ```js "customViewStyles": "\n .esd-block-button {\n border-radius: 8px;\n padding: 12px 24px;\n background: linear-gradient(45deg,#ff8a00,#e52e71);\n color:#fff;\n }\n .esd-text h1 {\n font-family: 'Roboto Condensed', sans-serif;\n letter-spacing: 1px;\n }\n" ``` ### brandColorPalette ```js "brandColorPalette": [ { "name": "Primary Brand Color", "value": "#FF5733" }, { "value": "#33C1FF" }, { "name": "Accent", "value": "rgba(50, 205, 50, 0.8)" } ] ``` The heading of this section in the color picker can be customized with the `brandColorPaletteLabel` parameter. Each time a user applies a brand color, the editor emits a `color_palette_used` event through the `onEvent` callback, so your application can track brand palette usage. ### brandColorPaletteLabel Custom heading for the brand colors section of the color picker. Type: `String`\ Default: `"Brand Palette"` **Sample:** ```js "brandColorPaletteLabel": "Acme Corp Colors" ``` ### colorPalette Pre-populates the My Palette section of the color picker — the personal set of colors the user collects while working in the editor. If the parameter is not provided, the palette is restored from the user's browser storage. Users add the currently selected color to this section with the “+” button; the palette holds up to 13 colors (newest first) and is persisted in the browser between sessions. Type: `Array` of CSS color values **Sample:** ```js "colorPalette": ["#FF5733", "#33C1FF", "rgba(50, 205, 50, 0.8)"] ``` ### customColorPalette Replaces the built-in Default Palette colors with your own fixed set. When this parameter is provided, the free-form color picker (spectrum canvas) and the My Palette section are hidden, so users can only choose from the colors you define — plus the transparent swatch and the brand palette, if configured. Use it when the color choice in the editor must be limited to an approved set. Note: the colors are displayed inside the Default Palette section, so this parameter takes effect only while `defaultPaletteEnabled` is not set to `false`. Type: `Array` of CSS color values **Sample:** ```js "customColorPalette": ["#000000", "#FFFFFF", "#FF5733", "#33C1FF"] ``` ### defaultPaletteEnabled Shows or hides the **Default Palette** section of the color picker — the built-in set of standard colors together with the transparent swatch (or your own set, if `customColorPalette` is configured). Type: `Boolean`\ Default: `true` **Sample:** ```js "defaultPaletteEnabled": false ``` ### customColorsEnabled Shows or hides the free-form color selection tools of the color picker: the spectrum canvas and the **My Palette** section with user-saved colors. When set to `false`, users cannot pick arbitrary colors — only the colors available in the default, custom, or brand palettes. If `defaultPaletteEnabled` and `customColorsEnabled` are both `false` and no `brandColorPalette` is configured, the color picker popup is disabled entirely; users can still type a color value manually into the input field. Type: `Boolean`\ Default: `true` **Sample:** ```js "customColorsEnabled": false ``` ### baseBlocks ```js "baseBlocks": { "ampAccordionEnabled": false, "ampCarouselEnabled": true, "ampFormControlsEnabled": false, "bannerEnabled": false, "buttonEnabled": true, "htmlEnabled": true, "imageEnabled": true, "menuEnabled": true, "socialNetEnabled": true, "spacerEnabled": true, "textEnabled": true, "timerEnabled": true, "videoEnabled": true } ``` ### ampFormServices Defines a list of predefined backend endpoints available for AMP Form blocks in the editor. Each entry includes a `label` (user-friendly name) and a `value` (submission URL). These endpoints appear in the AMP Form block settings as selectable options for form submission. For a detailed description and configuration examples, refer to the [AMP Form Services section](/editor-configuration/initialization-settings#amp-form-services). ### showExternalAmpFormServices Controls whether users can manually add their own external AMP Form endpoints. When set to `false`, the “Add external service” option is hidden, and users can only select endpoints from the predefined `ampFormServices` list. For a detailed description, refer to the [AMP Form Services section](/editor-configuration/initialization-settings#amp-form-services). ### copyPasteEnabled Enables copying and pasting of email elements (stripes, structures, containers, and blocks) between tabs and windows of the editor through the system clipboard. Type: `Boolean`\ Default: `false` When set to `false`: * Cmd/Ctrl+C on a selected element does not place the element into the clipboard, and Cmd/Ctrl+V does not paste one; * the “You have copied an element…” notification with the Paste action is not shown — including when the element was copied in another tab; * the “Copy to Clipboard” hint on the element handle is hidden. Everything else stays unaffected: selecting and natively copying text inside text blocks works as usual, and the Duplicate action on the element handle remains available. **Sample**: `"copyPasteEnabled": false` ### gradients Granular control over the gradient options in the editor's color settings. Lets you hide the “Apply gradient” switcher and gradient controls — everywhere at once or per element type — when your sending environment does not support CSS gradients. **Type**: Object (all fields are Boolean and default to `true`) ```js "gradients": { "enabled": true, // master switch for all gradient controls "buttonEnabled": true, // General Styles > Button (incl. hover), inline button color/hover, button block background "textEnabled": true, // text block background "containerEnabled": true, // container background "structureEnabled": true, // structure background "stripeEnabled": true, // inline stripe backgrounds and General Styles > Stripes "messageBackgroundEnabled": true, // General Settings > message background "spacerEnabled": true, // spacer block background "socialEnabled": true // social block background } ``` Disabling an element type hides the gradient controls in all locations of that type at once (for example, `buttonEnabled: false` removes the gradient option from General Styles > Button, from inline button settings including hover styles, and from the button block background), together with their dark-mode counterparts. The regular solid color picker keeps working. `enabled: false` takes priority over the individual flags and hides gradient controls everywhere. Gradients that already exist in the template content are not removed from the HTML and keep rendering as they are — only the controls are hidden. **Sample:** ```js "gradients": { "buttonEnabled": false } ``` ### imageResizeOnCanvasEnabled Controls the availability of the on-canvas resize handle for image blocks. With this feature, users can drag the handle that appears in an image's bottom-right corner directly on the canvas to resize it, with a live width/height tooltip and a maximum clamped to the available column width — without opening the settings panel. The handle is available only in Desktop preview mode. Type: `Boolean`\ Default: `true` When set to `false`, the resize handle is not shown on the canvas. Images can still be resized through the **Size** control in the settings panel — this flag only turns off the canvas drag gesture, not image resizing itself. **Sample**: `"imageResizeOnCanvasEnabled": false` ### containerResizeOnCanvasEnabled Controls resizing of containers by dragging the borders between them directly on the canvas. Type: `Boolean`\ Default: `true` When set to `false`, the resize handles between the containers of a structure do not appear on hover and dragging the borders is unavailable. Managing container widths from the settings panel stays available and is not affected by this option. **Sample**: `"containerResizeOnCanvasEnabled": false` ### dataFeed Controls the availability of the **Data Feed** option in the Data tab of Smart Elements — the ability to fill a smart element from a structured data source instead of a website page. Type: `Object`\ Default: `hidden` — the Data Feed option appears only when explicitly enabled ```js "dataFeed": { "enabled": true, "allowExternalSourceUrl": false }, "dataSources": [ { "id": "products", "name": "Products", "type": "JSON", "sourceUrl": "https://api.acme.test/products.json" } ] ``` * If the `dataFeed` section is not passed at all, or `enabled` is `false`, users see only the “Website Page” option — no “Data Feed”. * enabled: `true` with a non-empty `dataSources` shows the Data Feed option with your sources listed in the Data Source dropdown; when `allowExternalSourceUrl: true`, the “External Source URL” option is added to the list. * `allowExternalSourceUrl: false` hides the “External Source URL” option, so users can pick only from the sources you passed. * A configuration with no available source is treated as disabled: `enabled: true` with an empty `dataSources` and `allowExternalSourceUrl: false` hides the Data Feed option entirely. Sample (external URLs only): ```js "dataFeed": { "enabled": true, "allowExternalSourceUrl": true } ``` ### calendarEventLinkEnabled Controls the availability of the **Calendar Event Link** type in the Link control. With this link type, users describe an event (title, time, time zone, location, description, recurrence) right in the editor and get a generated add-to-calendar link they can attach to any clickable element. The link can be switched between seven calendar types — Stripo landing page, Google, Outlook, Yahoo, AOL, Apple, and a downloadable .ics file — and generated events are reusable across the elements of the same email via the Events tab of the link type dropdown. Type: `Boolean`\ Default: `true` When set to `false`, the Calendar Event Link option is not offered in the link type dropdown. Links already generated with this feature are plain href elements in the email HTML, so existing emails keep working regardless of this option. **Sample**: `"calendarEventLinkEnabled": false` ### dragImageFromGalleryEnabled Controls the availability of the drag-to-document gesture in the settings-panel image gallery. With this feature, users can drag an image thumbnail out of the gallery and drop it directly onto the canvas — onto an existing image block to fill/replace it, or next to a block to insert a new image block at that position. It applies only to galleries whose target is the document itself (the main image picker); auxiliary galleries (rollover image, timer expiration image, menu/social icons, dark-mode variant, etc.) never offer drag-to-document, regardless of this flag. Type: `Boolean`\ Default: `true` When set to `false`, gallery thumbnails are no longer draggable onto the canvas. Users can still pick an image by clicking a thumbnail in the gallery — this flag only turns off the drag-and-drop path, not image selection itself. ### playInEmailEnabled Controls the availability of the **Play in email** mode of the Video block. In this mode, the editor embeds a playable video into the email: an AMP-powered player for supported clients, with an automatically generated GIF and poster fallback for all other email clients. Type: `Boolean`\ Default: `false` The mode is disabled by default. To make it available to your users, pass `playInEmailEnabled: true` during the editor initialization — the **Play in email** tab then appears in the Video block settings. **Plan availability:** the parameter takes effect on the Business and Enterprise Plugin plans. On lower plans, `playInEmailEnabled: true` is ignored and the mode stays disabled. Disabling the mode affects only creating and changing videos: video blocks that already exist in the template keep rendering and are exported with their assets as usual, while the upload and regeneration controls are disabled with an explanatory hint. **Sample**: `"playInEmailEnabled": true` ### elementLockEnabled Enables the [Lock Element](/editor-configuration/lock-element) feature in your integration — the ability to lock stripes, structures, and containers against content or style changes. Type: `Boolean`\ Default: `false` The feature is disabled by default. When set to `true`, the Lock Element control becomes available to users who have the `elementsLock.write` permission — the parameter enables the feature but does not replace the permission check. When the parameter is omitted or set to `false`, only the lock management control is hidden: elements that are already locked in the template remain protected according to their lock settings. ### onUpdateDisplayConditionsForViewOptions ```js onUpdateDisplayConditionsForViewOptions: function(displayConditions) { // displayConditions is an array of: // { id: 'audience-new', name: 'New Customers', visibility: true } // Optionally update your View Options UI or save state externally } ``` ### onViewOptionsReset ```js onViewOptionsReset: function(entity, entityId) { // Optionally reset your interface to the default state } ``` ### onSettingsPanelBlockSorting ```js onSettingsPanelBlockSorting: function(names) { return names.sort(); } ``` ### onPreheaderChanged ```js onPreheaderChanged: function(preheader) { // Custom logic to execute when the email preheader changes } ``` ### onRtlSet ```js onRtlSet: function(value) { console.log('rtl set', value); } ``` ### onTemplateLoaded ```js onTemplateLoaded: function() { // Custom logic to execute after the email is fully rendered } ``` ### onTitleChanged ```js onTitleChanged: function(title) { // Custom logic to execute when the email title changes } ``` ### onCodeEditorVisibilityChanged ```js onCodeEditorVisibilityChanged: function(isOpen) { // ... } ``` ### onConnectCustomFont When provided, the **`onConnectCustomFont`** callback activates a **"Connect another font"** option in the font family dropdown. When the user clicks this option, the editor invokes the callback so your application can open a custom UI to collect font details and register the new font. **Parameters:** * `onSave` — a callback provided by the editor. Call it with font data once the user confirms. Accepts an object with the following fields: * `name` - display name shown in the selector, * `value` - CSS `font-family` declaration, * `link` (optional) - URL to the font stylesheet, * `importMethod` (optional) - `'link'` | `'import'` | `'fontFace'` | `'local'`. **Usage:**\ Define the **`onConnectCustomFont`** function during the initialization of the Stripo editor. When the user clicks "Connect another font", the editor invokes this function. Open your own modal or font selection UI, collect the font details, and call `onSave` to register the font. **Sample:** ```js onConnectCustomFont: function(onSave) { // Open your font selection UI, then call onSave with font data onSave({ name: 'Roboto', value: "Roboto, sans-serif", link: 'https://fonts.googleapis.com/css2?family=Roboto', importMethod: 'link' }); } ``` This callback ensures that your application can provide users with a seamless font connection experience directly from the font selector. If this parameter is not provided, the "Connect another font" option does not appear in the dropdown (unless activated via the `external-custom-font` extension in plugin mode). See [User-Added Fonts in Editor](/editor-configuration/initialization-settings#user-added-fonts-in-editor). ### onSettingsPanelPositionChanged ```js onSettingsPanelPositionChanged: function(position) { // Custom logic to execute when the settings panel position changes } ``` ### onVersionHistoryVisibilityChanged ```js onVersionHistoryVisibilityChanged: function(isOpen) { // Custom logic to execute when version history visibility changes } ``` ### onVersionHistoryReadAccessChanged ```js onVersionHistoryReadAccessChanged: function(enabled) { // Custom logic to execute when version history read access changes } ``` ### onSaveStarted ```js onSaveStarted: function() { // Custom logic to execute when the save procedure starts } ``` ### onSaveCompleted ```js onSaveCompleted: function() { // Custom logic to execute when the save procedure is completed } ``` ### onEditorVisualModeChanged ```js onEditorVisualModeChanged: function(visualMode) { // Custom logic to execute when the visual mode changes } ``` ### onUsersInfoRequest ```js onUsersInfoRequest: function (userIds, successCallback, errorCallback) { fetch(`/api/users/info`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids: userIds }) }).then(res => res.json()) .then(data => successCallback(data.users)) .catch(err => errorCallback(err)); } ``` ### onUsersInfoSearchRequest ```js onUsersInfoSearchRequest: function (params, successCallback, errorCallback) { fetch(`/api/users/search?query=${encodeURIComponent(params.filter || '')}`) .then(res => res.json()) .then(data => successCallback(data)) .catch(err => errorCallback(err)); } ``` ### onUserListChange ```js onUserListChange: function(usersList) { // Custom logic to execute when the user list changes } ``` ### onEvent ```js onEvent: function(type, params) { // Handle different event types } ``` ### onEditorClicked The `onEditorClicked` function is called at the beginning of any pointer interaction (`pointerdown`) inside the email preview area. Because the editor renders the email preview in a separate document, clicks inside it are not visible to the outside-click handlers of the host application. This callback lets your application react to such clicks — for example, to close its own popovers, dropdown menus, or overlays when the user starts interacting with the email. **Function Signature:** ```js onEditorClicked: function() { // called without any payload } ``` **Usage:**\ Define the optional `onEditorClicked` function during the initialization of the Stripo editor. The editor invokes it on every `pointerdown` inside the email preview. The first click both triggers the callback and performs the regular editor action (for example, selecting a block), so the user does not have to click twice. **Sample:** ```js onEditorClicked: () => { // Close all overlay menus of the host application myApp.overlayManager.closeAllOverlays(); } ``` ### onDataChanged ```js onDataChanged: function() { console.log('Data changed'); // Additional logic to handle unsaved changes } ``` ### onElementCopy ```js onElementCopy: function(modifier) { // Get the target element being copied const targetElement = modifier.getTargetNode(); // Find all blocks (elements with class starting with 'esd-block') const blocks = targetElement.querySelectorAll('[class*="esd-block"]'); // Set a custom attribute on each block to mark it as a copy blocks.forEach(function(block) { modifier.modifyHtml(block).setAttribute('data-copy-marker', 'copied'); }); } ``` ### onElementMove The `onElementMove` function is called whenever a block, container, structure, or stripe is moved within the email template — via drag-and-drop or via keyboard shortcuts. **Parameters:** * `modifier` — A TemplateModifier object. The root node of the moved element is pre-set as the target. Use it to read and modify the moved element's HTML. For more information, refer to the [Template Modification System](https://plugin.stripo.email/extensions/template-modification). * `sourceElement` (optional) — A read-only `ImmutableHtmlElement` representing the parent element from which the node was moved (its container, structure, or stripe before the move). Use it to inspect the origin of the move.\ Resolving the source module: ```js const sourceModule = sourceElement?.getClosestModuleElement(); const sourceModuleId = sourceElement?.getClosestModuleId(); ``` This parameter is undefined if the original parent could not be resolved (for example, if it was removed as part of the move flow). **Note:** You do not need to call `apply()` explicitly. It is called automatically after your function returns, and your changes are merged with the move patch as a single undo/redo entry. **Difference from `onElementCopy`:** `onElementCopy` is triggered when an element is duplicated (a new node is inserted).\ `onElementMove` is triggered when an existing element changes position within the document tree — no new node is created. Use `onElementMove` when you need to react to repositioning, such as updating link-tracking IDs that depend on a module context. **Sample:** ```js onElementMove: function(modifier, sourceElement) { // Determine which module the element was moved FROM const sourceModuleId = sourceElement?.getClosestModuleId(); // Determine which module the element was moved INTO const targetModuleId = modifier.getTargetNode().getClosestModuleElement() ?.getAttribute('data-module-id'); // If the element crossed a module boundary, regenerate its tracking ID if (sourceModuleId !== targetModuleId) { modifier.setAttribute('data-tracking-id', crypto.randomUUID()); } } ``` ### onModuleAdd ```js onModuleAdd: function(action, modifier) { // Get the module being added or copied const targetElement = modifier.getTargetNode(); // Generate new EVENT ID based on the action type const eventId = action === 'COPY' ? targetElement.getAttribute('event-id') + '_copied' : crypto.randomUUID(); // Set attribute to root module node modifier.setAttribute('event-id', eventId); } ``` ### onModuleDetach The `onModuleDetach` function is called when a user triggers the **"Unlink and Apply Changes Locally"** action for a synced module in the email template. It is the symmetric counterpart to [`onModuleAdd`](#onmoduleadd) and follows the same contract for the `modifier` parameter. Without `onModuleDetach`, plugins that react to unlinking via the `module_detached` event and apply changes through a `templateModifier` produce two separate entries in the undo/redo stack — one for the unlink itself and one for the plugin's modifications. This means the user must press Undo twice to fully revert the operation. `onModuleDetach` solves this by merging both the unlink action and your plugin-side modifications into a **single atomic undo/redo entry**. One Ctrl/Cmd+Z press reverts everything at once. **Parameters:** * **`modifier`** — A `TemplateModifier` object that provides methods to modify the unlinked module's HTML. The module's root node is set as the target node. The contract is identical to the `modifier` in `onModuleAdd`. For more information, refer to the [Template Modification System](/extensions/template-modification). **Usage:**\ Define the `onModuleDetach` function during the initialization of the Stripo editor. Use it to perform any custom HTML transformations needed at the moment of unlinking — such as injecting link-tracking attributes, stripping internal `data-` attributes, or updating references. You do not need to call `apply()` at the end; it is called automatically, and your changes are merged with the unlink patch as one atomic operation. **Backward compatibility:**\ If `onModuleDetach` is not defined, the existing `module_detached` event continues to fire as before, and any modifications applied via `templateModifier` in that event handler will create a separate undo-stack entry (requiring two Undo presses to fully revert). **Sample:** ```js onModuleDetach: function(modifier) { // Get the root node of the module being unlinked const targetElement = modifier.getTargetNode(); // Remove internal tracking attribute before unlinking modifier.removeAttribute('data-module-sync-id'); // Add a custom marker to identify locally-applied modules modifier.setAttribute('data-local-module', 'true'); } ``` ### onBeforeModuleSave ```js onBeforeModuleSave: function (data) { const copilotApi = window.StripoEditorApi.editorCopilotApi; const modifier = copilotApi.getTemplateModifier(); modifier .modifyHtml(data.moduleNode) .setAttribute('data-saved-at', new Date().toISOString()) .apply({ key: 'Set module save timestamp', params: {}, getValue() { return { key: this.key, params: this.params }; } }); return { canSave: true }; } ``` ### onImageSelected The `onImageSelected` function is called whenever an image is inserted into the editor — from any image gallery tab (General, Custom/External) **or via upload from the local machine**. It fires for **every block that contains an** `` **tag (Image, Button, Menu, Video, Banner)**. This initialization parameter allows developers to programmatically add custom `data-*` attributes (or any other attributes/classes/styles) to the inserted `` immediately after selection. **Function Signature** ```js onImageSelected: function(params, modifier, selectedImgNode) { // params: Image metadata object // modifier: HtmlNodeModifier already targeting the inserted // selectedImgNode: Immutable, read-only wrapper of the inserted } ``` **Parameters:** * `params` **–** image metadata (only `url` is guaranteed; other fields are optional): ```js { url: "https://example.com/image.jpeg", // freshly inserted URL (always present) originalName: "photo.jpeg", // original filename width: 640, // image dimensions in pixels height: 427, sizeBytes: 61656, // file size in bytes aiGenerated: true, // present when the image was created with the built-in AI image generator altText: "", // existing ALT text from the gallery item thumbnailUrl: "https://example.com/thumb.jpeg", // thumbnail URL for preview labels: { // custom labels / metadata category: "Summer Campaign", userId: "user123" } } ``` * `Modifier` – `HtmlNodeModifier` already targeting the inserted `` node. Use it to mutate the node: `setAttribute`, `removeAttribute`, `setClass`, `setStyle`, etc., refer to the [HTML Node Modification Section](/extensions/reference/modification/HtmlNodeModifier#htmlnodemodifier). * `selectedImgNode` – immutable, read-only wrapper of the `` node (`getAttribute`, `hasClass`, `getStyle`, `getTagName`, `querySelector` …). **IMPORTANT:** it reflects the CRDT state **before** the new `src` is committed — `selectedImgNode.getAttribute('src')` returns the *previous* URL. Use `params.url` to read the freshly inserted value. The `aiGenerated` flag lets your platform distinguish AI-generated visuals from regular images — for example, to apply a visible AI-content disclosure in your own UI, as required for realistic AI-generated images of people by the EU AI Act (Article 50). The flag accompanies the image both when it is uploaded to the image storage and when it is selected from the gallery. **Use Cases:**\ Define the `onImageSelected` function during the initialization of the Stripo editor. This function will be automatically triggered every time a user selects and inserts an image from any gallery tab into any image-containing block: Image, Button, Menu, Video, Banner, etc. The callback allows you to execute custom logic to enrich image elements with additional metadata before they are permanently added to the template. **Sample:** ```js onImageSelected: (params, modifier, selectedImgNode) => { if (params.labels?.category === 'summer-campaign') { modifier .setAttribute('data-campaign', params.labels.category) .setAttribute('data-recipient', window.externalService.getRecipient()); } } ``` ### onImageGalleryViewModeChange The `onImageGalleryViewModeChange` function is called when a user changes the view mode of the image gallery during the editing session. This function helps to notify your application about changes in the gallery display mode, allowing you to manage user preferences and adjust the interface accordingly in real-time. **Parameters:** * **mode** — string — The new view mode selected by the user. Possible values are `grid`, `masonry`, or `list`. **Usage:**\ Define the `onImageGalleryViewModeChange` function during the initialization of the Stripo editor. This callback is invoked whenever the user switches between different image gallery view modes, allowing your external front-account system to receive and process these changes. **Sample:** ```js onImageGalleryViewModeChange: function(mode) { // Notify your application about gallery view mode change console.log('Gallery view mode changed to:', mode); // Send to your backend or state management updateUserPreference('galleryViewMode', mode); } ``` This callback ensures that your application stays synchronized with user interactions in the image gallery, enabling you to persist user preferences, update UI controls, or log analytics data whenever the gallery view mode is changed. ## Metadata Information The `metadata` parameter is mandatory because it is used to identify the email. Additionally, you can specify the username and avatar, which will be used in the version history and during simultaneous editing. You can pass any information needed by the editor, and all these data will be sent to your server in the information provided to the permission checker API. This is also the right place to define the values for any variables needed during initialization. For example, if a customer configures the folder path for [images](image-gallery) or [modules](modules-library) and needs to operate with specific variables, the editor needs to know the values for those variables. This can be accomplished by including them in the `metadata` parameter. | Parameter | Description | Required | | ------------ | ----------------------------------------------------------------------------------- | -------- | | `emailId` | Email identifier | Yes | | `username` | Username. Used for version history and simultaneous editing | No | | `email` | User email | No | | `avatarUrl` | URL to the user's avatar image. Used for visualization during simultaneous editing. | No | | `customVar1` | Custom variable 1 | No | | `customVar2` | Custom variable 2 | No | Example: ```js { ..., "metadata": { "emailId": "123", "username": "John Smith", "email": "john.smith@stripo.email", "avatarUrl": "https://yourdomail.com/avatars/avatar_1.png", "customVar1": "12345", "customVar2": "12345" }, ... } ``` **Use Cases** 1. Version History and Simultaneous Editing: * The `username` and `avatarUrl` help in identifying users in the version history and during simultaneous editing. 2. Folder Paths: * If the customer configures a folder path for storing images or modules, the `custom` variables can be included in the metadata. This allows the editor to know where to save or retrieve these resources. 3. Custom Variables: * Any other custom variables required by the editor can be included in the `metadata` parameter. For instance, if certain configuration settings or values need to be dynamically provided during initialization, they can be passed here. By defining these values during initialization, the editor is equipped with all necessary information to function correctly and efficiently, tailored to the customer's specific setup and requirements. :::success Please be advised, all the data from this parameter will be included into the UIData parameter within the [Email Resources Permissions API](server-webhooks#email-resources-permissions-api). ::: ## Notification Settings The notifications parameter is used to display different types of messages on the application's UI. For example, ::: image-wrap ![](/img/plugin/new/image12.webp){width=964 height=144} ::: Message parameters: Example: ```js { ..., "notifications": { "info": function(message, id, params) { /* Show info message */ }, "error": function(message, id, params) { /* Show error message */ }, "success": function(message, id, params) { /* Show success message */ }, "warn": function(message, id, params) { /* Show warn message */ }, "loader": function(message, id, params) { /* Show loader message */ }, "hide": function(id) { /* Hide message by id */ }, }, ... } ``` ## Pasting Content Restrictions The `textEditorAllowedPasteContent` parameter allows you to restrict the tags and attributes that can be pasted from the clipboard into a text block. This is particularly useful in scenarios where maintaining a consistent and secure format for the text blocks is critical. **Use Case:** Imagine you are developing an email template editor for a marketing platform. To ensure that the pasted content adheres to your formatting standards and to prevent any potential security risks from unwanted HTML tags or attributes, you can use the `textEditorAllowedPasteContent` option. By defining which HTML tags and attributes are allowed, you can ensure that only specific elements and attributes are included when users paste content into the text editor. Any tags and attributes not specified in the allowed list will be ignored, and the text will be inserted as plain text. For example, you may want to allow basic formatting tags like `

`, ``, ``, and `` but restrict other tags that could disrupt the email's design or introduce security vulnerabilities. Additionally, you can specify which attributes are allowed for certain tags, such as allowing only `href` and `title` attributes for `` tags, and `src` and `alt` attributes for `` tags. Other tags and attributes will be stripped out, ensuring that only clean and secure content is inserted. | Parameter | Description | | ------------ | ---------------------------------------------------- | | `tags` | An array of tags that are allowed to be pasted | | `attributes` | An array of attributes that are allowed to be pasted | ```js { ..., "textEditorAllowedPasteContent": { "tags": ['b', 'strong', 'i', 'a'], "attributes": ['href', 'target'] }, ... } ``` In this example, only the specified tags and attributes will be permitted when pasting the coppied content into the text block. This ensures that the content remains consistent with the desired format and helps prevent any unintended HTML or security issues. ## Code Editor Settings This parameter is used to configure the default display state of the code editor. ::: image-wrap ![](/img/plugin/new/image13.webp){width=1999 height=425} ::: | Parameter | Description | | ---------------------- | ----------------------------------------------------- | | `isOpen` | True — if the code editor is open by default | | `isDefaultCSSOpen` | True — if the default CSS section is open by default | | `isCustomCSSOpen` | True — if the custom CSS section is open by default | | `containerHeight` | Default height of the code editor in pixels | | `defaultCSSPanelWidth` | Width of the default CSS section by default in pixels | | `customCSSPanelWidth` | Width of the custom CSS section by default in pixels | Example: ```js { ..., "codeEditor": { "isOpen": false, "isDefaultCSSOpen":true, "isCustomCSSOpen": false, "containerHeight": 80, "defaultCSSPanelWidth": 100, "customCSSPanelWidth": 100 }, ... } ``` In this example, the code editor is initially closed (`isOpen: false`), the default CSS section is open (`isDefaultCSSOpen: true`), and the custom CSS section is closed (`isCustomCSSOpen: false`). The default height of the code editor container is set to 80 pixels, and both the default and custom CSS panel widths are set to 100 pixels. ## Merge Tags Setup The `mergeTags` parameter is used to specify merge tags displayed in the settings of a text block. Merge tags are placeholders that dynamically insert personalized content, such as a recipient's name or other specific information, into the text. This is particularly useful in email marketing, where personalization can significantly improve engagement and response rates. **Use Case:** Imagine you are developing an email marketing platform that allows users to create and send personalized emails to their subscribers. To enhance the effectiveness of these emails, you want to allow users to insert personalized content easily. The mergeTags parameter lets you define which merge tags are available for users to insert into their email text blocks. For example, you might want to include merge tags for the recipient's first name, last name, and specific campaign details. By defining these merge tags, users can quickly add personalized elements to their emails without manually inputting each recipient's information. When the email is sent, the merge tags are replaced with the actual data for each recipient, creating a more personalized and engaging message. ::: image-wrap ![](/img/plugin/new/image14.webp){width=400 height=372} ::: Example: ```js { ..., "mergeTags": [ { "category": "Yespo", "entries": [ { "label": "First Name", "value": "%FIRSTNAME|%", "previewValue": "John", "hint": "Recipient's first name", "hidden": false }, { "label": "Last Name", "value": "%LASTNAME|%", "previewValue": "Doe", "hint": "Recipient's last name", "hidden": false } ] } ], ... } ``` Using these merge tags, users can effortlessly create personalized and targeted email content, improving the overall effectiveness of their email marketing campaigns. ### Custom Appearance of Merge Tags You can independently choose how exactly your personalization tags should be displayed. This can be configured during initialization with the following parameters: * `customAppearanceMergetags`: Enables custom appearance for merge tags. * `customAppearanceMergetagsInLinks`: Enables custom appearance for merge tags within links. * `customAppearanceMergetagsBorderColor`: Specifies the border color for merge tags. * `customAppearanceMergetagsBackgroundColor`: Specifies the background color for merge tags. **Example**: ```js { ... "customAppearanceMergetags": true, "customAppearanceMergetagsInLinks": true, "customAppearanceMergetagsBorderColor": "blue", "customAppearanceMergetagsBackgroundColor": "green" } ``` This configuration customizes the appearance of merge tags, making them visually distinct with a blue border and green background. ### Custom Thumbnails for Images with Merge Tags When customers use personalization tags (Merge tags) for different images, the editor can't recognize them and as a result, replaces the image with a "broken" icon: ::: image-wrap ![](/img/plugin/new/image62.webp){width=1187 height=383} ::: Some users find this confusing. If you want to replace the "broken" icon with your own image, use the configuration below: ```js { "defaultImgForMergeTagSrc": "URL" } ``` where: * `URL` - is the link to your image. You should apply this configuration during initialization, and it will work as shown in the screenshot below: ::: image-wrap ![](/img/plugin/new/image62_2.webp){width=2362 height=606} ::: ## Social Networks Configuration The `socialNetworks` parameter is used to specify the list of social networks that will be added to the social networks block. This feature is especially useful for individuals or businesses who want to include links to their social media profiles directly within their email templates, allowing recipients to easily connect with them across various platforms. **Use Case:** Imagine you are creating an email template for a marketing campaign. Including links to your social media profiles in the email can increase engagement and drive traffic to your social media pages. By specifying the `socialNetworks` parameter, you can define which social media icons and links should be displayed in the email's social networks block. If this parameter is specified with social networks, these icons will be added by default once the social network block is dropped into the email template. This ensures that your audience can easily find and follow your social media accounts, helping to build your online presence and foster community engagement. ::: image-wrap true ![](/img/plugin/new/image15.webp){width=1084 height=906} ::: | Parameter | Description | | --------- | --------------------------------- | | `name` | Name of the social network | | `href` | URL of the social network profile | ::: image-wrap ![](/img/plugin/new/image16.webp){width=400 height=319} ::: Example: ```js { ..., "socialNetworks": [ { "name": "facebook", "href": "https://facebook.com" } ], ... } ``` **Supported Social Network Names or Icons:** Using the `socialNetworks` parameter ensures that the social media links are consistently formatted and easily accessible, enhancing the overall effectiveness of the email marketing campaign by encouraging social media interaction and engagement. You can independently choose which style should be applied to the social icons when your customer drops them into an email message. All you need to do is initialize the Plugin with the configuration below: ```js "socialIconsDefaultView": "squareColoredBordered" ``` **Available Values:** ``` logoBlack, logoGray, logoWhite, circleColored, roundedColoredBordered, circleColoredBordered, roundedColored, squareColored, squareColoredBordered, circleBlack, circleBlackBordered, roundedBlack, roundedBlackBordered, squareBlack, squareBlackBordered, circleGray, circleGrayBordered, roundedGray, roundedGrayBordered, squareGray, squareGrayBordered, circleWhite, circleWhiteBordered, roundedWhite, roundedWhiteBordered, squareWhite, squareWhiteBordered, logoColored ``` The final result depends on your chosen value. In this example, the configuration is set to `logoColored`. By customizing the `socialIconsDefaultView` parameter, you can ensure that the social icons in your email messages match your preferred style and branding. ## Special Links Configuration The `specialLinks` parameter is used to specify a list of links that will be added to the link selection component. This feature is particularly useful for adding commonly used links, such as unsubscribe, support, or social media profile links, directly into email templates. It ensures consistency, saves time, and makes the email creation process more efficient. ::: image-wrap ![](/img/plugin/new/image63.webp){width=400 height=331} ::: **Use Case:** Imagine you are managing an email marketing platform where users frequently need to include specific links in their email templates, such as unsubscribe links or customer support links. Manually entering these URLs each time can be tedious and prone to errors. The `specialLinks` parameter allows you to define these essential links once, and then make them easily accessible for users to insert into their emails. For example, you might want to include links for unsubscribing from the newsletter, contacting support, or viewing email in browser. By using the `specialLinks` parameter, you can group these links under specific categories, making it easy for users to find and insert them into their emails Example: ```js { ..., "specialLinks": [ { "category": "Yespo", "entries": [ { "label": "Unsubscribe", "value": "https://my.yespo.io/unsubscribe", "hidden": false } ] } ], ... } ``` This approach not only ensures that the links are correctly formatted and consistently used but also significantly speeds up the email creation process. It reduces the likelihood of errors and enhances the overall user experience by providing quick access to frequently used links. Using the `specialLinks` parameter, you can help users maintain a high standard of professionalism and compliance in their email campaigns while also making the editing process more efficient and user-friendly. ## Share Link Configuration The `hideShareTab` and `hiddenShareItems` parameters give integrators fine-grained control over the **Share tab** that appears inside the link-type selector of the Link control (used in button blocks, image blocks, text links, and the hyperlink modal). ::: image-wrap true ![](/img/plugin/new/image82.webp){width=1071 height=433} ::: By default, the **Share tab** is visible and contains four items: **Facebook**, **X**, **LinkedIn**, and **Pinterest**. These parameters let you hide the entire tab or suppress individual items — for example, when your platform has its own sharing logic or when certain networks are not relevant to your users. ### Use cases Consider an ESP that has built its own social-sharing workflow outside the editor. Showing the editor's built-in Share tab would confuse users with redundant options. By setting `hideShareTab: true`, the integrator removes the tab entirely, keeping the link selector clean and focused on the options that matter. Alternatively, if an integrator wants to keep the Share tab but their platform does not support Pinterest or X, they can pass `hiddenShareItems: ['pinterest', 'x']` to suppress only those items while leaving Facebook and LinkedIn visible. ### Auto-hide tabs row rule: If the combination of your configuration (for example, \`Personalization\` disabled at the feature level \*\*and\*\* \`hideShareTab: true\`) results in only \*\*one\*\* tab remaining in the link-type selector, the tabs row is hidden entirely. The user sees the contents of the single remaining tab directly, without a tab strip above it. | Parameter | Description | | :---- | :---- | | `hideShareTab` | `boolean`. When `true`, the entire Share tab is removed from the link-type selector. Its content and related translations are absent from the DOM. Defaults to `false`. | | `hiddenShareItems` | `string[]`. An array of share-item keys to hide within the Share tab. Supported keys: `'facebook'`, `'x'`, `'linkedin'`, `'pinterest'`. Items not listed remain visible. Defaults to `[]` (all items shown). | #### Example — hide the entire Share tab: ```js { ..., "hideShareTab": true, ... } ``` #### Example — hide specific share items: ```js { ..., "hiddenShareItems": ["pinterest", "x"], ... } ``` **Note:** These parameters apply uniformly across all places where the Link control with the protocol selector is used: button block, image block, text link, and the hyperlink modal. ## Custom Appearance of Special Links Please refer to the section [Custom Appearance of Merge Tags](#custom-appearance-of-merge-tags). ## View Options The **View Options** feature allows your end users to control how the email template is rendered inside the editor. This helps them focus on the version of the message that is most relevant to them — whether it's HTML or AMP, mobile or desktop, personalized or raw content. View Options do not affect the final exported code. They change only what is displayed in the editor for the current user. **Use Cases:** Here are typical scenarios where `viewOptions` bring value to your end users: * **QA testing.** A QA engineer wants to verify how the AMP version of the email renders without interference from HTML blocks. * **Content personalization.** A marketer previews merge tags as final values to validate personalization logic. * **Condition debugging.** A support specialist investigates visibility logic by toggling hidden blocks or specific display conditions. * **Simplified writing.** A copywriter works in a distraction-free environment by hiding irrelevant structures or fallback content. Each user sees only their own specified View Options — they are not shared across collaborators. ### UI Behavior and Editor Feedback When you implement View Options in your interface (e.g., via a dropdown or toggles, see the sample below), users will interact with them to switch between different preview modes. ::: image-wrap ![](/img/plugin/new/image204.webp){width=350 height=343} ::: By default, the template is rendered based on the `viewOptions` object passed during editor initialization. These values define the initial display context for the editor session. If the user modifies any of the View Options through your custom UI, the editor will detect this and render content accordingly. In such cases, a persistent notification appears above the Settings panel. This warning explains that some elements might currently be hidden and includes a **Restore** button to revert back to the default view. ::: image-wrap ![](/img/plugin/new/image205.webp){width=350 height=189} ::: Example scenario: * You pass `showHiddenElements: true` and `mimeType: 'both'` during initialization. * The end user switches to `mimeType: 'html'` and disables hidden elements. * The editor displays a banner: *"Some elements may be hidden due to active view options"* with a **Restore** CTA. This helps users understand that hidden content is not deleted but simply filtered out. ### How to use Add the `viewOptions` parameter to your plugin initialization script to control how the editor displays the template content. Below is an example of how to pass this parameter: ```js viewOptions: { showHiddenElements: true, // show/hide hidden elements in the template mimeType: 'both', // 'both' | 'html' | 'ampHtml' mergeTags: raw, // 'raw' | 'label' | 'value' showPinsInEditor: false, displayConditions: [ // show/hide elements with display conditions { id: 'audience-new', name: 'New Customers', visibility: true } ] } ``` ### Parameter Reference ### Controlling View Options via API If your interface includes custom controls (e.g., toggles, dropdowns) that allow users to change the way the email is displayed, you should use the `setViewOptions` method to update the editor view dynamically. See reference in [JavaScript API](/plugin-invocations/javascript-api#view-options-api). This method applies new values for MIME type, merge tag rendering, or visibility filters in real time, without reinitializing the editor — it updates the display instantly. When calling this method, the editor updates its display immediately and triggers internal logic (like banners, conditional rendering, etc.). You can pass the full object or partial updates depending on your logic. Separately, the editor may update the internal list of display conditions, for example, when the user creates and applies a new condition or deletes an existing one in the settings panel for this specific email template opened in the editor. The plugin will invoke your `onUpdateDisplayConditionsForViewOptions` callback in two cases: * **On initialization** — to provide the full list of nodes that use display conditions, along with their visibility states; **On condition update** — whenever a new display condition is created, removed, or applied to a node within the editor. In such cases, the editor will invoke your callback `onUpdateDisplayConditionsForViewOptions`, passing the updated list of active condition IDs with their names and visibility states. ### This allows you to: * stay in sync with the condition logic configured inside the editor, * persist view settings externally if needed, * update your own UI state accordingly. Example of how the editor will call your callback: ```js onUpdateDisplayConditionsForViewOptions: function(displayConditions) { // displayConditions is an array of: // { id: 'audience-new', name: 'New Customers', visibility: true } // Optionally update your View Options UI or save state externally } ``` And finally, when the user clicks the "Restore" button in the View Options banner, the `onViewOptionsReset` callback will be triggered by the plugin. In this case, your application should remove any viewOptions stored on your end for this user and reset the interface accordingly. Example: ```js onViewOptionsReset: function(entity, entityId) { // Optionally reset your interface to the default state } ``` ### Behavior Notes * View Options affect only the editor view — not the exported result. * In Commenting Mode, all View Options reset to default. Previous settings are restored upon exit. * In Code View, all content is always shown regardless of filters. * If filters hide part of the content, the editor displays a persistent message with a reset button. ## Template Theme Mode The **Template Theme Mode** option allows you to define how the opened email template is visually rendered inside the editor — in **Light** or **Dark** mode. This setting affects **only the editor UI rendering** and does **not** modify the email HTML, styles, or exported content. It is designed to improve editing comfort and visual consistency with your application. :::success We understand that different email clients (such as Gmail, Outlook, Apple Mail, etc.) apply their own algorithms to determine how an email is rendered in dark mode. Because of this, **it is not possible to fully replicate or guarantee identical rendering across all inboxes**. The purpose of this feature is **not pixel-perfect emulation**, but rather a **fast visual check** based on a controlled dark-mode simulation inside the editor. You can rely on the following principles: * If the template looks **correct in the editor’s Dark mode**, it is very likely to render correctly in real email clients. * If a **visual issue is noticeable in the editor**, there is a high probability that the same issue will appear in at least some email clients. This makes Template Theme Mode a practical tool for **early risk detection**, rather than a strict preview of a specific mailbox. ::: ### **Use cases** Template Theme Mode is useful when you need to evaluate how the **email content itself** behaves when rendered in a dark-mode environment. Typical scenarios include: * Checking how background colors, images, and text contrast behave when the template is displayed in dark mode. * Identifying elements that rely on transparent or inherited backgrounds and may become unreadable. * Detecting potential issues with icons, buttons, or images that were designed primarily for light backgrounds. * Performing a quick visual risk check before exporting or sending an email campaign. * Validating design decisions early, without testing the email in multiple real inboxes. This feature helps you catch **dark-mode-related risks in the email layout**, while the template is being edited — not after it is sent. ### **How to use** To define the initial theme for the opened template, pass the `templateThemeMode` parameter during editor initialization. ```js templateThemeMode: 'DARK' ``` If the parameter is not provided, the editor opens in **Light** mode by default. ### **Parameter reference** | Parameter | Type | Default | Description | | ----- | ----- | ----- | ----- | | `templateThemeMode` | `'LIGHT' \| 'DARK'` | `'LIGHT'` | Defines how the template is rendered inside the editor UI. This setting affects only the editor view and does not modify the email HTML or exported output. | ### **Controlling the theme via API** You can also **change or read the template theme at runtime** using the [Template Theme Mode API](/plugin-invocations/javascript-api#template-theme-mode-api). See the **Template Theme Mode API** section for details on: * switching the theme dynamically; * retrieving the current theme value. ## Font Management The Font Management option enables users to customize the fonts available in the Stripo editor, providing flexibility in design and brand consistency. ### Use Cases Font management helps implement a variety of scenarios, such as: * **Customizable Font Lists**: Users can customize the list of fonts loaded in the editor. For example, an interface in your app can be created to configure the editor with specific fonts. * **Brand Consistency for Agencies**: Digital marketing agencies can customize the list of fonts in the editor according to a client's brand requirements. * **Expanded Font Options**: To expand the list of available fonts in the editor, add your web fonts from popular services like Google Fonts. * **Reduced Font Options**: By limiting the number of fonts, default fonts are removed, making it easier for users to adhere to brand guidelines. ### How to Activate ::: image-wrap ![](/img/plugin/new/image64.webp){width=350 height=453} ::: To activate option, include the `editorFonts` parameter in the plugin initialization script. This parameter allows specifying custom fonts, displaying default fonts, and organizing favorite fonts. ```js "editorFonts": { "showDefaultStandardFonts": true, "showDefaultNotStandardFonts": true, "favouriteFonts": { "label": "Favourite Fonts", "values": [ { "name": "Barriecito", "fontFamily": "'Barriecito', cursive", "url": "https://fonts.googleapis.com/css?family=Barriecito&display=swap" } ] }, "customFonts": [ { "name": "Oswald", "fontFamily": "'Oswald', 'helvetica neue', helvetica, arial, sans-serif", "url": "https://fonts.googleapis.com/css?family=Oswald" }, { "name": "Barriecito", "fontFamily": "'Barriecito', cursive", "url": "https://fonts.googleapis.com/css?family=Barriecito&display=swap" } ] } ``` Find the parameter descriptions below: :::success Please be advised that Stripo accepts only the CSS font embedding method, and the CSS file must be hosted in HTTPS protocol. You can use services like Google fonts that provide host font stacks and a well-formatted CSS file. If you want to change the default set of fonts, you need to disable them and use custom fonts to indicate a new set, including the URL parameter for web fonts. In this case, you don’t have to pass the URL parameter to the fonts from the “*Standard fonts*” list. ::: ### User-Added Fonts in Editor You can let users connect custom fonts directly from the font selector — without leaving the editor. When configured, a **"Connect another font"** option appears in the font family dropdown. Clicking it opens a UI where the user provides font details; the font is then added to the **Custom** category for the current session and immediately applied to the selected element. There are two ways to enable this: **Option 1 — `onConnectCustomFont` callback (recommended)** Works in any integration mode. Pass the callback at initialization — the editor calls it with an `onSave` function when the user clicks the option. Open any UI you choose, collect the font data, and call `onSave`: ```js onConnectCustomFont: function(onSave) { openYourFontModal(function(result) { onSave({ name: result.displayName, // shown in the font selector value: result.fontFamily, // CSS font-family, e.g. "'Roboto', sans-serif" link: result.cssUrl, // optional: URL to the font stylesheet importMethod: result.embedMethod // optional: 'link' | 'import' | 'fontFace' | 'local' }); }); } ``` The `importMethod` controls how the font is injected into the email ``: | Value | Injected into \`\\` | | :---- | :---- | | link | \ | | import | \ | | fontFace | \ | | local | nothing — font is assumed to be locally available | **Option 2 — Extensions SDK (fallback)** If you are building a plugin-mode integration and prefer not to use the callback, use the [external-custom-font](/extensions/core-concepts#external-custom-font) extension instead. It activates the same "Connect another font" entry point via the Extensions SDK. Note: if `onConnectCustomFont` is also provided, the callback takes priority and the extension is not invoked. No changes to existing extension integrations are required. This functionality is available in Plugin version **2.21.0** or higher and requires the **Business or Enterprise plan**. ## Display Conditions Display conditions allow you to change the content of emails displayed to recipients, depending on whether the specified condition on your end is met or not. Users can set conditions manually in the editor (Local) or select them from a list of predefined conditions set earlier (Predefined). **Use Cases:** 1. **Personalized Content**: Show different content blocks to different segments of your audience. For example, display a special offer only to female customers or show different products based on user preferences. 2. **Conditional Display**: Include or exclude certain content based on specific conditions, such as geographic location, membership status, or user activity. 3. **Dynamic Marketing Campaigns**: Enhance engagement and relevance by tailoring email content to different user segments. **Important Considerations:** * **Editor and Application Responsibilities**: While the editor is responsible for placing the conditional statements into the correct places within the HTML of the email, it is the responsibility of the application (where the plugin is embedded) to correctly handle these statements when it’s time to send the email, according to their business needs. * **No Language Limitations**: The editor does not have any limitations regarding the language of these statements, so they can be written in any scripting language supported by your application. ::: image-wrap ![](/img/plugin/new/image58.webp){width=1999 height=802} ::: Now let’s go through the initial process of activating and customizing the Display Conditions for the editor users. ### Activating Display Conditions To activate Display Conditions during Plugin initialization, include the following parameter: ```js { ... "conditionsEnabled": true, // activation of the Display Conditions control in the Editor ... } ``` This activates the Conditions tab in the editor: ::: image-wrap ![](/img/plugin/new/image59.webp){width=1200 height=542} ::: ### Allowing Local Display Conditions Creation To allow users to create local Display Conditions directly in the editor, you need to enable the custom conditions setting during the Plugin initialization. This provides users with the flexibility to define conditions based on their specific needs without relying solely on predefined conditions. Add the following setting to your Plugin initialization configuration: ```js { ... "conditionsEnabled": true, "customConditionsEnabled": true, // enables creating Local Display Conditions inside the Editor ... } ``` Once activated, users will see an option to create custom conditions in the editor: ::: image-wrap ![](/img/plugin/new/image60.webp){width=350 height=548} ::: ### Setting Up Predefined Conditions If you already have a predefined list of display conditions and want to show them in the editor so that users can simply choose from the available options — you can pass these conditions during the plugin initialization, as shown in the example below. Example configuration for predefined conditions with categories: ```js { ... "conditionCategories": [ { "category": "Gender", "conditions": [ { "id": 1, "name": "Female", "description": "Only female customers will see this part of the email.", "beforeScript": "{% if contact.gender == \"Female\" %}", "afterScript": "{% endif %}" }, { "id": 2, "name": "Male", "description": "Only male customers will see this part of the email.", "beforeScript": "{% if contact.gender == \"Male\" %}", "afterScript": "{% endif %}" } ] } ], ... } ``` ### Parameter Description: | Parameter | Description | | :----------- | :-------------------------------------------------------------------------- | | `category` | Name of the category | | `conditions` | An array of condition details | **Conditions Details:** | Parameter | Description | | :------------- | :------------------------------------------------------------------------ | | `id` | Unique identifier of the condition | | `name` | Name of the condition | | `description` | Description of the condition | | `beforeScript` | Content to be inserted into the email before the block with the condition | | `afterScript` | Content to be inserted into the email after the block with the condition | Here is how the predefined conditions will appear in the editor for users to choose from: ::: image-wrap ![](/img/plugin/new/image206.webp){width=1193 height=551} ::: ### Predefined External If you don't want to (or cannot) pass a predefined array of display conditions and prefer to let users create them manually, there's an alternative approach using external conditions. This method allows you to define an extension that opens a custom modal window when the display condition feature is activated in the editor. Within your modal, you can present the conditions in any format — even provide a full builder interface to generate them on the fly. Once the user completes their input, the resulting condition code is passed back via a callback and automatically inserted into the email. * **Documentation**: * **Code example (JavaScript)**: * **Code example (TypeScript)**: ## Link Selector Customization The Link Selector Customization feature allows you to set up the view of available link protocols independently. If certain protocols are not needed, you can hide them from customers. **Usage** To customize the link selector, use the following parameters during the initialization of the plugin: ```js { "hideLinks": ['mail', 'tel', 'https://some.special.link.url'], "hideLinksCategories": ['Yespo'] } ``` Where: * `mail`, `tel` are General protocols. * `https://some.special.link.url` represents [special links](/editor-configuration/initialization-settings#special-links-configuration) that you have defined in the editor. * `Yespo` is a category of [special links](/editor-configuration/initialization-settings#special-links-configuration) where it is placed. After enabling these parameters, the specified protocols will be hidden. By customizing the link selector, you can streamline the user interface and ensure that only the necessary link protocols are available to your customers, improving their experience and reducing potential confusion ## Event Handling The `onEvent` parameter is used to define event handlers for various actions within the editor. These events help track user interactions and trigger specific functions in response to those interactions. Below is a list of available events and their descriptions, along with the parameters passed for each event. **Available Events:** ```js {state: 'DESKTOP' | 'MOBILE'} ``` ```js { target: string } ``` ```js { target: string; moduleId: string; moduleCategory: { key: string | number; order: number; translatedValue: string; } syncModule: boolean; blockType: string; } ``` ```js { target: string; moduleId: string; blockType: string; moduleCategory: string; syncModule: boolean; } ``` ```js { target: string, moduleId: string, blockType: string, moduleCategory: string, syncModule: boolean } ``` ```js { moduleId: string; moduleCategory: { key: string | number; order: number; translatedValue: string; } syncModule: boolean; blockType: string; } ``` ```js { moduleId: string; moduleCategory: { key: string | number; order: number; translatedValue: string; } syncModule: boolean; blockType: string; } ``` ```js module_copied: { copied_from: "stripe", moduleId: 1351 } ``` ```js modules_already_deleted: { moduleIds: [1, 150, 100500] } ``` ```js module_detached: { moduleId: 153 } ``` ```js module_sync_property_updated: { moduleId: 1015, sync: true } ``` ```js module_restored: { moduleId: 153 } ``` ```js { html: string } ``` ```js { html: string } ``` ```js { html: string } ``` ```js {blockName: string} ``` ```js {blockName: string} ``` ```js {blockName: string} ``` ```js {blockName: string} ``` ```js {blockName: string} ``` ```js {} ``` **Example Usage:** ```js { "onEvent": function handleEvent(type, params) { switch (type) { case "editor_view_mode_changed": // Handle view mode change break; case "modules_panel_opened": // Handle modules panel opening break; case "module_dropped": // Handle module dropped break; case "module_saved": // Handle module saved break; case "module_updated": // Handle module updated break; case "module_deleted": // Handle module deleted break; case "module_removed": // Handle module removed break; case "module_copied": // Handle module copied from another module break; case "modules_already_deleted": // Handle detection of modules that were already deleted from the Modules Library break; case "module_detached": // Handle detaching a synchronized module from the Modules Library break; case "module_sync_property_updated": // Handle change of the module synchronization state break; case "module_restored": // Handle restoring module content from the Modules Library break; case "structure_dropped": // Handle structure dropped break; case "structure_deleted": // Handle structure deleted break; case "structure_copied": // Handle structure copied break; case "block_added": // Handle block added break; case "block_dropped": // Handle block dropped break; case "block_copied": // Handle block copied break; case "block_deleted": // Handle block deleted break; case "block_moved": // Handle block moved break; case "email_restored": // Handle email restored from version history break; default: console.warn("Unhandled event type:", type); } }, ... } ``` By configuring the `onEvent` parameter, you can effectively monitor and respond to user interactions within the editor, providing valuable insights and enhancing the overall user experience. ## UTM Parameters The `utm` parameter is used to configure the initial settings for UTM tags in your email templates. These tags are essential for tracking the performance of your email campaigns in analytics platforms. **Use Case:** Imagine you are setting up an email marketing campaign and you need to track its performance in Google Analytics. By using UTM tags, you can identify which email brought traffic to your website and how effective it was in terms of conversions. The `utm` parameter allows you to predefine these tags, ensuring that all links in your email carry the necessary tracking information. ::: image-wrap true ![](/img/plugin/new/image19.webp){width=400 height=603} ::: Here's a detailed description of each parameter and its usage. **Parameter Description:** **Example Usage:** ```js { ..., "utm": { "utmSource": "Yespo", "utmMedium": "email", "utmCampaign": "Spring_Sale", "utmContent": "Banner", "utmTerm": "Discount", "customUtms": [ { "name": "utmCustom1", "value": "someValue" } ] }, ... } ``` **Detailed Explanation:** 1. **utmSource:** This parameter identifies the source of the traffic. For example, if your emails are sent through the Yespo platform, you might set `utmSource` to "Yespo". 2. **utmMedium:** This parameter specifies the medium of the campaign. In the case of email campaigns, you would typically set this to "email". 3. **utmCampaign:** This parameter names the campaign. For example, if you are running a Spring Sale, you could set `utmCampaign` to "Spring\_Sale". 4. **utmContent:** This parameter differentiates content within the same ad or campaign. For instance, if you have multiple banners, you could label them individually using `utmContent`. 5. **utmTerm:** This parameter is used for paid search campaigns to capture the keyword term. Even if you are not running paid search campaigns, you can use this field to track specific promotions or discounts, like "Discount". 6. **customUtms:** This array allows you to add additional custom UTM tags that may be specific to your needs. Each custom UTM includes a `name` and a `value`. **How to Utilize:** 1. **Predefine UTM Tags:** Set the initial values for UTM tags in your email templates using the `utm` parameter. This ensures consistency across all your email campaigns. 2. **Add Custom Tags:** Use the `customUtms` array to include any additional tracking parameters that are unique to your campaign requirements. 3. **Track Campaign Performance:** By embedding these UTM tags in your email links, you can track the effectiveness of your campaigns in your analytics platform, such as Google Analytics. This helps you understand which emails drive the most traffic and conversions, allowing you to optimize future campaigns. By setting up these UTM tags initially, you ensure that all the necessary tracking information is included in your email links, facilitating comprehensive campaign performance analysis. ## AMP Form Services The `ampFormServices` parameter is used to predefine backend endpoints available for AMP Form blocks in the editor. This feature simplifies the setup of AMP forms by allowing users to select a ready-to-use endpoint from a dropdown list instead of manually typing a URL. It helps maintain consistency, prevents configuration errors, and ensures that form data is sent only to authorized endpoints. **Use Case** Imagine your users frequently include AMP forms in their emails — for example, to collect feedback, register event participants, or confirm attendance. Instead of manually inserting different submission URLs, you can provide them with a list of predefined endpoints. When they drop an AMP Form block into the template, the settings panel displays those endpoints as selectable options. ::: image-wrap ![](/img/plugin/new/image236.webp){width=1999 height=899} ::: Selecting a service automatically assigns its URL to the form’s `action-xhr` attribute, ensuring correct data routing. **Parameter Description** | Parameter | Description | | ----- | ----- | | `ampFormServices` | Defines the list of predefined backend endpoints available for selection in AMP Form block settings. Each service includes a label (visible name) and a value (submission URL). | | `showExternalAmpFormServices` | Controls whether users can add custom external services manually. If set to `false`, only predefined endpoints from `ampFormServices` will be available. Default: `true`. | **Example Usage** ```js { ..., "ampFormServices": [ { "label": "Feedback form", "value": "https://example.com/api/amp/feedback" }, { "label": "Event registration", "value": "https://example.com/api/amp/register" } ], "showExternalAmpFormServices": false } ``` In this example: * Two form services (“Feedback form” and “Event registration”) are predefined. * The user will see these two options in the dropdown list when configuring the AMP Form block. * The “Add external service” option will be hidden, ensuring that only approved endpoints are used. ::: image-wrap ![](/img/plugin/new/image237.webp){width=1999 height=980} ::: --- --- url: https://plugin.stripo.email/editor-configuration/image-gallery.md --- # Image Gallery ## Folders Configuration You can access the image gallery when you or your users choose an image for the "Image" block. You independently decide how many folders it contains and whether the user can only use it as drag-and-drop or also upload images to it for future use. For example, below you can see three folders in the image gallery named "Email", "Project", and "Common". ::: image-wrap ![](/img/plugin/new/image20.webp){width=450 height=298} ::: These folders were created and configured on the Plugin details page as shown below. ::: image-wrap ![](/img/plugin/new/image21.webp){width=1476 height=1090} ::: In this section, you can manage: * **Number of folders:** Decide how many folders you need. * **Folder names:** Specify the names of created folders in every supported language. * **Folder path:** Define the path where images should be stored in each folder. * **Permissions:** Grant respective permissions. You can specify a dynamic folder path to the repository and insert variables that may be provided during the initialization of the Stripo editor. For example, if you want to separate images for different email templates, you can create the "Emails" folder and specify its path as `${templateId}`. When initializing the Plugin, pass the `templateId` value (e.g., 00000) among the parameters in the `metadata`, and the Plugin will fetch images from the “00000” folder (this folder will be automatically created on the configured server if it does not exist yet). :::success Please be advised that by default, the Stripo plugin also creates technical folders to store generated banners, image previews for the “Video” basic block, etc. These folders cannot be managed from the Stripo Plugin details page. ::: If you want to set the User role for a specific folder and prevent your clients from removing images, initialize the Plugin with the additional configuration: ```js "hideDeleteImageAction": true ``` ::: image-wrap ![](/img/plugin/new/image22.webp){width=400 height=444} ::: This code hides the Delete option for images and leaves only the editing option for the User role. ::: image-wrap ![](/img/plugin/new/image23.webp){width=400 height=431} ::: If you have any questions on this matter, please contact our support team at . ## Upload Settings Here you can specify the maximum size of an image uploaded to the gallery. The maximum value cannot exceed 20 MB per document. Also you can activate the Image Compression control that helps to compress JPEG images for newly uploaded files. You can set up the desired compression level for your JPEG files (Low/Medium/ High) - that corresponds to 75%, 55% and 30% accordingly. Additionally, you can activate PNG Compression and add your API key from the TinePNG service. This service will automatically set up the suitable compression for the PNG images in order not to lose quality. ::: image-wrap ![](/img/plugin/new/image55.webp){width=1426 height=854} ::: **Use Case** This control is useful for reducing image file sizes, optimizing email load times, and ensuring better performance across different devices. **How to Support** Enable this feature in the plugin configuration settings. You can then specify the compression level for the images. ## Stock Image Configuration If you want to provide your users with the option to search for and use free stock images within the image gallery while creating an email, simply activate the "Stock images configuration" option. ::: image-wrap ![](/img/plugin/new/image25.webp){width=1140 height=1066} ::: Once activated, specify the stock folder name, choose an available stock images provider, and configure it according to the provided instructions. The list of providers may be extended in the future. ## Using Custom Image Gallery via Extensions If you want to replace Stripo’s default image gallery with your own image management system or media library, you can implement this via the Stripo **Extensions** framework. When configured, your custom gallery will open instead of the default one whenever a user clicks to insert or edit an image. You are free to define your own UI, search, filtering, or upload logic — as long as it returns a valid image URL and alt text for insertion into the email. **Availability:**\ This functionality is available starting from plugin version **2.21.0** and only for **Business** and **Enterprise** plans. **Documentation:** Please refer to [this guide](/extensions/core-concepts#external-image-library) to implement this feature. --- --- url: https://plugin.stripo.email/editor-configuration/image-storage.md --- # Image Storage With the Stripo Plugin, you have the flexibility to choose where all your images should be stored, and this feature is available for all pricing plans. While Stripo provides its own storage as the default option, you may prefer to use your own storage solutions for better control and management. ::: image-wrap ![](/img/plugin/new/image26.webp){width=1999 height=962} ::: ## Default Stripo Storage :::success Please be advised that there might be a limitation to the use of the Stripo storage depending on the selected Plugin subscription plan. In order to have full control over the images used by your users in newsletters, we do recommend keeping them on your own file storage servers. ::: ## Custom Storage Options If you prefer not to use Stripo's storage, you can configure the Plugin to use any other preferred storage option. Below are the configurations for the AWS S3 bucket, Azure Blob storage, Cloudinary, and your own server. ### AWS S3 Bucket AWS S3 bucket is a Plugin application configuration feature that allows you to easily connect your own Amazon Web Services S3 bucket to our Plugin for storing images. ::: image-wrap ![](/img/plugin/new/image27.webp){width=1266 height=942} ::: If you choose this option, you’ll have to fill out a form to establish a connection with your storage. Please take a look at the image above to see the description of the form fields with specifications regarding the information required for each of them. | Parameter | Required | Description | | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | S3 bucket name | Yes | The name you assigned to the bucket when creating it. | | Access key | Yes | You can provide AWS Root Account Credentials or IAM User Credentials (we recommend the second option for security reasons). The provided account must have the “Read” and “Write” access to the given bucket. [More about AWS credentials](http://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html). | | Secret access key | Yes | You can provide AWS Root Account Credentials or IAM User Credentials (we recommend the second option for security reasons). The provided account must have the “Read” and “Write” access to the given bucket. [More about AWS credentials](http://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html). | | Region | Yes | AWS region where you created the bucket. | | Base download url | Yes | Define the path that will be specified at the beginning of each URL to the images hosted in your S3 bucket. For example, it may be your CDN domain name or any other address, depending on your server configuration. | :::success Please make sure that the provided account has the Read and Write access to the given bucket. ::: #### Configuration of AWS S3 Storage To create custom AWS S3 storage, you need to: ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:GetBucketCORS", "s3:DeleteObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::bucketname", "arn:aws:s3:::bucketname/*" ] } ] } ``` ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucketname/*" } ] } ``` ```json location /content { add_header 'Access-Control-Allow-Origin' '*'; proxy_pass http://{{ S3_BUCKET_URI }}/; proxy_redirect off; } ``` ### Adobe AEM To set up the integration, you need to create an API connection in Adobe and then add the credentials in Stripo. Create a new project; Click **Add API** and select **AEM Assets Author API;** Choose **OAuth Server-to-Server authentication;** Assign a product profile with access to your AEM environment; Make sure the technical account has permission to write to:\ `/content/dam/stripo` Collect required credentials:\ After setup, copy the following values from Adobe: * Client ID; * Client Secret; * Organization ID (ends with @AdobeOrg); * Author host (AEM Author domain); * Delivery URL (AEM Publish domain). Find your AEM URLs:\ You can get them in **Adobe Cloud Manager** → **Environments**: **Author URL** ``` https://author-p-e.adobeaemcloud.com ``` **Publish URL** ``` https://publish-p-e.adobeaemcloud.com ``` **Connect AEM in Stripo:** ::: image-wrap ![](/img/plugin/new/image24.webp){width=1432 height=1092} ::: 1. Go to **Settings → Plugin → Image Gallery** 2. Select **Adobe Experience Manager** 3. Fill in the required fields: * Client ID; * Client Secret; * Organization ID; * Author host; * Delivery URL. 4. Click **Save.** #### How does it work after setup? Once connected: * Images uploaded to emails are stored in AEM automatically; * Files are saved under: `/content/dam/stripo//` * Images are automatically published; * Assets are delivered via your Adobe CDN. :::success **Important notes** * Client Secret is stored securely and hidden after saving * If uploads stop working, check AEM permissions for `/content/dam/stripo` * If you regenerate Client Secret in Adobe, update it in Stripo immediately ::: ### Azure Blob Storage Azure Blob storage is a Plugin application configuration feature that allows you to easily connect your own Azure storage account to our Plugin for storing images. ::: image-wrap ![](/img/plugin/new/image28.webp){width=1270 height=622} ::: To do so, you need to generate a connection string in your Azure portal account: ::: image-wrap ![](/img/plugin/new/image29.webp){width= height=} ::: If you choose the “Azure Blob Storage” option, you will have to fill out the form to establish a connection with your storage. Please take a look at the image above to see the description of the form field with specification regarding the information that you will need to enter there: | Parameter | Required | Description | | ------------------------- | -------- | ------------------------------------------------- | | `Azure connection string` | Yes | Connection string from your azure portal account. | ### Cloudinary Cloudinary allows you to manage, optimize, and deliver images seamlessly across different platforms. To store your images using Cloudinary, follow these steps: ### Google Cloud We are now also integrated with Google Cloud so you can connect it with Stripo and store your images here. Let's now check how to set it up and find the credentials needed to connect it. ### Connecting Your Own Server This option may be the best choice for you if you’re using another storage type or want to build a more custom and flexible solution to host your images. We created a way to connect the Plugin to a custom file system provider (via HTTPS protocol), allowing you to use the Stripo editor with your own file storage, no matter which technology you use. It is required to support the set of the methods described below to provide successful communication between the two systems: the Stripo server and yours. The Basic Authentication is used to send these requests, so please make sure that you have specified the correct Login, Password, and Base API URL on the Stripo Plugin details page of your Stripo account (if you don’t have an account, please [sign up](https://stripo.email/plugin/)). ::: image-wrap ![](/img/plugin/new/image30.webp){width=1270 height=936} ::: Note, that your storage must support chunked-encoding mode if you want to get logs about any request. See more [here](https://github.com/ardas/stripo-plugin-samples/blob/master/server-side-api-file-uploader/bin/application.properties) #### OpenAPI Specification ```yaml openapi: 3.0.1 info: title: Stripo Plugin Storage API description: | It is required to support the set of the methods described below to provide successful communication between the two systems: the Stripo server and yours. The Basic Authentication is used to send these requests. Note, that your storage must support chunked-encoding mode if you want to get logs about any request. See more [here](https://github.com/ardas/stripo-plugin-samples/blob/master/server-side-api-file-uploader/bin/application.properties). version: 1.0.0 servers: - url: https://{YOUR_BASE_URL} description: Base URL for API requests variables: YOUR_BASE_URL: default: '' components: securitySchemes: basicAuth: type: http scheme: basic schemas: Document: type: object properties: url: type: string description: Absolute URL to the document. originalName: type: string description: Document name. uploadTime: type: integer description: Document upload time in milliseconds. size: type: integer description: Document size in bytes. height: type: integer description: Document height in pixels (px). width: type: integer description: Document width in pixels (px). thumbnailUrl: type: string description: Absolute URL of document preview thumbnail. aiGenerated: type: boolean description: | Optional. `true` if the image was generated with AI. Return the flag your storage persisted at upload time; for regular images the field can be omitted entirely. Folder: type: object properties: key: type: string description: Generated key for the folder. documents: type: array items: $ref: '#/components/schemas/Document' paths: /: get: tags: - Methods summary: Get list of files description: Allows you to retrieve a list of files within specified folders. This method helps you manage and access your file directory efficiently by providing a comprehensive list of available files in the targeted folders, streamlining your workflow and file management within the Stripo platform. security: - basicAuth: [] parameters: - name: keys in: query required: true description: Repeated query parameter, one occurrence per folder key. schema: type: string responses: '200': description: A list of documents grouped by folder keys. content: application/json: schema: type: array items: $ref: '#/components/schemas/Folder' post: tags: - Methods summary: Upload file to storage description: | Enables you to upload an image to a specified folder in your storage. This method facilitates the organization and management of your images by allowing you to easily add new files to designated locations within your Stripo storage, ensuring your media assets are well-organized and readily accessible. > [!WARNING] > **Filename uniqueness is your responsibility.** Stripo does not rename files for Other storage. If a file with the same name already exists in the folder, your server must save the new file under a modified name and return both the modified `originalName` and the corresponding `url` pointing to the newly saved file. If you return the same `originalName` and `url` as an already existing file — Stripo will use them as-is, which may cause the new image to overwrite the existing one or conflict with images previously hidden from the gallery. > [!NOTE] > When a user inserts an image created with the editor's AI image generation, the upload request to your storage includes the `aiGenerated: "true"` attribute. Persist this flag together with the image and return it as a boolean in the `aiGenerated` field of the `Document` object — in this upload response, in `GET /` listings and in `GET /info`. Your platform may need it to distinguish AI-generated visuals, for example to meet the EU AI Act transparency requirements. Visible disclosure of AI-generated content is the responsibility of the platform that displays the image to end users. security: - basicAuth: [] requestBody: content: multipart/form-data: schema: type: object properties: key: type: string description: Folder key where the file will be uploaded. file: type: string format: binary description: Multipart file to be uploaded. aiGenerated: type: string description: | Optional. Sent with the value `"true"` only for images created with the editor's built-in AI image generator; for regular uploads this part is not sent at all. Multipart form parts are always textual, so the value arrives as the string `"true"`. Your storage is expected to persist this flag together with the image. responses: '200': description: Uploaded file details. content: application/json: schema: $ref: '#/components/schemas/Document' /delete: post: tags: - Methods summary: Remove file from storage description: Marks a file as removed, so it is no longer displayed within the folder for the customer. Physically, the file is not deleted from storage, ensuring data integrity and the possibility of recovery if needed. This method helps maintain an organized and clutter-free storage view for users. security: - basicAuth: [] requestBody: content: application/json: schema: type: object properties: url: type: string description: Absolute URL to the document. responses: '200': description: Successful deletion response. content: application/json: schema: type: object properties: message: type: string /info: get: tags: - Methods summary: Get file info description: Allows you to retrieve detailed information about a specific file. This information helps your customers see useful details in the editor once the image is selected on the UI, enhancing their file management and editing experience within the Stripo platform. security: - basicAuth: [] parameters: - name: src in: query required: true description: Absolute URL of the document. schema: type: string responses: '200': description: Specific file information. content: application/json: schema: type: object properties: originalName: type: string description: Document original name. size: type: integer description: Document size in bytes. aiGenerated: type: boolean description: | Optional. `true` if the image was generated with AI. Return the flag your storage persisted at upload time; for regular images the field can be omitted entirely. security: - basicAuth: [] ``` :::custom-warning **Note:** If a file with the same name already exists in the folder, your server must save the new file under a modified name and return both the updated `originalName` and the correct `url` pointing to the newly saved file in the response (e.g., `originalName: "image_a1b2.png"` and `url: "https://your-storage.com/folder/image_a1b2.png"`). ::: #### Parameters | Parameter | Description | | ----- | ----- | | **`key`** | Generated automatically from the Plugin ID and the value specified in the [Folder path](/editor-configuration/image-gallery#folders-configuration) field. For example, `key=0000000_99999`, where `0000000` is the Plugin ID and `99999` is the value set to the Folder path. | | `documents` | An array of the uploaded documents grouped by Key. | | `url` | Absolute URL to the document. | | `originalName` | Document name. | | `uploadTime` | Document upload time in milliseconds. | | `size` | Document size in bytes. | | `height` | Document height in pixels (px). | | `width` | Document width in pixels (px). | | `thumbnailUrl` | The absolute URL of the document preview thumbnail. | :::success Please be advised that the keys for system images (used for a banner block, a video block, and module thumbnails) have different types than those described above. The keys for system images are as follows: * Video block: `pluginId_[application_id]_video` * Banner block: `pluginId_[application_id]_banner` * Module thumbnail: `pluginId_[application_id]_modules` Where `[application_id]` is the Plugin ID of your application. ::: --- --- url: https://plugin.stripo.email/editor-configuration/modules-library.md --- # Modules Library ## What Are Modules If you want to offer your users interesting structures with a non-standard layout or provide the opportunity to save particular elements separately from the entire email (such as structures, containers, or stripes) for future use, you can do so with modules. Modules are reusable components of email templates that help users design emails faster and maintain consistent branding across all campaigns. Each module can include one or several blocks — such as text, images, buttons, or banners — combined into a single editable element. You can save the following types of content as modules: **Stripe** — defines a full-width section (row) of an email layout.\ ::: image-wrap ![](/img/plugin/new/image32.webp){width=1910 height=666} ::: **Structure** — represents a column layout inside a stripe, used to organize content horizontally.\ ::: image-wrap ![](/img/plugin/new/image33.webp){width=1642 height=558} ::: **Container** — a content holder inside a structure where blocks like text, images, or buttons are placed. ::: image-wrap ![](/img/plugin/new/image34.webp){width=1634 height=472} ::: ::: image-wrap ![](/img/plugin/new/image242.webp){width=400 height=866} ::: After you click on the **“Save as Module”** button, you will see the automatic window where you can: * Give the module a name on the settings panel; * Enter a description (optional). The description will later help you understand what content this module contains; * Select a category for easy search; * You can activate **”Keep module styles”** - when enabled, the control allows users to preserve the visual appearance of a module by **inlining global appearance styles from the email message into the module’s HTML** when the module is saved. This helps ensure that the module keeps its original design when reused in other email messages. To use it, please add the corresponding [parameter](/editor-configuration/initialization-settings#keepmodulestylesenabled) to the initialization; * Also, you can activate Synchronization - changes made to this module will be applied to all templates / email messages where this module has been used with the synchronization option activated. To activate this feature, please add the corresponding [parameter](/editor-configuration/synchronized-modules); * Enter tags. The "Tags" field lets you group saved modules by tag. You can add one or several tags. Then, choosing the modules, you will see that your modules are grouped by tags; * Click "Save". ### Editing Modules in a Standalone Editor Besides saving modules from inside an email, you can launch the editor in a dedicated **Module Editing Mode** to create or edit a single module on its own — useful for building a "New module" or "Edit module" flow in your application. This mode is enabled with the `entityType: 'module'` initialization parameter and is available on the Enterprise plan. See [Module Editing Mode](/editor-configuration/module-editing-mode) for the full parameter reference, behavior, and limitations. ## Configuring the Modules Feature ### Enabling Modules Before users can save and reuse modules, the feature must be activated in your Plugin configuration. This is a **global control** that enables the **Modules** tab in the editor, allowing users to save new modules and access previously saved ones by default. ::: image-wrap ![](/img/plugin/new/image238.webp){width=1999 height=1056} ::: If you need to manage this functionality dynamically — for example, show or hide it for specific users or sessions — you can use the `modulesDisabled` [parameter](/editor-configuration/initialization-settings#modulesdisabled) during initialization. When `modulesDisabled` is set to `true`, the **Modules** tab will be hidden, and module saving will be unavailable for that particular session, even if the global control is enabled in the Plugin settings. ### Setting Up Module Folders Similar to the [Image Gallery](/editor-configuration/image-gallery), users will see as many folders (tabs) in the **Modules** tab as you have configured for your Plugin application. ::: image-wrap ![](/img/plugin/new/image239.webp){width=1424 height=1098} ::: On the Plugin configuration page, you can manage the following settings: * **Number of folders:** define how many folders you want to make available to users. * **Folder names:** specify folder names in every supported language. * **Storage key ID:** assign a unique identifier used to store and retrieve modules from the plugin’s database. * **Write permissions:** define which user role has permission not only to view and use modules from this folder but also to **create**, **edit**, or **delete** them. :::success **Please be advised:** * If you set the role for a folder to a user, but the plugin is initialized with a token generated for an **admin** role, the user will still be able to view and insert modules from that folder, but will not be able to modify, delete, or save new ones. Refer to the [Authentication section](/getting-started/authentication#roles) for more details on role configuration. * You can define the **Storage Key ID** as either a static value or a variable (use braces, for example, `${UserId}`). This enables the dynamic separation of module storage for different users.\ **Example:**\ If you want each user to see only their own modules, you can create a folder named My Modules and set its path to `${UserId}`. When initializing the Plugin, pass the user’s ID (e.g., `00000`) to the `metadata` parameter.\ The Plugin will then load modules stored under the key `00000` from its database. If you initialize the Plugin with another user ID (e.g., `00001`), previously saved modules from the first user will no longer be displayed for the new session. ::: ### Configuring Module Categories Each module can belong to a **category**, helping users organize and filter reusable elements more efficiently.\ You can create any number of categories to group modules by theme, purpose, or department — for example, **Headers**, **Footers**, **Promotions**, or **Transactional**. ::: image-wrap ![](/img/plugin/new/image240.webp){width=1444 height=930} ::: Once categories are configured, they will appear in two places within the Plugin interface: * in the **Modules** tab, where users can filter saved modules; * in the **Save / Edit Module** dialog, allowing users to assign a category when saving a new module. To make the module library more focused and relevant to each user group, you can control which categories are visible for particular users.\ Use the following parameter during plugin initialization: `"modulesExcludedCategories": [1, 2]` This parameter hides specific categories (by ID) from the modules list in the editor. It’s useful when certain categories should remain internal or available only to specific roles or environments. ## Synchronized Modules Stripo allows creating **Synchronized Modules** — reusable blocks that update automatically across all templates where they are used. When a synchronized module is updated, the changes are instantly reflected in all templates that contain it. To learn more about synchronization logic and how to handle it programmatically, refer to: [Synchronized Modules](/editor-configuration/synchronized-modules). ## Module Save Validation {#module-save-validation} ### How It Works In some cases, you may want to restrict users from saving or updating a module based on your own business logic — for example: * when a module contains specific HTML code or content, * when synchronization is enabled,\ or when it fails validation in your external system. This can be achieved using the `validateModuleSave` callback. When defined, the editor calls this function each time a user clicks **Save** or **Update** in the module editor. ### Usage Add the `validateModuleSave` parameter to your plugin initialization: ```js { ... "validateModuleSave": function(data) { // Your validation logic here return { canSave: true }; }, ... } ``` You can also define the `validateModuleSave` function as **asynchronous**, for example, when you need to perform validation through an external API or database check before allowing the module to be saved. If the callback is not defined, the save process continues normally. ```js { ... "validateModuleSave": async function(data) { // Example: asynchronous check via external API // Allow saving return { canSave: true }; }, ... } ``` ### Parameters The callback receives a single argument, `data`, which is an object of type `ModuleSaveValidationData`. This object contains all available information about the module. | Parameter | Type | Description | | ----- | ----- | ----- | | `id` | `number` *(optional)* | Unique ID of the module. Empty when creating a new one, populated when updating. | | `name` | `string` *(optional)* | Module name entered by the user. Empty when creating a new one, populated when updating. | | `html` | `string` | Full HTML code of the module, including all nested blocks. | | `type` | `string` | Type of module — `STRIPE`, `STRUCTURE`, or `CONTAINER`. | | `isSynced` | `boolean` *(optional)* | Indicates whether the module is synchronized. | The editor always sends the entire module data object to the `validateModuleSave` callback. You can use any of its fields for your validation logic — for example, analyze the HTML content, check the module type, or restrict updates to synchronized modules. ### Return Value The callback must return an object of the following structure: | Property | Type | Description | | ----- | ----- | ----- | | `canSave` | `boolean` | Determines whether saving is permitted. | | `errorMessage` | `string` *(optional)* | A message displayed in the UI when saving is blocked. | If the callback returns: * { **canSave**: true } → the module is saved as usual. * { **canSave**: false, **errorMessage**: '...' } → saving is blocked, and the provided message is displayed in the UI. If no callback is defined, the save process continues normally. :::success To display a custom message on UI when saving is blocked, ensure that your Plugin implementation supports notifications. Refer to the [Notification Settings](/editor-configuration/initialization-settings#notification-settings) section for setup details. ::: ### Example of **Synchronous Usage** ```js { ... "validateModuleSave": function(data) { // Example: Restrict saving synchronized modules if (data.isSynced) { return { canSave: false, errorMessage: 'Synchronized modules cannot be updated manually.' }; } // Allow saving all other modules return { canSave: true }; }, ... } ``` ### Example of **Asynchronous Usage** ```js { ... "validateModuleSave": async function(data) { // Example: asynchronous check via external API const response = await fetch('https://api.example.com/validate-module', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (!result.isAllowed) { return { canSave: false, errorMessage: result.message || 'Module cannot be saved at this time.' }; } // Allow saving return { canSave: true }; }, ... } ``` If the callback returns a **Promise**, the editor waits for it to resolve before continuing the save process.\ If the Promise resolves to `{ canSave: false }`, saving is blocked and the message is shown in the editor.\ If it resolves to `{ canSave: true }`, the module is saved normally. ## Module Save Preprocessing ### How It Works In some cases, you may want to modify a module right before it is saved or updated in the library — for example: * to normalize or clean up HTML, * to inject additional attributes or metadata, * to apply transformations using [TemplateModifier](/extensions/reference/modification/TemplateModifier#templatemodifier), * or to ensure consistency with your external system. This can be achieved using the `onBeforeModuleSave` callback. When defined, the editor calls this function each time a user initiates: * **Save** in the module panel, * **Update** in the module panel, * **Update Module** for synchronized modules. The callback runs **before the module is saved**, allowing you to modify its content directly in the editor. ### Usage Add the `onBeforeModuleSave` parameter to your plugin initialization: ```js { ... "onBeforeModuleSave": function(data) { // Your preprocessing logic here return { canSave: true }; }, ... } ``` This callback is: * optional, * synchronous, * executed before the module is saved, * independent from `validateModuleSave`. If the callback is not defined, the editor saves the module immediately using the current HTML from the template. If `onBeforeModuleSave` is provided: 1. The user clicks **Save / Update / Update Module**. 2. The editor calls `onBeforeModuleSave`. 3. The editor waits for the response. 4. During this time, a loading indicator is shown on the button. 5. As a plugin integrator, you can: * modify the module using `TemplateModifier`, * update the DOM directly inside the editor. 6. The callback returns a result: * `{ canSave: true }` → proceed with saving, * `{ canSave: false, errorMessage: '...' }` → cancel saving and show message. 7. If allowed: * the editor retrieves the **updated HTML from the template**, * the module is saved to the library. ⚠️ The saved HTML is always taken from the **actual DOM after modifications**, not from any previous snapshot. ### Parameters The callback receives a single argument `data`, which is an object of type `ModuleSaveValidationData`. | Parameter | Type | Description | | ----- | ----- | ----- | | `id` | number (optional) | Unique ID of the module. Empty when creating a new module. | | `name` | string | Module name entered by the user. | | `html` | string | Full HTML of the module at the moment of save. | | `type` | string | Module type — `STRIPE`, `STRUCTURE`, or `CONTAINER`. | | `isSynced` | boolean (optional) | Indicates whether the module is synchronized. | | `moduleNode` | ImmutableHtmlNode | Reference to the actual module node in the template. Can be used with [TemplateModifier](/extensions/reference/modification/TemplateModifier#templatemodifier). | ### Return Value The callback must return an object of the following structure: | Property | Type | Description | | ----- | ----- | ----- | | `canSave` | boolean | Determines whether saving is allowed. | | `errorMessage` | string (optional) | Message displayed if saving is blocked. | * `{ canSave: true }` → module is saved * `{ canSave: false, errorMessage: '...' }` → saving is blocked and message is shown ### Example of Usage ```js { ... "onBeforeModuleSave": function (data) { console.log('[onBeforeModuleSave] called with:', data); const copilotApi = window.StripoEditorApi.editorCopilotApi; const modifier = copilotApi.getTemplateModifier(); modifier .modifyHtml(data.moduleNode) .setAttribute('data-saved-at', new Date().toISOString()) .apply({ key: 'Set module save timestamp', params: {}, getValue() { return { key: this.key, params: this.params }; } }); console.log('[onBeforeModuleSave] Set data-saved-at on module node'); return { canSave: true }; }, ... } ``` ## Module Delete Validation {#module-delete-validation} ### How It Works You can use the `validateModuleDelete` callback to verify whether a module can be deleted from the library before the action is confirmed. This allows you to apply additional business rules — for example: * preventing deletion of synchronized modules, * restricting deletion for specific module types, * or it fails validation in your external system. When a user clicks **Delete** icon in the module library and confirms the action in the pop-up: 1. The editor checks if the `validateModuleDelete` callback is defined. 2. If found, the editor calls this function and passes a complete data object describing the module (`id`, `name`, `html`, `type`, `isSynced`). 3. While waiting for the callback response, a loading indicator appears on the confirmation button. 4. When the callback resolves: * if it returns `{ ok: true }`, the module is deleted; * if it returns `{ ok: false, errorMessage: '...' }`, the deletion is canceled and the message is shown in a notification. 5. If the callback is not defined, the module is deleted immediately, following standard behavior. ### Usage Add the `validateModuleDelete` parameter to your plugin initialization: ```js { ... "validateModuleDelete": function(data) { // Your validation logic here return { ok: true }; }, ... } ``` If the callback is not defined, the module will be deleted immediately after confirmation. You can also define the function as **asynchronous**, for example, if your validation requires a server check or remote API call. ```js { ... "validateModuleDelete": async function(data) { // Example: simulate API request delay // Allow deletion return { ok: true }; }, ... } ``` If the callback returns a Promise, the editor waits for it to resolve before proceeding with deletion. ### Parameters The function receives a single argument `data`, which is an object of type `ModuleDeleteValidationData` containing all available details about the module: | Parameter | Type | Description | | ----- | ----- | ----- | | `id` | `number` | Unique ID of the module. | | `name` | `string` | Module name entered by the user. | | `html` | `string` | Full HTML code of the module, including all nested blocks. | | `type` | `string` | Type of module — `STRIPE`, `STRUCTURE`, or `CONTAINER`. | | `isSynced` | `boolean` *(optional)* | Indicates whether the module is synchronized. | ### Return Value The function must return an object of type `ModuleDeleteValidationResult`: | Property | Type | Description | | ----- | ----- | ----- | | `ok` | `boolean` | Determines whether deletion is permitted. | | `errorMessage` | `string` *(optional)* | A message displayed in the UI when deletion is blocked. | Example of a returned object: ```json { ok: false, errorMessage: 'Stripe modules cannot be deleted.' } ``` :::success To display a custom message on UI when deletion is blocked, ensure that your Plugin implementation supports notifications. Refer to the [Notification Settings](/editor-configuration/initialization-settings#notification-settings) section for setup details. ::: ### Example of **Synchronous Usage** ```js { ... "validateModuleDelete": function(data) { // Example: restrict deletion of synchronized modules if (data.isSynced) { return { ok: false, errorMessage: 'Synchronized modules cannot be deleted.' }; } // Allow deletion of all other modules return { ok: true }; }, ... } ``` ### Example of **Asynchronous Usage** ```js { ... "validateModuleDelete": async function(data) { // Simulate a delay (e.g., API call) await new Promise(resolve => setTimeout(resolve, 2000)); // Example: block deletion for "STRIPE" modules if (data.type === 'STRIPE') { return { ok: false, errorMessage: 'Stripe modules cannot be deleted.' }; } // Allow deletion for all other types return { ok: true }; }, ... } ``` If the callback returns a **Promise**, the editor will wait until it resolves before proceeding. While waiting, a loading indicator appears on the confirmation button in the delete popup. --- --- url: https://plugin.stripo.email/editor-configuration/synchronized-modules.md --- # Synchronized Modules Synchronized Modules allow for seamless updates across all email templates that utilize a specific module. This feature is particularly useful for tasks such as updating branding or contact details, as changes made to a synchronized module will be applied automatically to all templates where the synchronized module is activated. ::: image-wrap ![](/img/plugin/new/image56.webp){width=840 height=372} ::: **How to Activate** To enable the Synchronized Modules feature, initialize the plugin with the following parameter: ```js "syncModulesEnabled": true ``` Once enabled, your customers can save their modules and while saving they can activate the synchronized module option. ::: image-wrap ![](/img/plugin/new/image57.webp){width=350 height=480} ::: ## Usage When a synchronized module is dropped into the email, a "Sync ON" symbol will appear on the module. If customers make any changes, the symbol will change to "Sync OFF". Customers can then choose to: * **Save Changes to the Module**: Apply modifications to the module in the library. * **Revert Changes**: Revert the module to its default (saved) state. * **Unlink and Apply Changes Locally**: This will remove the link with the module in the library. All changes will be saved only in this email template. :::success If a user lacks permission to modify the module, the "Update module" option will be hidden from the settings panel. ::: ## How It Works 1. When the specified parameter is included during plugin initialization, the editor queries its database for all saved synchronized modules based on the configurations provided (such as those in the `metadata` parameter). 2. The editor scans the HTML of the opened template to detect any synchronized modules. If found, it replaces the content with the version saved in the library. 3. If the plugin was initialized with the `syncModulesEnabled` parameter, the [`getTemplateData`](/plugin-invocations/javascript-api#actions-api) method returns the HTML, CSS, and arrays of IDs of synchronized modules used in the template. 4. Once you receive a notification about changes in your email template, you can call the [Get HTML and CSS of email template](/reference/editor-api-retrieving-html#tag/methods/GET/bapi/coediting/v1/email/{emailId}/html-css) method from your server. Our backend returns the HTML, CSS, and IDs of synchronized modules used in the template. 5. The plugin owner must store the information about which synchronized module IDs belong to which template in their database. When a synchronized module is updated, the editor fires an `module_saved` [event](/editor-configuration/initialization-settings#event-handling) that the customer's application must read to start the synchronization of other templates containing that module. 6. To achieve this, run the "[Compiling Email Templates](/plugin-invocations/backend-api#compiling-email-templates)" method ONLY for THOSE templates that include the updated module. --- --- url: https://plugin.stripo.email/editor-configuration/module-editing-mode.md --- # Module Editing Mode :::custom-warning Plan: **Enterprise** only. Available from v2.67.0. ::: By default, the Plugin Editor opens **emails** for editing (`entityType: 'email'`). With Module Editing Mode you can launch the same editor to **create or edit a single reusable module** (a stripe, structure, or container) stored in your plugin's module library — without opening a full email. This lets you use Stripo as a standalone module editor inside your own application: build a "New module" button, an "Edit module" action in your library UI, and let users design modules in the same drag-n-drop editor they already know. The mode is controlled entirely through initialization parameters. No new permanent UI elements are added to the editor. :::custom-warning Module Editing Mode is available on the Enterprise plan only. If the editor is initialized with `entityType: 'module'` on any other plan, it does not initialize and shows a toaster notification signaling that the Enterprise plan is required for this mode. ::: ## **Enabling the Mode ​** Pass `entityType: 'module'` when initializing the editor: ```js window.UIEditor.initEditor(domContainer, { metadata: { /* ... */ }, entityType: 'module', onTokenRefreshRequest: function (callback) { /* ... */ } // + module parameters, see below }); ``` What happens next depends on whether you pass a `moduleId`: * **`moduleId` provided** → the editor opens that existing module for editing. * **`moduleId` omitted** → the editor starts creating a new module (`moduleType` is required). :::custom-warning If `entityType` is omitted or set to `email`, the editor works in the regular email-editing mode and every parameter on this page is ignored. ::: ## Editing an Existing Module Pass `moduleId` to open a module that already exists in your library: ```js window.UIEditor.initEditor(domContainer, { metadata: { /* other parameters */ moduleId: '12345' }, entityType: 'module', onTokenRefreshRequest: function (callback) { /* ... */ } }); ``` :::custom-warning Where to get a `moduleId`: use the [Modules API](/reference/plugin-modules-api) (`GET /api/v1/customblocks/v4/modules/list`) to list the modules that belong to your plugin. It supports filtering by key, category, tags, name, synchronization status, and ID, with pagination — and every returned module includes its `id`, which you pass here. The auth token must have the `API` role. ::: When `moduleId` is provided: * Any `html` and `css` passed in the configuration are **ignored** — the module content is loaded from the plugin database. * The editor validates that the module: * is not deleted, * belongs to the current `pluginId`, * is available for editing. * If validation fails — the module is not found, deleted, or does not belong to this plugin — **the editor does not initialize** and a toaster notification is shown to the user (see Error handling). :::custom-warning When both `moduleId` and `moduleType` are provided, the editor treats it as editing an existing module and ignores `moduleType`.\ The metadata parameters (`moduleName`, `moduleDescription`, `moduleCategoryId`, `moduleTags`) are also ignored when editing an existing module — its existing metadata is preserved. ::: ## Creating a New Module Omit `moduleId` and pass `moduleType` to start a new module: ```js window.UIEditor.initEditor(domContainer, { metadata: { /* ... */ }, entityType: 'module', moduleType: 'STRUCTURE', // 'STRIPE' | 'STRUCTURE' | 'CONTAINER' key: 'shared-modules', // optional: target folder storage key css: '...', // optional: see CSS below onTokenRefreshRequest: function (callback) { /* ... */ } }); ``` Rules: * `moduleType` is **required** and must be one of `STRIPE`, `STRUCTURE`, or `CONTAINER`. If it is missing or invalid, **the editor does not initialize** and a toaster notification is shown (see Error handling). * `key` (optional) is the storage key of the folder the new module will be saved to — the same **Storage Key ID** you configure for module folders (see Modules Library). If omitted, the first folder the user is allowed to write to is used. * An empty module of the chosen type is prepared, but it is **persisted only after the first change** to the module content or its metadata (see Saving). The `html` parameter is **not used** in Module Editing Mode. A new module always starts empty for the chosen `moduleType`; an existing module is always loaded from the database by `moduleId`. Any `html` passed at init is ignored in both cases. ### CSS * If `css` is provided, it is applied to the new module on creation. * If `css` is not provided, the editor's default CSS is applied. * `css` applies only when **creating** a new module; it is ignored when editing an existing module (`moduleId`). ### Initial Metadata for a New Module When creating a new module you can preset its metadata so the module is saved with the right name, description, category, and tags — without requiring the end user to fill them in manually. ```js window.UIEditor.initEditor(domContainer, { metadata: { /* ... */ }, entityType: 'module', moduleType: 'STRIPE', moduleName: 'Promo header', moduleDescription: 'Reusable promotional header with CTA', moduleCategoryId: 12, moduleTags: ['promo', 'header'], onTokenRefreshRequest: function (callback) { /* ... */ } }); ``` | Parameter | Type | Default | Description | | ----- | ----- | ----- | ----- | | `moduleName` | `string` | `New module` | Name of the new module. Maximum length is **200 characters**; longer values are trimmed to 200. | | `moduleDescription` | `string` | empty | Description of the new module. Maximum length is **500 characters**; longer values are trimmed to 500. | | `moduleCategoryId` | `number` | `Uncategorized` | ID of the category assigned to the new module. Silently ignored if the category does not exist or is unavailable for this plugin — initialization is not blocked and no toaster is shown. | | `moduleTags` | `string[]` | no tags | Tags applied to the new module. | These parameters apply **only when creating a new module** (when `moduleId` is not provided). If `moduleId` is provided, all four are ignored without error. If a parameter is omitted, the default value above is used. ## Saving Saving in Module Editing Mode follows your plugin's existing save configuration: * **Autosave** — if autosave is enabled in your Plugin configuration, it works in module mode too. If it is disabled, only manual saving is available via `window.StripoEditorApi.actionsApi.save()`. See Autosaving. * **New modules** are written to the library only **after the first change** to the module content or its metadata. Opening the editor and closing it without edits creates nothing. * The module is stored in Stripo's module database and assigned to your plugin and the target folder key. The module save callbacks already available in the plugin are supported in this mode: * `onBeforeModuleSave` — called on save and update, lets you preprocess the module before it is stored. * `validateModuleSave` — called on save and update, lets you allow or block the operation. ## Module Details Dialog In Module Editing Mode the module metadata (name, description, category, tags) is edited in a details dialog, similar to account mode. You can open this dialog programmatically — for example, from a "Show module data" button in your own UI: ```js window.StripoEditorApi.modulesApi.openModuleDetailsDialog(); ``` See JavaScript API for the full API surface. ## Limitations In `entityType: 'module'` mode the following are **not** supported: * versioning; * version history; * simultaneous (collaborative) editing. The following **work as usual**: * autosave (when enabled); * manual saving; * standard editing logic. ## Compatibility With Other Settings * `html` passed at init is **always ignored** in this mode (a new module starts empty; an existing module is loaded by `moduleId`). * `css` is honored **only when creating** a new module and ignored when editing an existing one. * Version History does not work in Module Editing Mode — version-history controls (e.g. `versionHistoryButtonSelector`) have no effect, because version history is disabled in this mode. * Email-specific metadata and features (e.g. UTM, email title/preheader) do not apply to modules. * All other editor configuration — panel position, locale, permissions, image gallery, fonts, etc. — behaves the same as in email mode. ## Error Handling Errors during initialization are surfaced to the user through the standard toaster. **HTTP error codes (400 / 403) are not returned to the initialization function** — initialization simply does not complete and the user sees a notification. | Situation | Message | | ----- | ----- | | `entityType: 'module'` on a non-Enterprise plan | Editor does not initialize; a toaster notification signals that this mode requires upgrading to the Enterprise plan. | | Invalid or unavailable `moduleId` (not found, deleted, or not owned by this plugin) | Module not found or unavailable for this plugin | | Missing or invalid `moduleType` (create mode) | Invalid module type. Allowed values: Stripe, Structure, Container. | :::custom-warning To display these messages, your plugin implementation must support notifications. See [Notification Settings](/editor-configuration/commenting#notifications-api). ::: ## Example ```js // Edit an existing module window.UIEditor.initEditor(document.getElementById('editor'), { metadata: { /* other mandatory parameters */ moduleId: '8842', }, entityType: 'module', onTokenRefreshRequest: refreshToken }); // Create a new "structure" module preset with metadata, saved to a specific folder window.UIEditor.initEditor(document.getElementById('editor'), { metadata: { /* ... */ }, entityType: 'module', moduleType: 'STRUCTURE', key: 'marketing-modules', moduleName: 'Footer · Legal', moduleDescription: 'Standard legal footer', moduleCategoryId: 8, moduleTags: ['footer', 'legal'], onBeforeModuleSave: function (data) { return { canSave: true }; }, validateModuleSave: function (data) { return { canSave: true }; }, onTokenRefreshRequest: refreshToken }); ``` --- --- url: https://plugin.stripo.email/editor-configuration/advanced-controls.md --- # Advanced Controls In this section, we have gathered advanced controls available in paid subscriptions that enhance the email template creation experience. These controls provide powerful customization options, allowing users to tailor their emails with precision and flexibility. Below is the list of these controls with detailed explanations for each. ## Element Hiding The Element Hiding control allows users to hide specific elements on mobile or desktop devices. This control can be added to every basic block (e.g., “Image,” “Text,” “Button”), containers, structures, and stripes. ::: image-wrap ![](/img/plugin/new/image37.webp){width=768 height=116} ::: **Use Case** When a user wants certain elements to be visible only on mobile or desktop, they can activate this control. For example, promotional banners might be hidden on mobile for a cleaner look. **How to Support** To enable this feature, ensure it's activated in the plugin configuration settings. ## Mobile Padding The Mobile Padding control enables users to set individual padding for structures, stripes, and basic blocks (e.g., “Image,” “Text,” “Button”) specifically for mobile devices. It appears in the Padding section when configuring these elements. ::: image-wrap ![](/img/plugin/new/image39.webp){width=350 height=164} ::: **Use Case** This control is useful when users need to adjust padding for better mobile display. For instance, increasing padding around a button to make it more touch-friendly on smaller screens. **How to Support** Activate this control in the plugin configuration settings. Users can then enter specific padding values for mobile devices. ## Containers Inversion on Mobile The Containers Inversion on Mobile control allows users to specify the order in which containers appear on mobile devices. It is available for structures with exactly two containers and will only be active if the “Responsive structure” control is enabled. ::: image-wrap ![](/img/plugin/new/image40.webp){width=350 height=152} ::: **Use Case** This control is useful for adjusting the visual hierarchy on mobile. For example, reversing the order of content blocks to prioritize certain information on smaller screens. ::: image-wrap ![](/img/plugin/new/image41.webp){width=1999 height=914} ::: **How to Support** Enable this feature in the plugin configuration settings and ensure the “Responsive structure” control is active. ## Basic Block Alignment on Mobile The Basic Block Alignment on Mobile control allows users to set different alignments for basic blocks (e.g., “Image,” “Text,” “Button”) on mobile devices. If the user switches to the mobile version of the email template while working in the editor, this control will appear on the settings panel of the selected element. ::: image-wrap ![](/img/plugin/new/image42.webp){width=350 height=60} ::: **Use Case** This control is ideal for ensuring that elements are properly aligned on mobile devices, enhancing the readability and overall appearance of the email on smaller screens. **How to Support** Activate this feature in the plugin configuration settings. Users can toggle the mobile version to set different alignments for mobile. ## Border of Content Part of the Stripe The Content Border control allows users to set borders for the content area of a selected stripe. Users can configure the border type (straight, dashed, dotted), its color, and width for all sides or specific sides (e.g., only on the left or top). ::: image-wrap ![](/img/plugin/new/image43.webp){width=350 height=332} ::: **Use Case** This control is useful for enhancing the visual separation of content within a stripe, providing a more polished and defined look. **How to Support** Activate this feature in the plugin configuration settings. Users can then customize the border settings as needed. ## Container Background Color The Container Background Color control allows users to set the background color for a selected container. This control will be added to the set of container’s controls and will be visible in the settings panel when configuring the container. ::: image-wrap ![](/img/plugin/new/image44.webp){width=350 height=152} ::: **Use Case** This control is beneficial for highlighting specific containers or matching the container background with the overall design theme of the email. **How to Support** Activate this feature in the plugin configuration settings. Users can then select and apply their desired background color. ## Structure Background Image The Structure Background Image control allows users to set any image as the background for a selected structure. Users can configure the alignment and choose to repeat the image if desired. This control will be added to the set of structure’s controls and will be visible in the settings panel when configuring the structure. ::: image-wrap ![](/img/plugin/new/image45.webp){width=350 height=214} ::: **Use Case** This control is useful for adding visual interest and branding to specific sections of the email by using custom background images. **How to Support** Activate this feature in the plugin configuration settings. Users can then upload and adjust their background images. ## Container Background Image The Container Background Image control allows users to set any image as the background for a selected container. Users can configure the alignment and choose to repeat the image if desired. This control will be added to the set of container’s controls and will be visible in the settings panel when configuring the container. ::: image-wrap ![](/img/plugin/new/image46.webp){width=350 height=207} ::: **Use Case** This control is useful for adding visual interest and branding to specific containers within the email by using custom background images. **How to Support** Activate this feature in the plugin configuration settings. Users can then upload and adjust their background images. ## Management of Container Numbers and Sizes in the Structure The Management of Container Numbers and Sizes in the Structure control allows users to add or remove containers within a selected structure, with a limit of up to 11 containers per structure. It also enables adjusting the width of each container and the indents between them. This control will be added to the set of structure’s controls and will be visible in the settings Panel when configuring the structure. ::: image-wrap ![](/img/plugin/new/image47.webp){width=350 height=420} ::: **Use Case** This control is useful for customizing the layout of email sections by dynamically managing the number and size of containers within a structure. **How to Support** Activate this feature in the plugin configuration settings. Users can then add, remove, and adjust container sizes as needed. ## Image Path Configuration The Image Path Configuration control adds an extra "Link" control to the Image block (and other blocks like Menu, Banner). It allows users to specify or replace the image URL without modifying the HTML code, which is useful for hosting images externally. ::: image-wrap ![](/img/plugin/new/image48.webp){width=350 height=244} ::: **Use Case** This feature is beneficial for users who prefer to host images on their own storage or an external site rather than the default storage. **How to Support** Activate this feature in the plugin configuration settings. ## Smart-elements Properties The Smart-elements Properties control allows users to activate smart properties for containers, structures, and stripes. When this control is activated, a new "Data" tab appears above the set of controls for the selected element, enabling users to configure smart properties. ::: image-wrap ![](/img/plugin/new/image49.webp){width=350 height=273} ::: **Use Case** This feature is useful for automating the creation of similar elements and reducing the time required for template design. **How to Support** Activate this feature in the plugin configuration settings. For detailed information, refer to [the Smart Elements Blog Post](https://stripo.email/blog/smart-elements-reducing-time-creating-similar-letters-automating/). ## AMPHTML MIME Type Support The AMPHTML MIME Type Support control allows users to include AMP components in email templates, enabling dynamic and interactive content within emails. This feature adds a new control to the settings panel for blocks or containers, allowing users to specify whether an element should be included in the traditional HTML version, the AMP HTML version, or both. ::: image-wrap ![](/img/plugin/new/image50.webp){width=350 height=92} ::: **Use Case** This control is useful for creating engaging and interactive email experiences, leveraging AMP technology supported by Gmail and other clients. **How to Support** Activate this feature in the plugin configuration settings. For detailed information, refer to [How to Build AMP Emails with Stripo](https://stripo.email/blog/how-to-build-amp-emails-with-stripo/). **Additional Note** Please be advised that if the email template has at least one element included in the AMP version (or contains custom AMP components) when you call the [`compileEmail`](https://plugin.stripo.email/plugin-invocations/javascript-api#actions-api) JS function or the [`Compiling Email Templates`](https://plugin.stripo.email/plugin-invocations/backend-api#compiling-email-templates) method from your server, you will receive both the HTML version and the AMP HTML version in the response. For more details, refer to the [Plugin Invocations](https://plugin.stripo.email/plugin-invocations) section. ## Image Editor The Image Editor control allows users to apply various effects to images, change their sizes, shapes, and more. When activated, a new control is added to the settings panel for any selected image in the email template. ::: image-wrap ![](/img/plugin/new/image51.webp){width=350 height=182} ::: **Use Case** This control is useful for editing images directly within the email editor, enabling quick adjustments and enhancements without the need for external tools. **How to Support** Enable this feature in the plugin configuration settings. Users can then access the image editor by selecting any image within their email template. ### Hide Stickers in Image Editor If you want to hide certain elements from the “Sticker” menu for your customers, add these parameters to your initialization script: ```js "imageEditor": { "stickers": { "emotIcons": { "exclude": [ "asian", "asian-1", "afro" ] } } } ``` Icons will not be visible to the end user: ::: image-wrap ![](/img/plugin/new/image52.webp){width=1719 height=178} ::: If you want to display only specific stickers, use these parameters: ```js "imageEditor": { "stickers": { "emotIcons": { "include": [ "asian", "asian-1", "afro" ] } } } ``` Your customer will see only those icons: ::: image-wrap ![](/img/plugin/new/image53.webp){width=1710 height=745} ::: ### Prohibit Image Modification In certain scenarios, you might want to restrict users from modifying images that come from specific domains. This can be useful for maintaining brand consistency or ensuring that certain images remain unchanged. You can achieve this by configuring the plugin to restrict image editing based on image path domains. To prohibit image modification for images from specific domains, add the following configuration to your plugin initialization script: ```js { "imageEditor": { "restrictedUrlRegexList": [ "domain1", "domain2", "domainN" ] } } ``` Where: * `domain1`, `domain2`, `domainN` are the domain names for images. You can specify one or multiple domain names, separated by commas. As a result, when your customer clicks on an image from these domains, the icon for editing it will not appear: ## Rollover Effect The Rollover Effect, also known as mouseover, replaces an image with another when the mouse cursor hovers over it. This effect works on desktop devices only, and can be applied to any image except banners. ::: image-wrap ![](/img/plugin/new/image54.webp){width=350 height=365} ::: **Use Case** This control is useful for creating interactive and engaging visuals within emails, enhancing user experience with dynamic image changes. **How to Support** Activate this feature in the plugin configuration settings. Users can then toggle the “Rollover effect” button in the settings panel, upload images, and configure necessary properties. --- --- url: https://plugin.stripo.email/editor-configuration/artificial-intelligence.md --- # Artificial Intelligence Supported since plugin version **2.17.0** This section describes the AI capabilities available in the Stripo Plugin, which can be enabled via the **Artificial Intelligence** section of the plugin configuration page. When activated, AI features enhance user productivity by assisting in content creation, image description, subject line optimization, and more. Plugin owners can selectively enable specific features and define the AI model used for image generation. ::: image-wrap ![](/img/plugin/new/image198.webp){width=1999 height=1020} ::: Some AI-powered tools require additional credentials to operate. In these cases, Stripo will prompt for the necessary API tokens or keys. These credentials are used solely to request data from the selected third-party services (OpenAI, Google Gemini, Stability AI) under the plugin owner's account. [How to Get API Keys for AI Models?](#how-to-get-api-keys-for-ai-models) For text-related features (text blocks, Smart modules, subject line suggestions, alt-text, and GPT-Image-1.5 image generation), an **OpenAI API key** with access to the **gpt-4o** model is required. This key must belong to a paid plan and be tied to a company/organization account, not a personal user. ## Improve Subject Lines and Hidden Preheader This utility helps marketers optimize subject lines and hidden preheaders using AI-generated suggestions. It enhances email engagement by offering smart phrasing options that improve open rates and attract user attention. **Where it appears** ::: image-wrap ![](/img/plugin/new/image243.webp){width=400 height=365} ::: * When creating or editing an email, you'll find the magic wand icon next to the **Subject** **Line** and **Hidden Preheader** input fields. * Click the icon to open a modal with suggestions generated by AI. * You can scroll through multiple suggestions and apply the one you prefer by clicking **Insert**. **How suggestions are generated** * The AI analyzes the email’s content, including key topics, tone, and goal of the campaign. * It leverages best practices from marketing communications to offer subject lines that: * spark curiosity, * highlight urgency or exclusivity, * personalize the message, * or offer clear value propositions. * The generated suggestions aim to boost open rates while maintaining alignment with the email's tone and structure. * Suggestions are concise (typically 40–60 characters for subject lines, 50–100 characters for preheaders) and avoid spam-triggering words or formatting. * Users can regenerate suggestions if the first set doesn’t meet their expectations. **How to activate** * Go to **Plugins → Artificial Intelligence**. * Enable the toggle for **"Improve Subject Lines and Hidden Preheader"**. * Insert your OpenAI API key. **Requirements** * OpenAI API Key * Access to the `gpt-4o` model * Organization-level key (not personal) ## AI Assistant for the Text Block The AI Assistant for the Text block helps users quickly generate, rewrite, or improve written content inside emails. It is designed to save time and inspire more effective communication. **Where it appears** ::: image-wrap ![](/img/plugin/new/image200.gif){width=1200 height=344} ::: * When a user selects any **Text block**, a **magic wand icon** appears in the text toolbar above the content area. * Clicking this icon opens a panel with access to the AI assistant features. **How it works** * The assistant offers several predefined actions (or "prompts"): * **Fix Grammar** – Correct grammatical errors in the selected text. * **Make Shorter** – Reduce the length of the selected content. * **Make Longer** – Expand the content with more detail or structure. * **Translate** – Translate the selected text into English. * **Change Tone of Voice** – Adjust tone to be more formal, friendly, professional, etc. * **Explain as an Expert** – Rephrase the content with more expertise. * **Add Emoji** – Add suitable emoji to make the text more expressive. * In addition to these quick actions, users can enter a **custom prompt** manually — for example: "Write this as a product announcement" or "Make it sound like a luxury brand". * The assistant uses AI models to generate responses that align with marketing communication standards: clear, compelling, and relevant. * Multiple suggestions may be offered, and users can insert or regenerate with a click. * Inserted results can still be manually edited as needed. **How to activate** * Go to **Plugins → Artificial Intelligence**. * Enable the toggle for **"AI assistant for the Text block"**. * Provide your OpenAI API key. **Requirements** * OpenAI API Key * Access to the `gpt-4o` model * Organization-level key (not personal) ## AI Assistant for Smart Modules This tool is designed to help users improve the performance and clarity of structured content within Smart modules. It leverages AI to optimize headlines, descriptions, and text fields collectively. **Where it appears** ::: image-wrap ![](/img/plugin/new/image201.webp){width=1999 height=703} ::: * When a user selects an existing or newly created **Smart Module** and navigates to the **Data** tab in the settings sidebar, а **magic wand icon** appears above the list of content variables (e.g., Title, Description, Text). * Clicking the icon triggers the assistant, which analyzes the overall content across all fields and suggests optimized versions for higher engagement. **How it works** * The assistant reviews the values of all editable Smart module fields (e.g., title, description, CTA text). * It generates a cohesive and high-converting alternative, enhancing clarity, tone, and persuasiveness. * The suggested changes are applied automatically. * If the result is not satisfactory, the user can: * Regenerate suggestions using the assistant icon again. * Revert to the original version via the editor's version history controls. * This ensures full flexibility while encouraging faster content iteration. **How to activate:** * Go to **Plugins → Artificial Intelligence**. * Enable the toggle for **"AI assistant for Smart modules"**. * Provide your OpenAI API key. **Requirements:** * OpenAI API Key * Access to the `gpt-4o` model * Organization-level key (not personal) ## AI-generated Image Description This tool helps users automatically generate alternative (alt) text for images, improving accessibility and email client rendering. **Where it appears** ::: image-wrap ![](/img/plugin/new/image202.webp){width=400 height=423} ::: In the settings panel of the **Image**, **Video**, **Banner**, or **Menu** blocks, а **magic wand icon** appears next to the **Alt text** input field. **How it works** * When the user clicks the icon, the AI analyzes the content of the image. * It then generates a short, relevant alt text description and automatically applies it to the input field. * If the user clicks the icon again, a new variation will be generated. * If the user is unsatisfied with the AI-generated result, they can always revert the change via the editor’s built-in version history. **How to activate** * Go to **Plugins → Artificial Intelligence**. * Enable the toggle for **"AI-generated Image Description"**. * Provide your OpenAI API key. **Requirements** * OpenAI API Key * Access to the `gpt-4o` model * Organization-level key (not personal) ## AI Image Generation Models This setting allows plugin owners to choose which AI model should be used to generate images from text prompts within the Stripo Plugin. AI-generated images can be used to enrich email designs, especially for visual storytelling, promotions, and banners. **Where it appears** ::: image-wrap ![](/img/plugin/new/image199.webp){width=450 height=455} ::: * When a user opens the **Image Gallery**, a new tab appears labeled **AI Image**. * Clicking this tab opens the AI image generation interface with the following elements: * A text input field for entering a detailed image prompt. * A model selector (e.g., Nano Banana 2 🍌, Stability, GPT-Image-1.5, GPT-Image-2). * An aspect ratio selector (e.g., 1:1, 4:3, 3:4, 16:9, 9:16). * A **Generate** button. * A list of example prompts to help users understand how to write effective image descriptions. **How it works** * The user enters a text prompt describing the desired image. * The selected AI model generates a corresponding image. * The generated image replaces the example prompt area and is previewed in the interface. * Users can: * Regenerate the image by adjusting the prompt. * Click to insert the image into the relevant email block. * All generated images are saved in the plugin’s image gallery for future reuse. **Model selection and requirements:** * Plugin owners choose which AI image models are enabled in the **Plugins → Artificial Intelligence** section. * Available models: * **Nano Banana 2 🍌** – requires Project ID and API Key. * **Stability** – requires Bearer Token. * **GPT-Image-1.5, GPT-Image-2** – uses the same OpenAI API key as text-related features. **How to activate** * Go to **Plugins → Artificial Intelligence**. * Toggle on the desired image generation models. * Enter required API credentials for each selected provider. **Requirements** * Valid API key/token for each selected model. * For GPT-Image-1.5 and GPT-Image-2, OpenAI API Key with `gpt-4o` access (same key as for text) and access to image-generation models. ### How to Get API Keys for AI Models {#how-to-get-api-keys-for-ai-models} Below is a quick guide to generating and managing credentials for each supported AI provider. ### OpenAI (GPT-4o, GPT-Image-1.5, GPT-Image-2) To use OpenAI features for both text and image generation, you need an API key with access to: * `gpt-4o` (for text features) * GPT-Image-1.5, GPT-Image-2 (for image generation) **Steps to generate the key:** 1. Go to [OpenAI API Keys](https://platform.openai.com/account/api-keys). 2. Sign in and click **"Create new secret key"**. 3. Ensure your account is on a paid plan (minimum $5 credit top-up). 4. Confirm the key has access to `gpt-4o` and `gpt-image-1.5`/`gpt-image-2` models. ### Nano Banana 2 🍌 Nano Banana 2 🍌 models require a project-specific API key. **Steps to generate the key:** 1. Visit Google AI Studio. 2. Sign in with your Google account. 3. Click **"Get API key"** and follow the guided steps. ### Stability AI (Stable Diffusion) Stability AI provides high-quality image generation via their Stable Diffusion models. **Steps to generate the token:** 1. Sign up or log in at [Stability AI](https://platform.stability.ai/). 2. Go to your account settings and navigate to **API Keys**. 3. Click **"Create API Key"**. Once you’ve obtained the appropriate keys, paste them into the corresponding fields in **Plugins → Artificial Intelligence** to enable each service. ## Interactive Widgets Interactive Widgets bring no-code gamification and surveys to emails created in the editor: games, quizzes, feedback forms, surveys, etc., and clickable layouts. Widgets are created and edited conversationally — the user describes the desired widget in the AI chat, and the editor generates it along with all the required versions: an AMP-powered interactive version, an interactive HTML version, and a static fallback, so the widget degrades gracefully in email clients that do not support interactivity. ::: image-wrap ![](/img/plugin/new/image246.webp){width=1999 height=455} ::: ### How to Activate The feature is managed from your **Plugin Settings** in the Stripo account, under the **Interactive Widgets** section of the left-hand menu: 1. Switch on the **No-code gamification and surveys (widgets)** toggle. 2. Enter your **OpenAI API Key** — widget generation uses your own key. You can find your Secret API key in your OpenAI account settings. ::: image-wrap ![](/img/plugin/new/image247.webp){width=1999 height=455} ::: Once enabled, widgets become available to all editor instances connected to your Plugin. :::success If you need to hide specific widgets from your users or change the order in which widgets are listed, contact the Stripo support team. ::: ## Using Your Own AI Assistant (via Extension) If you want to replace Stripo’s built-in AI Assistant with your own solution, you can do so by implementing a custom **Extension**. **This allows you to:** * Fully override the default AI behavior; * Launch your own UI (modal, panel, etc.) when the AI button is clicked; * Connect any external AI model or service (e.g., OpenAI, Claude, internal LLM); * Return generated text back to the editor for insertion. **How it works:** * The AI button will open your external dialog instead of the default Stripo modal. * After user interaction, your extension must return the updated content via a callback. * Stripo will then update the corresponding block or input field with the new content. **This integration is useful if you:** * Want to use your own AI infrastructure; * Need to enforce specific tone of voice or content restrictions; * Already use an AI assistant in other parts of your app and want to keep the experience consistent. **See the implementation example:** * [External AI Assistant Extension](/extensions/tutorials/examples/integrations/external-ai-assistant) --- --- url: https://plugin.stripo.email/editor-configuration/accessibility-checker.md --- # Accessibility Checker Supported since plugin version **2.59.0** The **Accessibility Checker** lets users evaluate email templates for accessibility issues directly in the editor. It helps ensure that emails are readable and usable for people with visual impairments and other accessibility needs. The feature provides a dedicated **Accessibility Testing Mode** that lets users inspect the email under different accessibility scenarios and identify potential issues before sending the campaign. With Accessibility Testing Mode, users can: * analyze accessibility issues in the email layout; * simulate different types of color vision deficiencies; * hide images to verify how the email behaves without them; * preview the email in mobile or desktop layouts during testing; * inspect the email HTML through the code editor if necessary. You can learn more about the accessibility feature in our [article](https://stripo.email/blog/email-accessibility-checker-find-issues-in-your-emails-and-fix-them-faster/). ## How It Works When the Accessibility Checker is activated, the editor switches to **Accessibility Testing Mode**. ::: image-wrap ![](/img/plugin/new/image241.gif){width=2880 height=1382} ::: In this mode: * the email is displayed in a testing environment designed to highlight accessibility issues; * users can simulate different visual impairments using color vision filters; * users can temporarily hide images to verify whether the email content remains understandable; * the preview can be switched between **mobile** and **desktop** layouts. Accessibility Testing Mode does not modify the email content automatically. Instead, it provides visual tools that help users identify potential accessibility problems and fix them manually. Please be advised, the controls used to manage this mode (such as switching previews, hiding images, or applying color vision filters) are **not part of the plugin UI itself**. These controls can be implemented in the host application interface (for example, in a custom header panel). To support such integrations, the plugin provides additional [API methods](/plugin-invocations/javascript-api#accessibility-api) that allow plugin owners to connect their own UI controls to the Accessibility Testing functionality. ## How to Activate Accessibility Testing Mode can be opened either through the editor interface or programmatically using the JavaScript API. To start accessibility testing via API: ```js window.StripoEditorApi.accessibilityApi.openAccessibilityTestingMode(); ``` To exit the testing mode and return to the regular editing mode: ```js window.StripoEditorApi.accessibilityApi.closeAccessibilityTestingMode(); ``` The Accessibility API provides additional capabilities for controlling the testing environment, such as simulating color vision deficiencies, hiding images, or switching between mobile and desktop previews. For the complete list of available methods and usage examples, see the [Controlling Accessibility Testing via API](#controlling-accessibility-testing-via-api) section. ## Permissions Accessibility Checker introduces a new permission key: `accessibilityTesting` This permission controls whether users can access Accessibility Testing Mode and whether they are allowed to modify the email while testing accessibility. This permission controls whether users can access Accessibility Testing Mode and whether they are allowed to modify the email while testing accessibility. | Permission | Description | | ----- | ----- | | `read` | Allows the user to open Accessibility Testing Mode. | | `write` | Allows editing the email while Accessibility Testing Mode is active. | Example configuration: ```js { "accessibilityTesting": { "read": true, "write": true } } ``` If the permissions checker is not configured, both values default to **true**. See the [Permissions and Access Management](https://plugin.stripo.email/editor-configuration/permissions-and-access-management) section for details about configuring permissions. ## Controlling Accessibility Testing via API The Accessibility Checker can be controlled programmatically using the **Accessibility API**. This API allows your application to: * open or close Accessibility Testing Mode; * simulate color vision deficiencies; * hide or show images in the preview; * switch between mobile and desktop previews; * open the code editor while testing accessibility. All methods are available through: `window.StripoEditorApi.accessibilityApi` For the full list of available methods, see the [**Accessibility API**](/plugin-invocations/javascript-api#accessibility-api) section --- --- url: https://plugin.stripo.email/editor-configuration/commenting.md --- # Commenting The Commenting feature allows users to discuss email designs directly within the editor, highlight specific elements that need attention, and collaborate in real time with other team members. Comments can include text, replies, mentions, and resolution tracking. When enabled in your Plugin settings, it becomes accessible in the editor interface, adding a **Comments** tab to the settings panel of each email message or template. ::: image-wrap ![](/img/plugin/new/image230.webp){width=1424 height=355} ::: ## How It Works When the **Comments** tab is available in the editor, users can start collaborating on email design directly inside the template. To add a comment, a user opens the **Comments** tab and clicks the **Add comment** icon (or uses the shortcut shown in the tooltip). ::: image-wrap ![](/img/plugin/new/image231.webp){width=1234 height=421} ::: Once activated, the cursor changes to a crosshair — this means the user can click anywhere within the email area to place a **pin**. A comment box appears next to that pin, where the user can enter feedback or suggestions. Each pin is visually tied to a specific part of the email (for example, an image, button, or text block) so everyone knows exactly which element the comment refers to. Inside the **Comments** panel, users can: * write new comments or reply to existing ones, * mention teammates using the **@** symbol, * leave emoji reactions, * edit or delete their own comments, * resolve threads once they’re no longer relevant. ::: image-wrap ![](/img/plugin/new/image232.webp){width=1097 height=323} ::: The system automatically tracks which comments each user has viewed. When a user opens an email and hovers the cursor over the comments they’ve already seen they are automatically marked as **read**, while new ones appear as **unread**, helping everyone stay on top of what’s new. Every comment includes information about: * the author (user ID and name), * the element it’s attached to, * and the text content of the discussion. When the email is reopened, the editor restores all existing comments and automatically synchronizes the authors’ names and avatars using the integration callbacks — so users always see up-to-date information about who left each comment. ## How to Activate The Commenting feature is managed from your **Plugin Settings** in the Stripo account. You can find it under the **Commenting** section of the left-hand menu — this is where you control whether commenting is available for users of your Plugin. ::: image-wrap ![](/img/plugin/new/image233.webp){width=1178 height=620} ::: When the toggle is switched **on**, commenting becomes active for all editor instances connected to your Plugin. From that moment, a new **Comments** tab will appear inside the editor, allowing users to review and discuss email designs directly within the interface. No additional parameters are required in your initialization code — activation is handled automatically by Stripo’s backend. Whether users can view or interact with comments depends on the permissions you define for them in your application. The editor supports two types of comment-related permissions: * **manageOwnComments** * `read`: Allows the user to open the Comments tab and view comment threads created by them. This permission controls the visibility of the tab: it is shown if either `manageOwnComments.read` or `manageAllComments.read` is enabled, and hidden only when both are disabled. * `write`: Allows the user to create new comments and reply to existing threads. Authors can edit, delete, resolve, reopen, and move their own comments when they have read access. * **manageAllComments** * `read` Allows the user to open the Comments tab and view comments created by all users. When this permission is disabled but `manageOwnComments.read` is enabled, the user sees only their own comment threads. * `write` Allows the user to edit, delete, resolve, reopen, and move comments created by other users, providing moderation capabilities. If you haven't configured any comment-related permissions, the editor assumes full access by default. All users will be able to view, create, and manage comments without restrictions. This ensures that the feature works out of the box for teams that have yet to implement custom access control. :::success Refer to the [Permissions and Access Management](/editor-configuration/permissions-and-access-management) section for implementation details. ::: ## Fetching User Information {#fetching-user-information} Every time a user leaves a comment, the editor automatically stores their basic profile information (such as name, ID, and avatar) inside the email model. This ensures that even if the comment author later becomes unavailable, their name and picture still appear correctly in the discussion. However, there are cases when user information changes — for example, a teammate updates their display name or profile picture. To make sure these updates are reflected in previously created comments, the editor can dynamically refresh user details when the email is opened. For that, you can define the `onUsersInfoRequest` function during Plugin initialization. When the editor opens a template containing comments, it automatically calls this function, passing the list of user IDs found in the comment data. Your backend should respond with the latest user information so that the editor can update all displayed comments accordingly. **Function Parameters** | Parameter | Type | Description | | :---- | :---- | :---- | | `userIds` | Array | List of user IDs to fetch information for. | | `successCallback` | Function | Callback to invoke with the user data array. | | `errorCallback` | Function | Callback to invoke if an error occurs. | **User Object Structure**\ Each user object in the response should contain: | Property | Type | Required | Description | | :---- | :---- | :---- | :---- | | `userId` | String | Yes | Unique identifier of the user. | | `userName` | String | Yes | Display name of the user. | | `avatar` | String | No | URL to the user's avatar image. | | `email` | String | No | Email address of the user. | **Usage Example** ```js onUsersInfoRequest: function (userIds, successCallback, errorCallback) { fetch(`/api/users/info`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids: userIds }) }).then(res => res.json()) .then(data => successCallback(data.users)) .catch(err => errorCallback(err)); } ``` This callback is called automatically and should return an array of user objects. If your application supports avatars, display names, or user emails, include those fields in the response so the editor can show the most accurate and up-to-date information next to each comment. ## Mentioning Users To make communication even more natural, users can mention teammates directly inside comments — just like in chat applications. When typing a comment, the user can enter the **@** symbol and start typing a name. A dropdown will appear with a list of users matching the text, allowing the commenter to tag the right person and automatically notify them about the discussion. ::: image-wrap ![](/img/plugin/new/image234.webp){width=1286 height=758} ::: To make this feature work, your Plugin must know how to find and return possible matches from your user base. That’s why the editor can call a special function — `onUsersInfoSearchRequest` — which you define during initialization. If this function is not defined during initialization, the mention feature is completely disabled: typing the **@** symbol in a comment does not open the user search popup, and no search requests are made. When a user starts typing a name after "**@**", the editor automatically calls the `onUsersInfoSearchRequest` function, providing search parameters and expecting a list of matching users in response. **Function Parameters** | Property | Type | Description | | :---- | :---- | :---- | | `params` | Object | Search parameters (see structure below). | | `successCallback` | Function | Callback to invoke with the search results. | | `errorCallback` | Function | Callback to invoke if an error occurs. | **Search Parameters Object** | Property | Type | Description | | :---- | :---- | :---- | | `offset` | Number | Starting position for pagination. | | `size` | Number | Number of results to return. | | `asc` | Boolean | Sort order (ascending if true). | | `sortBy` | String | Field to sort by (optional). | | `filter` | String | Search query text (optional). | **Response Object Structure**\ The callback should receive an object containing: | Property | Type | Description | | :---- | :---- | :---- | | `items` | Array | Array of user objects matching the search criteria. | | `total` | Number | Total count of matching users. | Each user object in the `items` array should follow the same structure as described in the [Fetching User Information](#fetching-user-information) section. **Usage Example** ```js onUsersInfoSearchRequest: function (params, successCallback, errorCallback) { fetch(`/api/users/search?query=${encodeURIComponent(params.filter || '')}`) .then(res => res.json()) .then(data => successCallback(data)) .catch(err => errorCallback(err)); } ``` Your API should return a JSON object that includes the array of matching users and the total count. Each user object can contain a name, avatar, and ID — these will be displayed in the mention dropdown to help collaborators quickly tag the right person. ## Notifications API In collaborative environments, it’s often important to stay updated on what’s happening with email feedback — especially when several people are working on the same design. The **Notifications API** helps with that by letting your system receive automatic alerts whenever something happens in the comments section of the editor. When this option is activated in your Plugin settings, Stripo will start sending webhook requests to your specified endpoint each time: * a new comment is added, * someone replies to an existing discussion, * a teammate is mentioned in a comment, or * a comment is resolved. This allows your system to instantly notify users (for example, via email or internal messaging), update task trackers, or trigger any other workflow that depends on feedback events. If the Notifications API is not activated, the editor will work normally — comments will still function inside the UI, but no external notifications will be sent. **How to Enable** The Notifications API is configured directly in your Stripo **Plugin Settings**, under the **Commenting** section. Here’s what you’ll need to provide: 1. **Enable the toggle** to activate webhook notifications. 2. Enter your **endpoint URL** — the HTTPS address where notifications should be sent. 3. Specify **login** and **password** for basic authentication, so only Stripo’s backend can access the endpoint. ::: image-wrap ![](/img/plugin/new/image235.webp){width=1172 height=555} ::: Once this is set up, our backend will automatically start delivering JSON payloads with comment activity to your endpoint in real time. For details on request structure, field descriptions, and expected response format, refer to the [**Server Webhooks → Comments Notifications**](/editor-configuration/server-webhooks#comments-notifications) section, which includes the full OpenAPI specification for comment-related events. **Delivery and Reliability** * Each webhook request must be acknowledged with an HTTP **2xx** response code. * If no acknowledgment is received, Stripo will retry sending the request up to **5 times within 10 minutes**. * After the final attempt, undelivered notifications will no longer be resent. * Only secure **HTTPS** endpoints are supported. --- --- url: https://plugin.stripo.email/editor-configuration/lock-element.md --- # Lock Element The Lock Element feature allows users with the appropriate permission to lock individual elements of an email — stripes, structures, and containers — so that other collaborators cannot modify them. Locking protects brand-critical areas (headers, footers, legal blocks) from accidental or unauthorized changes, while the rest of the template remains fully editable. ::: image-wrap ![](/img/plugin/new/image244.webp){width=1999 height=815} ::: ## How It Works Users who are allowed to manage locks see the **Lock Element** control in the settings panel of a stripe, structure, or container. The control consists of the main lock toggle and two options that define what exactly is protected: * **Prevent content editing** — the content of the element cannot be changed: inline text editing is disabled, and blocks inside the locked element cannot be added, removed, or rearranged. * **Prevent style editing** — the appearance settings of the element (colors, spacing, borders, and other style controls) are disabled. Locked elements are marked on the canvas with a lock icon and a highlighted frame, so every collaborator can immediately see which parts of the template are protected. ::: image-wrap ![](/img/plugin/new/image245.webp){width=1999 height=926} ::: For users who are **not** allowed to manage locks: * the Lock Element control is not shown in the settings panel of stripes, structures, and containers. The only exception: when such a user selects an element that is already locked, the control is displayed so the user can see that the element is protected — but its state cannot be changed; * the settings controls of the locked element and its child elements are disabled, according to the selected lock options; * inline editing of text inside a content-locked element is blocked. Lock protection is also enforced on the server side: changes that modify a locked element are rejected for users without the corresponding permission. This keeps templates consistent even during real-time co-editing, when several users with different permission sets work on the same email. The lock state travels together with the element. If a locked element is saved as a module or copied to another email, its lock configuration is preserved, and modules created from locked elements display a lock icon in the modules panel. ## How to Activate Lock Element is disabled in the Plugin by default and is controlled on two levels — both are required for lock management to be available: ### 1. Feature availability — the `elementLockEnabled` initialization parameter. Pass the following parameter in the editor initialization config to make the feature available in your integration: ```js elementLockEnabled: true ``` Without this parameter, the Lock Element control is not shown to any user, even if the `elementsLock.write` permission is granted. ::: image-wrap ![](/img/plugin/new/image248.webp){width=1999 height=815} ::: ### 2. Per-user access — the `elementsLock` permission, which your backend returns via the User Permissions API: * `elementsLock.write: true` — the user can lock and unlock elements, and can edit locked elements. * `elementsLock.write: false` — the user cannot change the lock state of elements and cannot modify the locked content or styles of locked elements. The Lock Element control is hidden from the settings panel and appears only when the user selects an element that is already locked. ```js { "elementsLock": { "read": true, "write": true } } ``` The `elementsLock.read` action is reserved for consistency with the permission model and currently has no effect on the editor behavior. :::info-clear Disabling the feature hides only the lock management control. If the template already contains locked elements, those elements remain protected according to their lock settings — this ensures that protection configured earlier does not silently disappear. ::: :::success Refer to the [Permissions and Access Management](/editor-configuration/permissions-and-access-management) section for implementation details. ::: --- --- url: https://plugin.stripo.email/editor-configuration/autosaving.md --- # Autosaving The new Stripo editor offers flexible settings for saving emails. This option can be activated or deactivated within the plugin configuration page in your Stripo account, on the Server Settings tab. When autosave is enabled, each action in the editor is instantly sent for processing, and changes are recorded in the “reference email” in Stripo’s database. This mechanism prevents users from losing important work during long sessions and is ideal for team collaboration, where multiple users can simultaneously edit the same email. With autosave disabled, changes accumulate in the editor and are saved only when the user explicitly commands it (by calling the API method `window.StripoEditorApi.actionsApi.save()` to save changes, see [Stripo plugin JS API](/plugin-invocations/javascript-api#actions-api)). This provides additional control over the publication process, which can be useful when working on critical projects. For convenience, you can use two parameters, `onSaveStarted` and `onSaveCompleted`, to indicate in your application when the saving process has started or completed. These functions can be defined during initialization (see the [Initialization Settings](initialization-settings) table). --- --- url: https://plugin.stripo.email/editor-configuration/server-webhooks.md --- # Server Webhooks ## User Permissions API To efficiently manage access to email template and its different parts in the editor, Stripo has implemented a webhook designed to externally retrieve [permissions](permissions-and-access-management) for a specific user to interact with the email template. To process requests from Stripo, you will need to implement an API according to this specification. ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: User Permissions API description: | This API specification describes the webhook endpoint that your server must implement to provide user permissions for the Stripo Email Editor. The Stripo Plugin enforces role-based access control by calling your endpoint to retrieve permissions for each user session. This allows you to define granular access controls for different parts of the editor (code editor, appearance settings, content editing, modules, version history, and comments management). **How It Works**: 1. Configure the User Permissions API endpoint in your Stripo Plugin settings 2. When a user opens the editor, Stripo calls your endpoint with user metadata 3. Your server responds with a JSON object specifying which actions are allowed 4. The editor enforces these permissions by enabling or disabling features **Authentication**: HTTP Basic Authentication is required. Configure credentials in the plugin settings: Plugin → Server Settings → User Permissions API. **Performance**: This endpoint is called during editor initialization, so response time should be optimized (recommended: < 500ms). version: 1.0.0 contact: name: Stripo Support url: https://stripo.email servers: - url: https://{YOUR_USER_PERMISSIONS_CHECKER_URL} description: Your user permissions webhook endpoint variables: YOUR_USER_PERMISSIONS_CHECKER_URL: default: '' paths: /: get: tags: - Methods summary: Get user permissions for email template operationId: getUserPermissionsForEmail description: | Retrieves the set of permissions granted to a specific user for a particular email template. The Stripo editor calls this endpoint during initialization with user metadata in the `ES-PLUGIN-UI-DATA` header. Your server should: 1. Parse the metadata to identify the user and email template 2. Check the user's role and permissions in your system 3. Return a JSON object specifying which editor features are accessible **Use Cases**: - Restrict content editing for reviewers (read-only access) - Allow text-only editing for copywriters - Grant full access to administrators - Control comment creation and moderation capabilities - Manage module library access **Performance Considerations**: This endpoint is called on every editor initialization, so responses should be fast (< 500ms recommended) and may be cached by your application. security: - basicAuth: [] parameters: - in: header name: ES-PLUGIN-UI-DATA required: true schema: type: string description: | User and email template metadata that was passed during editor initialization. This header contains the `metadata` object you provided in the `window.Stripo.init()` call. Typically includes email ID, and any custom context data. The value is URL-encoded JSON. Your server should decode and parse this to identify the user and determine their permissions. example: '{"emailId":"456","projectId":"789"}' - in: header name: Cookies required: true schema: type: string description: | Browser cookies from the user's session. Can be used for additional authentication or session validation if needed. example: 'sessionId=abc123;' responses: '200': description: | User permissions retrieved successfully. content: application/json: schema: $ref: '#/components/schemas/UserPermissions' examples: fullAccess: summary: Full access (administrator) description: All permissions granted for admin users value: codeEditor: read: true write: true appearance: read: true write: true content: read: true write: true textOnly: false modules: read: true write: true versionHistory: read: true write: true manageOwnComments: read: true write: true manageAllComments: read: true write: true accessibilityTesting: read: true write: true readOnly: summary: Read-only access (reviewer) description: User can view everything but cannot make changes value: codeEditor: read: true write: false appearance: read: true write: false content: read: true write: false textOnly: false modules: read: true write: false versionHistory: read: true write: false manageOwnComments: read: true write: false manageAllComments: read: false write: false accessibilityTesting: read: true write: false textOnlyEditor: summary: Text-only editing (copywriter) description: User can only edit text content, not layout or design value: codeEditor: read: false write: false appearance: read: true write: false content: read: true write: false textOnly: true modules: read: true write: false versionHistory: read: true write: false manageOwnComments: read: true write: true manageAllComments: read: false write: false accessibilityTesting: read: false write: false contentEditor: summary: Content editor with comment moderation description: Can edit content and manage all comments, but not code or appearance value: codeEditor: read: false write: false appearance: read: true write: false content: read: true write: true textOnly: false modules: read: true write: true versionHistory: read: true write: false manageOwnComments: read: true write: true manageAllComments: read: true write: true accessibilityTesting: read: true write: true components: securitySchemes: basicAuth: type: http scheme: basic description: | HTTP Basic Authentication using username and password configured in the Stripo Plugin settings under: Plugin → Server Settings → User Permissions API. The editor will send these credentials with every request to your endpoint. schemas: UserPermissions: type: object description: | Complete set of user permissions for the email editor. Each permission group controls access to specific editor features and capabilities. **Permission Groups**: - `codeEditor`: HTML code editor access - `appearance`: Design and styling controls (fonts, colors, themes) - `content`: Template content editing (blocks, text, images, layout) - `modules`: Custom module library access (create, edit, delete saved modules) - `versionHistory`: Version control features (view history, restore versions) - `manageOwnComments`: Comment creation and participation - `accessibilityTesting`: Accessibility Testing Mode access and editing behavior - `manageAllComments`: Comment moderation and management **Permission Logic**: - `read: false` hides the feature from the UI entirely - `read: true, write: false` shows the feature but in read-only mode - `read: true, write: true` grants full access to the feature properties: codeEditor: allOf: - $ref: '#/components/schemas/UserPermissionValue' - description: | Controls access to the HTML code editor. **read: true** - User can open and view the HTML source code of the email template. Useful for developers who need to inspect the markup. **write: true** - User can edit and save changes to the HTML code. Requires technical knowledge. Should be restricted to developers and administrators. **Use Case**: Grant read-only access to designers who need to inspect HTML but shouldn't modify it directly. appearance: allOf: - $ref: '#/components/schemas/UserPermissionValue' - description: | Controls access to appearance settings (fonts, colors, styles, theme customization). **read: true** - User can view current appearance settings and design system values. **write: true** - User can modify appearance settings, affecting the overall look and feel of the email template (global colors, fonts, spacing). **Use Case**: Restrict appearance changes to brand managers and designers while allowing editors to view the current settings. content: allOf: - $ref: '#/components/schemas/UserContentPermissionValue' - description: | Controls access to template content editing capabilities. **read: true** - User can view the email template content. **write: true** - User can add/remove blocks, modify layout, change images, and fully edit the template structure. **textOnly: true** - Special mode that allows only text editing without structural changes. Ideal for copywriters and translators. When enabled, `write` should be `false`. **Use Cases**: - Copywriters: read: true, write: false, textOnly: true - Reviewers: read: true, write: false, textOnly: false - Content editors: read: true, write: true, textOnly: false modules: allOf: - $ref: '#/components/schemas/UserPermissionValue' - description: | Controls access to the custom module library (saved template blocks). **read: true** - User can browse the module library and insert saved modules into email templates. **write: true** - User can create new modules, update existing ones, and delete modules from the library. **Use Case**: All users can typically insert modules (read: true), but only designers and administrators should create/modify modules (write: true). versionHistory: allOf: - $ref: '#/components/schemas/UserPermissionValue' - description: | Controls access to email template version history and restoration features. **read: true** - User can view the version history, see who made changes and when, and preview previous versions. **write: true** - User can restore previous versions of the email template, effectively reverting changes. **Use Case**: Allow all editors to view history (read: true) but restrict version restoration to administrators (write: true) to prevent accidental data loss. manageOwnComments: allOf: - $ref: '#/components/schemas/UserPermissionValue' - description: | Controls the user's ability to participate in commenting and collaboration. **read: true** - User can open the Comments tab and view all existing comments on the email template. This allows them to see feedback and discussions. **write: true** - User can create new comments, reply to existing comment threads, and participate in discussions. They can edit and delete their own comments. **Use Case**: - Collaborators: read: true, write: true (can participate in discussions) - Reviewers: read: true, write: false (can see feedback but not comment) - External viewers: read: false, write: false (no access to comments) **Note**: Users with write access can only manage their own comments. To allow editing/deleting other users' comments, use `manageAllComments`. manageAllComments: allOf: - $ref: '#/components/schemas/UserPermissionValue' - description: | Controls comment moderation capabilities across all users. **read: true** - Currently not utilized by the system. Reserved for future use. **write: true** - User can edit and delete comments created by any user, providing full moderation capabilities. This is in addition to the permissions granted by `manageOwnComments`. **Use Case**: - Administrators/Moderators: write: true (can moderate all comments) - Regular users: write: false (can only manage their own comments via manageOwnComments) **Important**: This permission grants elevated privileges and should only be given to trusted administrators or moderators who need to manage inappropriate content or maintain discussion quality. accessibilityTesting: allOf: - $ref: '#/components/schemas/UserPermissionValue' - description: | Standard permission value structure with **read** and **write** access flags. Controls access to the **Accessibility Testing Mode** in the editor. Note: **write: true is meaningless if read: false** UserPermissionValue: type: object description: | Standard permission value structure with read and write access flags. Used for most permission groups in the editor. properties: read: type: boolean description: | Controls visibility and read access to the feature. **true**: User can view and access the feature (possibly in read-only mode) **false**: Feature is hidden from the user interface entirely example: true write: type: boolean description: | Controls modification permissions for the feature. **true**: User can make changes and save modifications **false**: Feature is read-only (requires read: true) Note: write: true is meaningless if read: false example: false UserContentPermissionValue: type: object description: | Extended permission value structure for content editing with an additional text-only editing mode. This allows fine-grained control over content editing capabilities. properties: read: type: boolean description: | Controls visibility and read access to the template content. **true**: User can view the email template content **false**: User cannot access the template content at all example: true write: type: boolean description: | Controls full content modification permissions. **true**: User can add/remove blocks, change layout, edit text, modify images, and make any structural changes to the template **false**: User cannot make structural changes (but may still edit text if textOnly: true) example: false textOnly: type: boolean description: | Enables text-only editing mode for copywriters and translators. **true**: User can edit text content within existing blocks but cannot modify layout, add/remove blocks, or change design elements. Perfect for copywriters and translators who should focus only on content. **false**: Standard editing mode (controlled by write permission) **Important**: When textOnly: true, the write permission should be false. The textOnly flag provides a special editing mode separate from full write access. example: false tags: - name: Methods description: User permissions API endpoints ``` ## Email Resources Permissions API The Email Resources Permissions API ensures that only authorized users have the rights to edit email resources such as modules and images in your application. This feature helps prevent unauthorized access and ensures that the Stripo Plugin performs server-side operations only with your permission. To enable this feature, you need to implement the following backend endpoint on your server. ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: Email Resources Permissions API description: The Email Resources Permissions API ensures that only authorized users have the rights to edit email resources such as modules and images in your application. This feature helps prevent unauthorized access and ensures that the Stripo Plugin performs server-side operations only with your permission. To enable this feature, you need to implement the following backend endpoint on your server. version: 1.0.0 servers: - url: https://{YOUR_RESOURCE_PERMISSIONS_CHECKER_URL} variables: YOUR_RESOURCE_PERMISSIONS_CHECKER_URL: default: '' paths: /: post: tags: - Methods summary: Check and grant permissions description: Verifies and assigns necessary permissions to a user for accessing specific features or content within the Stripo platform. The editor sends a request to the customer's endpoint with the information from the metadata and expects to receive a set of granted permissions in response. This method ensures that users have the appropriate access levels required to work inside the editor, enhancing security and effective role-based management. security: - basicAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ResourcePermissionsRequest' responses: '200': description: Resources permissions content: application/json: schema: $ref: '#/components/schemas/ResourcePermissionsResponse' components: securitySchemes: basicAuth: type: http scheme: basic schemas: ResourcePermissionsRequest: type: object properties: pluginId: type: string description: ID of the plugin requesting permissions example: YOUR_PLUGIN_ID uiData: type: object description: The value of 'metadata' field from editor initialization parameters additionalProperties: type: string requestPermissions: type: array description: Array of permissions that plugin requests items: $ref: '#/components/schemas/ResourcePermission' required: - pluginId - uiData - requestPermissions ResourcePermission: type: object properties: type: type: string description: Operation subject. Supported values - BLOCKS, DOCS example: BLOCKS action: type: string description: Operation type. Supported values - READ, MODIFY example: READ key: type: string description: >- Key identifier that was configured in plugin settings with filled values example: pluginId_YOUR_PLUGIN_ID_emailId_123_id_456 keyTemplate: type: string description: Key identifier that was configured in plugin settings example: emailId_${emailId}_id_${someAnotherIdentifier} ResourcePermissionsResponse: type: object properties: grantPermissions: type: array items: $ref: '#/components/schemas/ResourcePermission' ``` ## Email Change Notification API The Email Change Notification API allows you to receive information about the time and author of each autosave for security and atomic integrity purposes during simultaneous editing. This webhook needs to be specified in the plugin settings to function correctly. To ensure that the webhook functions correctly, you need to specify an endpoint in the plugin settings that meets the following specifications. ### OpenAPI Specification ```yaml openapi: 3.0.1 info: title: Email Changes Notifications API description: | The Email Change Notification API allows you to receive information about the time and author of each autosave for security and atomic integrity purposes during simultaneous editing. This webhook needs to be specified in the plugin settings to function correctly. To ensure that the webhook functions correctly, you need to specify an endpoint in the plugin settings that meets the following specifications. version: 1.0.0 servers: - url: https://{YOUR_EMAIL_CHANGE_NOTIFICATION_URL} variables: YOUR_EMAIL_CHANGE_NOTIFICATION_URL: default: '' paths: /: post: tags: - Methods summary: Notification on email changes description: Provides details about the time and author of each autosave, ensuring security and atomic integrity during simultaneous editing. operationId: handleEmailChanged security: - basicAuth: [] requestBody: content: application/json: schema: $ref: '#/components/schemas/SaveRequest' required: true responses: '200': description: Successful response content: application/json: schema: type: object nullable: true components: securitySchemes: basicAuth: type: http scheme: basic schemas: SaveRequest: type: object required: - emailId - userId - dateTime properties: emailId: type: string userId: type: string dateTime: type: integer format: int64 ``` ## Comments Notifications This API delivers real-time notifications about all comment-related activities in your email templates — such as new comments, replies, mentions, and resolutions. It allows your backend to stay synchronized with collaboration events happening inside the editor. You can enable and configure this integration in your **Plugin Settings → Commenting → Notifications API** section. For an overview of how the feature works, see the [Commenting documentation](/editor-configuration/commenting). ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: Comments Notification API description: | This API specification describes the webhook endpoint that plugin servers must implement to receive notification events from the Stripo Email Editor. The editor will POST notification events to the configured plugin URL when specific actions occur (e.g., comments created, replied, resolved, or viewed). **Authentication**: HTTP Basic Authentication is required. The editor will send credentials configured in the plugin settings. **Important**: All event type and subtype values use UPPERCASE with underscores. version: 1.0.0 contact: name: Stripo Support url: https://stripo.email servers: - url: https://{YOUR_PLUGIN_NOTIFICATION_URL} description: Plugin notification webhook endpoint (configure in plugin settings) variables: YOUR_PLUGIN_NOTIFICATION_URL: default: '' security: - basicAuth: [] paths: /notifications: post: summary: Receive plugin notification events description: | Endpoint to receive notification events from the Stripo Email Editor. Your plugin server must implement this endpoint and respond with either: - 200 OK (with optional response body) - 204 No Content (no response body) Any other status code will be treated as an error. operationId: receiveNotificationEvent security: - basicAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PluginNotificationEventRequest' examples: commentCreated: summary: Comment Created Event value: createdOn: 1699564800000 type: "EDITOR_COMMENTS" subtype: "COMMENT_CREATED" pluginId: "my-plugin-123" details: emailId: "email-456" commentId: "comment-789" commentAuthorId: "user-001" commentText: "This looks great!" taggedUserIds: ["user-002", "user-003"] commentReplied: summary: Comment Replied Event value: createdOn: 1699564900000 type: "EDITOR_COMMENTS" subtype: "COMMENT_REPLIED" pluginId: "my-plugin-123" details: emailId: "email-456" commentId: "comment-790" threadCommentId: "comment-789" replyAuthorId: "user-002" commentText: "Thanks for the feedback!" initialCommentText: "This looks great!" repliesCount: 1 taggedUserIds: ["user-001"] threadUserIds: ["user-001", "user-002"] initialCommentAuthorId: "user-001" initialCommentCreationDate: 1699564800000 commentResolved: summary: Comment Resolved Event value: createdOn: 1699565000000 type: "EDITOR_COMMENTS" subtype: "COMMENT_RESOLVED" pluginId: "my-plugin-123" details: emailId: "email-456" commentId: "comment-789" resolveUserId: "user-001" threadCommentId: "comment-789" commentText: "This looks great!" threadUserIds: ["user-001", "user-002"] commentAuthorId: "user-001" commentViewed: summary: Comment Viewed Event value: createdOn: 1699565100000 type: "EDITOR_COMMENTS" subtype: "COMMENT_VIEWED" pluginId: "my-plugin-123" details: emailId: "email-456" commentId: "comment-789" threadCommentId: "comment-789" viewerId: "user-003" responses: '200': description: Event received and processed successfully content: application/json: schema: type: object properties: message: type: string example: "Event processed successfully" example: message: "Event processed successfully" '204': description: Event received and processed successfully (no content) '400': description: Bad request - invalid event format or missing required fields content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: "Invalid request format" message: "Missing required field: pluginId" '401': description: Unauthorized - invalid or missing credentials content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: "Unauthorized" message: "Invalid credentials" '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: "Internal Server Error" message: "Failed to process event" components: securitySchemes: basicAuth: type: http scheme: basic description: | HTTP Basic Authentication using username and password configured in the plugin notification settings. schemas: PluginNotificationEventRequest: type: object required: - createdOn - type - subtype - pluginId - details properties: createdOn: type: integer format: int64 description: Unix timestamp in milliseconds when the event was created example: 1699564800000 type: type: string description: High-level event category (always "EDITOR_COMMENTS" for comment events) example: "EDITOR_COMMENTS" enum: - EDITOR_COMMENTS subtype: type: string description: Specific event type within the category example: "COMMENT_CREATED" enum: - COMMENT_CREATED - COMMENT_REPLIED - COMMENT_RESOLVED - COMMENT_VIEWED pluginId: type: string description: Unique identifier of the plugin this event is for example: "my-plugin-123" details: oneOf: - $ref: '#/components/schemas/PluginCommentCreatedEventDetails' - $ref: '#/components/schemas/PluginCommentRepliedEventDetails' - $ref: '#/components/schemas/PluginCommentResolvedEventDetails' - $ref: '#/components/schemas/PluginCommentViewedEventDetails' description: Event-specific details. The structure depends on the type and subtype. discriminator: propertyName: subtype mapping: COMMENT_CREATED: '#/components/schemas/PluginCommentCreatedEventDetails' COMMENT_REPLIED: '#/components/schemas/PluginCommentRepliedEventDetails' COMMENT_RESOLVED: '#/components/schemas/PluginCommentResolvedEventDetails' COMMENT_VIEWED: '#/components/schemas/PluginCommentViewedEventDetails' PluginCommentCreatedEventDetails: type: object description: Details for a comment creation event additionalProperties: false required: - emailId - commentId - commentAuthorId - commentText properties: emailId: type: string description: Unique identifier of the email template example: "534449i" commentId: type: string description: Unique identifier of the created comment example: "516ee02e-4325-42c6-9c54-48e60a1beba5" commentAuthorId: type: string description: User ID of the comment author (string format) example: "19" commentText: type: string description: Text content of the comment example: "This looks great!" taggedUserIds: type: array description: List of user IDs tagged in the comment (optional, string array) items: type: string example: ["user-002", "user-003"] PluginCommentRepliedEventDetails: type: object description: Details for a comment reply event additionalProperties: false required: - emailId - commentId - threadCommentId - replyAuthorId - commentText - initialCommentText - repliesCount - initialCommentAuthorId - initialCommentCreationDate properties: emailId: type: string description: Unique identifier of the email template example: "534449i" commentId: type: string description: Unique identifier of the reply comment example: "516ee02e-4325-42c6-9c54-48e60a1beba5" threadCommentId: type: string description: Unique identifier of the parent comment thread example: "76836e77-2f3c-4b3e-9479-6e8330692325" replyAuthorId: type: string description: User ID of the reply author (string format) example: "19" commentText: type: string description: Text content of the reply example: "sss" initialCommentText: type: string description: Text content of the initial comment in the thread example: "ffff" repliesCount: type: integer format: int32 description: Total number of replies in the thread example: 0 taggedUserIds: type: array description: List of user IDs tagged in the reply (optional, string array) items: type: string example: ["user-001"] threadUserIds: type: array description: List of all user IDs participating in the thread (optional, string array) items: type: string example: ["user-001", "user-002"] initialCommentAuthorId: type: string description: User ID of the author who created the initial comment (string format) example: "19" initialCommentCreationDate: type: integer format: int64 description: Unix timestamp in milliseconds when the initial comment was created example: 1761137862000 PluginCommentResolvedEventDetails: type: object description: Details for a comment resolution event additionalProperties: false required: - emailId - commentId - resolveUserId - threadCommentId - commentText - commentAuthorId properties: emailId: type: string description: Unique identifier of the email template example: "534449i" commentId: type: string description: Unique identifier of the resolved comment example: "516ee02e-4325-42c6-9c54-48e60a1beba5" resolveUserId: type: string description: User ID of the person who resolved the comment (string format) example: "19" threadCommentId: type: string description: Unique identifier of the comment thread example: "76836e77-2f3c-4b3e-9479-6e8330692325" commentText: type: string description: Text content of the resolved comment example: "This looks great!" threadUserIds: type: array description: List of all user IDs participating in the thread (optional, string array) items: type: string example: ["user-001", "user-002"] commentAuthorId: type: string description: User ID of the original comment author (string format) example: "19" PluginCommentViewedEventDetails: type: object description: Details for a comment view event additionalProperties: false required: - emailId - commentId - threadCommentId - viewerId properties: emailId: type: string description: Unique identifier of the email template example: "534449i" commentId: type: string description: Unique identifier of the viewed comment example: "516ee02e-4325-42c6-9c54-48e60a1beba5" threadCommentId: type: string description: Unique identifier of the comment thread example: "76836e77-2f3c-4b3e-9479-6e8330692325" viewerId: type: string description: User ID of the person who viewed the comment (string format) example: "19" ErrorResponse: type: object description: Standard error response format properties: error: type: string description: Error type or category example: "Bad Request" message: type: string description: Detailed error message example: "Invalid request format" ``` ## Plugin Usage API This API allows you to track plugin usage metrics, including information about email view counts and timer view counts. This webhook is called once during plugin initialization to provide current usage limits for your subscription. The webhook delivers information about: * **Timer views remaining** — the number of timer element views left in your plan * **Unique emails remaining** — the number of unique email templates left in your plan You can enable and configure this integration in your **Plugin Settings → Server Settings**. ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: Plugin Usage API description: | This endpoint is called by the Stripo to send counter updates for timer views and unique emails remaining for a plugin. version: 1.0.0 servers: - url: https://your-webhook-endpoint.com description: Webhook receiver endpoint (configurable) security: - basicAuth: [] paths: /: post: summary: Receive plugin counter data description: | Receives webhook notifications about plugin usage counters including remaining timer views and unique emails. operationId: receivePluginCounters security: - basicAuth: [] requestBody: required: true description: Plugin counter data containing remaining limits content: application/json: schema: $ref: '#/components/schemas/WebhookCounterData' examples: withBothCounters: summary: Both counters present value: timerViewsLeft: 1000 uniqueEmailsLeft: 500 onlyTimerViews: summary: Only timer views counter value: timerViewsLeft: 1000 uniqueEmailsLeft: null onlyUniqueEmails: summary: Only unique emails counter value: timerViewsLeft: null uniqueEmailsLeft: 500 responses: '200': description: Webhook received successfully components: securitySchemes: basicAuth: type: http scheme: basic description: | HTTP Basic Authentication. The Authorization header is generated using Base64 encoding of username:password. schemas: WebhookCounterData: type: object description: Contains the remaining counter values for a plugin properties: timerViewsLeft: type: integer format: int64 nullable: true description: | Number of timer views remaining for the plugin. example: 1000 uniqueEmailsLeft: type: integer format: int64 nullable: true description: | Number of unique emails remaining for the plugin. example: 500 example: timerViewsLeft: 1000 uniqueEmailsLeft: 500 ``` --- --- url: >- https://plugin.stripo.email/editor-configuration/permissions-and-access-management.md --- # Permissions and Access Management Permissions and Access Management allows you to define what actions each user can perform in the Stripo Plugin editor. This ensures that only authorized users can view or modify emails, modules, or related resources. All permissions are managed on your backend — the Stripo Plugin enforces them automatically. ## How it works 1. Enable the **User Permissions API** in your Stripo Plugin account: Plugin → Server Settings → User Permissions API. 2. Provide your backend endpoint URL and Basic Authentication credentials. ::: image-wrap ![](/img/plugin/new/image229.webp){width=1999 height=1008} ::: 3. When the editor is initialized, Stripo will call your endpoint with the `ES‑PLUGIN‑UI‑DATA` header, which contains the `metadata` you passed in the plugin initialization script. 4. Your backend must respond with a JSON object that specifies which actions are allowed for this user. 5. Based on the response, the Stripo editor will enable or disable features accordingly. ## Response format ```json { "codeEditor": { "read": true, "write": false }, "appearance": { "read": true, "write": false }, "content": { "read": true, "write": false, "textOnly": false }, "modules": { "read": true, "write": false }, "versionHistory": { "read": true, "write": false }, "manageOwnComments": { "read": true, "write": false }, "manageAllComments": { "read": true, "write": false }, "accessibilityTesting": { "read": true, "write": true }, "elementsLock": { "read": true, "write": true } } ``` ## Permissions reference The response object supports the following permission groups and actions: ## Example scenarios * **Full editing**\ Grant `read: true` and `write: true` in all permissions. * **Read-only access**\ Grant `read: true` and set all `write: false`. * **Text-only editing**\ Grant `content.read: true` and `content.textOnly: true`, with `content.write: false`. ## API Reference For details on the request/response format, see [User Permissions API → `GET` method](/reference/user-permissions-api?utm_source=chatgpt.com#tag/methods/get/). --- --- url: https://plugin.stripo.email/plugin-invocations.md --- # Plugin Invocations The Stripo Plugin offers both a Javascript API and a Backend API. The Javascript API is used for interaction with the UI part of the Plugin, while the Backend API is utilized to retrieve a ready-to-send email template. --- --- url: https://plugin.stripo.email/plugin-invocations/javascript-api.md --- # JavaScript API The new Stripo editor provides a flexible API for interaction with external environments. Access to the API can be obtained through the global `StripoEditorApi` object. **Accessing the Stripo API:** ```js const StripoEditorApi = window.StripoEditorApi ``` Available APIs: API for working with modules in Module Editing Mode. A detailed description can be found in the [modulesApi](#4b.-додати-окремий-підрозділ-##-modules-api)) section. **Example Usage:** To access the version history API, you can use the following code: ```js const VersionHistoryApi = window.StripoEditorApi.versionHistoryApi ``` ## Actions API The `actionsApi` provides methods for performing various actions within the Stripo email editor. This API allows for dynamic interactions with the editor, enabling users to execute commands such as saving templates, undoing/redoing changes, setting names, and more. **Available Methods:** ```js const callback = function ({html, css, width, height, utmParams, syncModulesIds}) { // Process the template data here }; window.StripoEditorApi.actionsApi.getTemplateData(callback); ``` ```js const callback = function (html, css, width, height) { // ... } window.StripoEditorApi.actionsApi.getTemplate(callback) ``` ```js const сompileEmailCallback = function(error, html, ampHtml, ampErrors, displayConditions) { // ... }; const utmParameters = { utmSource: '', utmMedium: '', utmCampaign: '', utmContent: '', utmTerm: '', customUtms: [ { 'name1': 'val1', 'name2': 'val2' } ] }; window.StripoEditorApi.actionsApi.compileEmail( { callback: сompileEmailCallback, minimize: true, utmEntity: utmParameters, mergeTags: ['Tag1', 'Tag2'], forсeAmp: false, resetDataSavedFlag: false, disableLineHeightsReplace: true } ); ``` ```js window.StripoEditorApi.actionsApi.setName('New email name') ``` ```js const isAllDataSaved = window.StripoEditorApi.actionsApi.isAllDataSaved() ``` ```js const saveCallback = function (error) { //... } window.StripoEditorApi.actionsApi.save(saveCallback) ``` ```js window.StripoEditorApi.actionsApi.undo() ``` ```js window.StripoEditorApi.actionsApi.redo() ``` ```js window.StripoEditorApi.actionsApi.showAmpErrorsModal( ampErrors, [ { text: 'Preview', action: () => { console.log('"Preview" button clicked') }, }, ], () => { console.log('"Close" button clicked') }, () => { console.log('"Fix in Code Editor" button clicked') } ) ``` ```js // Turn custom styles ON window.StripoEditorApi.actionsApi.activateCustomViewStyles(true); // Turn custom styles OFF window.StripoEditorApi.actionsApi.activateCustomViewStyles(false); ``` ```js const html = '

Hello world

'; const css = 'p { color: #ff0000; }'; window.StripoEditorApi.actionsApi.updateHtmlAndCss( html, css, () => { console.log('Template HTML and CSS were updated'); } ); ``` ## Email Metadata API The `emailMetadataApi` provides methods for accessing and managing the metadata associated with an email template in the Stripo email editor. This API allows users to retrieve and update metadata such as the email title and preheader, facilitating integration with external systems and enhancing the customization of the email editing experience. **Available Methods:** ```js const title = window.StripoEditorApi.emailMetadataApi.getTitle(); if (title) { console.log('Current email title:', title) } else { console.log('No title is specified for the email in the editor.') } ``` ```js const newTitle = 'Welcome Email' window.StripoEditorApi.emailMetadataApi.setTitle(newTitle) ``` ```js const preheader = window.StripoEditorApi.emailMetadataApi.getHiddenPreHeader(); if (preheader) { console.log('Current hidden preheader:', preheader) } else { console.log('No hidden preheader is specified for the email in the editor.') } ``` ```js const newPreheader = "Don't miss out on our latest updates!" window.StripoEditorApi.emailMetadataApi.setHiddenPreHeader(newPreheader) ``` ## Accessibility API The `accessibilityApi` provides methods for controlling the [Accessibility Testing Mode](/editor-configuration/accessibility-checker) in the Stripo email editor. This API allows your application to programmatically start accessibility testing, simulate visual impairments, adjust preview settings, and control the editor interface while testing accessibility. Accessibility Testing Mode helps users identify potential accessibility issues in email templates by simulating different visual conditions and providing tools for inspecting the email content. **Available Methods:** `openAccessibilityTestingMode` The **openAccessibilityTestingMode** method switches the editor to **Accessibility Testing Mode**.\ This method only changes the editor mode. The UI controls used to manage accessibility testing (for example preview switching, image visibility, or color vision filters) must be implemented in the host application interface. **Sample** ```js window.StripoEditorApi.accessibilityApi.openAccessibilityTestingMode(); ``` **Detailed Explanation** * **Custom UI Integration:**\ The plugin does not provide built-in UI controls for managing the testing mode. These controls can be implemented by the plugin owner in the host application interface. * **Testing Environment Activation:**\ Once activated, the editor becomes ready to accept additional Accessibility API commands such as applying color filters or switching preview modes. **Use Cases** * **Custom Accessibility Button:**\ Add a button in your application header that starts accessibility testing. * **Accessibility Workflow:**\ Trigger accessibility testing as part of a quality assurance workflow before sending the email. `closeAccessibilityTestingMode` The **closeAccessibilityTestingMode** method exits Accessibility Testing Mode and returns the editor to the regular editing mode. **Sample** ```js window.StripoEditorApi.accessibilityApi.closeAccessibilityTestingMode(); ``` **Detailed Explanation** * **Return to Editing Mode:**\ The editor switches back to the normal editing interface. * **Accessibility Simulation End:**\ Any testing environment previously enabled (such as image hiding or color vision filters) is no longer applied. **Use Cases** * **Custom Exit Button:**\ Implement a button that allows users to leave accessibility testing mode. * **UI State Control:**\ Automatically exit testing mode after accessibility checks are completed. `isAccessibilityTestingModeOpen` The **isAccessibilityTestingModeOpen** method checks whether the editor is currently in Accessibility Testing Mode. **Sample** ```js const isOpen = window.StripoEditorApi.accessibilityApi.isAccessibilityTestingModeOpen(); ``` **Detailed Explanation** * **Mode Status Detection:**\ Returns the current state of the Accessibility Testing Mode. * **Boolean Response:**\ Returns `true` if testing mode is active and `false` otherwise. **Use Cases** * **UI State Synchronization:**\ Update custom UI controls depending on whether the editor is currently in testing mode. * **Prevent Duplicate Activation:**\ Avoid triggering Accessibility Testing Mode if it is already active. `showImages` The **showImages** method displays images in the email preview while Accessibility Testing Mode is active. **Sample** ```js window.StripoEditorApi.accessibilityApi.showImages(); ``` **Detailed Explanation** * **Image Visibility Control:**\ This method restores image visibility in the email preview. * **Accessibility Simulation:**\ It is typically used after images were previously hidden during testing. **Use Cases** * **Toggle Image Visibility:**\ Implement a UI control that allows users to switch between showing and hiding images. * **Testing Email Readability:**\ Compare how the email appears with images enabled and disabled. `hideImages` The **hideImages** method hides images in the email preview while Accessibility Testing Mode is active. **Sample** ```js window.StripoEditorApi.accessibilityApi.hideImages(); ``` **Detailed Explanation** * **Image Suppression:**\ All images in the preview are temporarily hidden. * **Accessibility Testing:**\ This allows users to verify whether the email content remains understandable without images. **Use Cases** * **Accessibility Verification:**\ Ensure that important information is not conveyed only through images. * **Alt Text Testing:**\ Evaluate how well the email content works in text-only scenarios. `setColorVisionDeficiency` The **setColorVisionDeficiency** method applies a visual filter that simulates a specific type of color vision deficiency. **Supported values** `'protanopia'`\ `'deuteranopia'`\ `'tritanopia'`\ `'achromatopsia'`\ `''` An empty string disables the filter. **Sample** ```js window.StripoEditorApi.accessibilityApi.setColorVisionDeficiency('protanopia'); ``` **Detailed Explanation** * **Color Vision Simulation:**\ The editor applies a filter that mimics how users with specific color vision deficiencies perceive the email. * **Testing Visual Accessibility:**\ This helps ensure that color contrast and visual hierarchy remain understandable. **Use Cases** * **Color Accessibility Testing:**\ Verify that text and interactive elements remain distinguishable. * **Inclusive Design Validation:**\ Improve accessibility for users with color blindness. `getColorVisionDeficiency` The **getColorVisionDeficiency** method returns the currently applied color vision deficiency filter. **Sample** ```js const filter = window.StripoEditorApi.accessibilityApi.getColorVisionDeficiency(); ``` **Detailed Explanation** * **Filter State Retrieval:**\ Returns the currently active color vision simulation filter. * **Possible Values:**\ `'Protanopia'`, `'deuteranopia'`, `'tritanopia'`, `'achromatopsia'`, `''` **Use Cases** * **UI Synchronization:**\ Update custom UI controls based on the currently applied accessibility filter. * **Testing State Tracking:**\ Ensure that the correct filter is applied during accessibility testing. `switchToDesktopPreview` The **switchToDesktopPreview** method switches the email preview to the **desktop layout** while Accessibility Testing Mode is active. **Sample** ```js window.StripoEditorApi.accessibilityApi.switchToDesktopPreview(); ``` **Detailed Explanation** * **Preview Mode Change:**\ Displays the desktop version of the email template. * **Accessibility Evaluation:**\ Helps evaluate layout accessibility on larger screens. **Use Cases** * **Responsive Accessibility Testing:**\ Compare accessibility between desktop and mobile views. `switchToMobilePreview` The **switchToMobilePreview** method switches the email preview to the **mobile layout** while Accessibility Testing Mode is active. **Sample** ```js window.StripoEditorApi.accessibilityApi.switchToMobilePreview(); ``` **Detailed Explanation** * **Mobile Rendering:**\ Displays the mobile layout of the email template. * **Accessibility Testing:**\ Allows checking readability on smaller screens. **Use Cases** * **Mobile Accessibility Checks** * **Responsive Design Validation** `emitToggleCodeEditor` The **emitToggleCodeEditor** method opens or closes the Code Editor while Accessibility Testing Mode is active. **Sample** ```js window.StripoEditorApi.accessibilityApi.emitToggleCodeEditor(); ``` **Detailed Explanation** * **Code Editor Toggle:**\ This method toggles the visibility of the Code Editor panel. * **Template Inspection:**\ Developers can inspect or adjust the HTML structure while performing accessibility testing. **Use Cases** * **Advanced Template Review** * **Debugging Accessibility Issues** ## Version History API The `versionHistoryApi` provides methods for managing the version history of email templates in the Stripo editor. This API allows users to track changes, undo or redo actions, and restore previous versions of an email template. Please be advised, that option can be activated or deactivated within the plugin configuration page in your Stripo account, on the Server Settings tab. If the option is disabled, the Version History feature will not be shown or function for any users of the plugin. If you want to customize who can see and use the Version History feature, you can manage permissions individually using the Stripo User Permissions API. ::: image-wrap ![](/img/plugin/new/image35.webp){width=350 height=661} ::: **Version annotations: tags and names.** Every saved version (patch) of an email can carry annotations that your integration manages through this API: * Tags — up to 20 labels per version, each 1–60 characters. Tags are case-insensitive: adding a duplicate tag is a successful no-op. The same tag can be attached to many versions. * Name — a single label of 1–60 characters. A version can have only one name, and a name is unique within the email: assigning it to another version releases it from the previous one. ::: image-wrap ![](/img/plugin/new/image249.webp){width=350 height=403} ::: Annotation methods work with already saved versions only — they never trigger `actionsApi.save` and do not touch unsaved local changes. Reading methods require the `versionHistory.read` permission; modifying methods require `versionHistory.write`. If the Version History option is disabled in your Plugin settings, every method below fails with the `VERSION_HISTORY_NOT_ENABLED` error without sending a request. **Available Methods:** ```js const onStateChanged = function (state) { const prevPatch = state.previousPatch const currentPatch = state.currentPatch const nextPatch = state.nextPatch const currentPatchId = currentPatch.id const currentPatchDate = currentPatch.date const currentPatchDescription = currentPatch.description const currentPatchAuthorId = currentPatch.authorId const currentPatchAuthorName = currentPatch.authorName } const onVersionHistoryClosed = function () {} window.StripoEditorApi.versionHistoryApi.openVersionHistory(onStateChanged, onVersionHistoryClosed) ``` ```js window.StripoEditorApi.versionHistoryApi.closeVersionHistory() ``` ```js window.StripoEditorApi.versionHistoryApi.switchToMobilePreview() ``` ```js window.StripoEditorApi.versionHistoryApi.switchToDesktopPreview() ``` ```js const prevPatch = ...; window.StripoEditorApi.versionHistoryApi.previewVersion(prevPatch.id, function(error) { // handle error }); ``` ```js const prevPatch = ...; window.StripoEditorApi.versionHistoryApi.restoreVersion(prevPatch.id, function() { // do something on success }, function(error) { // handle error }); ``` ```js window.StripoEditorApi.versionHistoryApi.getLastSavedPatchId( patchId => console.log('last saved patch:', patchId), error => console.error(error) ) ``` ```js window.StripoEditorApi.versionHistoryApi.addTag( { tag: 'approved', patchId: '869f6f19-9fbd-4e4b-9e61-0ff74b5fa4f9' }, // patchId is optional result => console.log(result), // { patchId, tag, alreadyExisted } error => console.error(error) ) ``` ```js window.StripoEditorApi.versionHistoryApi.setName( { name: 'release 2.678', patchId: '869f6f19-9fbd-4e4b-9e61-0ff74b5fa4f9' }, // patchId is optional result => console.log(result), // { patchId, name } error => console.error(error) ) ``` `getTags` Returns the tags of the email's versions as a map, where each tag points to the array of version IDs it is attached to. ```js window.StripoEditorApi.versionHistoryApi.getTags( { tagsCount: 10 }, // max number of tags per request result => console.log(result), // { tags: { approved: ['patch1', 'patch2'] }, totalTagsCount: 1, lastTag: '...' } error => console.error(error) ) ``` **Pagination:** * request the first page with `tagsCount` only. If the response contains `lastTag`, there are more results — pass it together with `tagsCount` to fetch the next page. `lastTag` is the real last value of the page (an exclusive boundary); when it is absent, you have reached the end. * `totalTagsCount` always contains the total number of tags. Requires the `versionHistory.read` permission. Fails with `VERSION_HISTORY_NOT_ENABLED` when the Version History option is disabled. `getNames` Returns the names of the email's versions as a map. A name is unique within the email, so each entry points to a single version ID. ```js window.StripoEditorApi.versionHistoryApi.getNames( { namesCount: 10 }, // max number of names per request result => console.log(result), // { names: { 'release 2.678': 'patch678' }, totalNamesCount: 1, lastName: '...' } error => console.error(error) ) ``` **Pagination:** works the same way as in `getTags` — request the first page with `namesCount`, continue with the returned `lastName` boundary until it is no longer present in the response. Requires the `versionHistory.read` permission. Fails with `VERSION_HISTORY_NOT_ENABLED` when the Version History option is disabled. `movePatchName` Moves an existing name from one version to another — for example, to shift a `production` label to a newer version. ```js window.StripoEditorApi.versionHistoryApi.movePatchName( '7caee114-c7a9-4801-bddd-cbae5cbcd568', // sourcePatchId -- the version that currently has the name '869f6f19-9fbd-4e4b-9e61-0ff74b5fa4f9', // targetPatchId -- the version to receive it result => console.log(result), // { name, sourcePatchId, targetPatchId, replacedName? } error => console.error(error) ) ``` **Behavior notes:** * After a successful call, the source version no longer has a name and the target version carries it. * If the target version already had a different name, it is overwritten and returned in the result as `replacedName`. * The call fails if the source version has no name or if source and target are the same version. * The operation does not open the Version History panel and never triggers a save. Requires the `versionHistory.write` permission. Fails with `VERSION_HISTORY_NOT_ENABLED` when the Version History option is disabled. `openVersionHistoryByPatchId` Opens Version History directly on a specific version. The editor loads the required data for the given `patchId`, rebuilds that version, shows its preview, expands the group the version belongs to, scrolls the panel to it, and highlights its card as selected. Combine it with `getNames` / `getTags` / `getLastSavedPatchId` to open a version found by its annotation without making the user search the history manually. ```js window.StripoEditorApi.versionHistoryApi.openVersionHistoryByPatchId( 'c6cb4e93-35c2-4b66-b542-1df30bdb976a', // patchId state => console.log('state:', state), // optional, same shape as in openVersionHistory () => console.log('history closed'), // optional error => console.error(error) // optional ) ``` **Behavior notes:** * The first `onStateChanged` call fires when the target preview is ready, with `state.currentPatch.id` equal to the requested `patchId`; afterwards the callback behaves exactly as in `openVersionHistory`. * If Version History is already open, the call switches it to the new target version without closing the panel. * If several calls run concurrently, the latest one wins. * If the version does not exist, belongs to another email, or is not available to the user, `onFailed` receives `VERSION_HISTORY_PATCH_NOT_FOUND` — the method never falls back to the latest version. * The regular `openVersionHistory` keeps working as before and opens the latest version. Requires the `versionHistory.read` permission and an active connection. Fails with `VERSION_HISTORY_NOT_ENABLED` when the Version History option is disabled. ## Code Editor API The `codeEditorApi` provides methods for interacting with the code editor within the Stripo email editor. This API allows users to open the code editor, close the code editor, and retrieve the current state of the code editor panel. ```js const state = window.StripoEditorApi.codeEditorApi.getCodeEditorState() /* { "isOpen": false, "isDefaultCSSOpen":true, "isCustomCSSOpen": false, "containerHeight": 80, "defaultCSSPanelWidth": 100, "customCSSPanelWidth": 100 } */ ``` ```js window.StripoEditorApi.codeEditorApi.openCodeEditor() ``` ```js window.StripoEditorApi.codeEditorApi.closeCodeEditor() ``` ## Editor Copilot API The **Editor Copilot API** allows you to work with the email template markup (HTML and CSS) in a structured and safe way. Instead of modifying raw HTML or CSS strings, this API exposes **AST-like nodes and modifiers** that are already used internally by Stripo extensions. The API is available via: ```js window.StripoEditorApi.editorCopilotApi ``` ```js const templateModifier = window.StripoEditorApi.editorCopilotApi.getTemplateModifier(); ``` ```js const htmlRoot = window.StripoEditorApi.editorCopilotApi.getDocumentRootHtmlNode(); ``` ```js const cssRoot = window.StripoEditorApi.editorCopilotApi.getDocumentRootCssNode(); ``` ## UI API The `uiApi` interface provides methods to control the visibility of UI panels in the Stripo plugin. ```js window.StripoEditorApi.uiApi.setSettingsPanelVisible(false); // Hides the settings panel ``` ```js window.StripoEditorApi.uiApi.setBlocksPanelVisible(false); // Hides the blocks panel ``` ```js window.StripoEditorApi.uiApi.setActiveGeneralPanelTab(tabId, callback); ``` ```json { "status": "success" } ``` ```json { "status": "error", "code": "TAB_NOT_AVAILABLE", "message": "Tab \"{tabId}\" is not available or cannot be activated." } ``` ```js window.StripoEditorApi.uiApi.setActiveGeneralPanelTab('styles', function(result) { if (result.status === 'success') { console.log('Tab switched successfully'); } else { console.error(result.message); } }); ``` ```js window.StripoEditorApi.uiApi.setActiveGeneralPanelTab('comments'); ``` `getActiveGeneralPanelTab` Returns the identifier of the currently active tab in the **General Settings panel** of the editor. This method allows external applications to read the current UI state of the General Panel — for example, to build integrations that react to which tab is active without manually tracking tab switches. **Syntax:** ```js const tabId = window.StripoEditorApi.uiApi.getActiveGeneralPanelTab(); ``` **Return value** *(string)* — Identifier of the currently active tab. **Possible values:** * `"letter"` — Message Settings tab * `"styles"` — General Styles tab * `"comments"` — Comments tab * extension tab `id` — identifier defined by an extension with `type: "extensionGeneralTab"` **Example:**\ Read the active tab: ```js const activeTab = window.StripoEditorApi.uiApi.getActiveGeneralPanelTab(); console.log('Active tab:', activeTab); // e.g. 'styles' ``` Check the active tab before switching: ```js if (activeTab !== 'comments') { window.StripoEditorApi.uiApi.setActiveGeneralPanelTab('comments', function(result) { if (result.status === 'success') { console.log('Switched to Comments tab'); } }); } ``` **Use Cases:** * **Use Cases: UI State Synchronization**: Keep your application UI controls in sync with the editor's current General Panel tab. * **Conditional Logic:** Build integrations that react to the currently active tab — for example, showing or hiding application-side controls based on whether the user is in the Styles or Comments tab. * **Tab Guard:** Check the current tab before calling `setActiveGeneralPanelTab` to avoid redundant tab switches. **Example: Hiding Panels When Opening Code Editor** To automatically hide both the settings panel and the blocks panel when opening the code editor, use the following sample implementation: ```js onCodeEditorVisibilityChanged: function (isCodeEditorVisible) { window.StripoEditorApi.uiApi.setSettingsPanelVisible(!isCodeEditorVisible); window.StripoEditorApi.uiApi.setBlocksPanelVisible(!isCodeEditorVisible); } ``` This ensures that when the code editor is opened, the panels are hidden, and when it is closed, the panels become visible again. ## View Options API The `viewOptionsApi` allows you to dynamically control how the template is displayed inside the editor. You can use it to respond to changes in your own UI — for example, if you allow your users to toggle preview modes, merge tag visibility, or hide/show elements dynamically. See details [here](/editor-configuration/initialization-settings#view-options). ```js window.StripoEditorApi.viewOptionsApi.setViewOptions(viewOptions) ``` ```js window.StripoEditorApi.viewOptionsApi.setViewOptions({ mimeType: 'html', mergeTags: 'label', showHiddenElements: false, displayConditions: [ { id: '1234', name: 'VIP Users', visibility: true }, { id: '5678', name: 'Beta Testers', visibility: false } ] }); ``` `showPinsInEditor` Displays all comment pins on the editor canvas. Pins become visible to users in the editing area, allowing them to see comment annotations attached to document elements. **Note:** This method only affects canvas visibility. Pins in the Comments tab panel remain always visible. **Sample:** ```js window.StripoEditorApi.viewOptionsApi.showPinsInEditor() ``` **Details:** This method shows all comment pins that were previously hidden. The pins remain attached to their respective elements and are now visually displayed. This is useful for switching between editing modes where users need to see or ignore comment annotations. `hidePinsInEditor` Hides all comment pins from the editor canvas view. Pins remain attached to elements but are not visually displayed on the canvas, providing a distraction-free editing experience. **Note:** This method only affects canvas visibility. Pins in the Comments tab panel remain always visible. **Sample:** ```js window.StripoEditorApi.viewOptionsApi.hidePinsInEditor() ``` **Details:** This method hides all currently visible comment pins. The pins are preserved on their elements and can be shown again by calling `showPinsInEditor()`. This is useful for focused editing or presentation modes where comment annotations should not be visible. `getPinsEditorState` Returns the current visibility state of pins on the editor canvas. Use this method to check whether comment pins are currently shown or hidden on the canvas before toggling visibility. **Note:** This method reflects canvas visibility only, not the Comments tab where pins are always visible. **Sample:** ```js const areVisible = window.StripoEditorApi.viewOptionsApi.getPinsEditorState() ``` **Parameters:** * Return value — `boolean` — `true` if pins are visible in the editor, `false` if pins are hidden. **Example:** ```js window.StripoEditorApi.viewOptionsApi.getPinsEditorState() // Returns: true (pins are visible) // Toggle pin visibility based on current state const currentState = window.StripoEditorApi.viewOptionsApi.getPinsEditorState() if (currentState) { window.StripoEditorApi.viewOptionsApi.hidePinsInEditor() } else { window.StripoEditorApi.viewOptionsApi.showPinsInEditor() } ``` **Details:** You can call this method to determine the current pin visibility before performing conditional logic. This method does not change the editor state — it only reports the current visibility status. Use it in combination with `showPinsInEditor()` and `hidePinsInEditor()` to implement toggle functionality ## **Comments API** Used to manage comments in the editor. Methods allow developers to programmatically trigger comment creation mode and check comment system status. `addComment` Activates comment creation mode in the editor. The cursor changes to a crosshair, prompting the user to select a document element for adding a comment. Once an element is selected, the comment creation dialog appears with permission checks applied based on the user's role. **Sample:** ```js window.StripoEditorApi.commentsApi.addComment() ``` **Behavior**. When `addComment()` is called: * Editor enters comment mode with crosshair cursor active. * User can click on any document element to attach a comment. * Comment dialog opens with selected element context. * Permission validation occurs — if user lacks comment creation rights, multilingual error message displays. * User can type and submit comment, or cancel the operation. **Error Handling**\ If the user doesn't have permission to add comments, error messages appear in the user's language through the [notifications](/editor-configuration/initialization-settings#notification-settings) parameter. **Example:** ```js // Trigger comment mode from custom UI button document.getElementById('addCommentBtn').addEventListener('click', () => { window.StripoEditorApi.commentsApi.addComment() }) // User clicks on an image element → comment dialog opens // User types comment → comment is saved with element reference ``` **Details:** Use this method to create custom "Add Comment" triggers in your application UI. The method respects role-based permissions automatically. If permissions validation fails, the appropriate error message is shown without entering comment mode. ## Template Theme Mode API The `templateThemeModeApi` allows you to dynamically control how the **email template is rendered inside the editor canvas** — in **Light** or **Dark** mode. You can use this API to synchronize template rendering with your application UI, allow users to toggle between light and dark views, or apply theme changes dynamically without reloading the editor. See details [here](/editor-configuration/initialization-settings#template-theme-mode). `setTemplateThemeMode` Updates the template rendering mode inside the editor. Use this method when users interact with your UI controls (for example, a Light / Dark toggle) and you want to immediately update how the email template is displayed. **Sample** ```js StripoEditorApi.templateThemeModeApi.setTemplateThemeMode(templateThemeMode); ``` **Parameters:** * **templateThemeMode** — one of the following values: * `'LIGHT'` — renders the template in light mode. * `'DARK'` — renders the template in dark mode. **Example:** ```js StripoEditorApi.templateThemeModeApi.setTemplateThemeMode('DARK'); ``` **Details:** * The change is applied immediately. * The editor is not reinitialized. * The email HTML, styles, and export output remain unchanged. `getThemeMode` Returns the currently active template theme mode. **Sample** ```js StripoEditorApi.templateThemeModeApi.getThemeMode(); ``` **Example** ```js const mode = StripoEditorApi.templateThemeModeApi.getThemeMode(); console.log(mode); // 'LIGHT' or 'DARK' ``` **Details:** * This method can be used to synchronize your UI state with the editor. * Useful when restoring UI controls or reacting to external state changes. ## Modules API The `modulesApi` provides methods for working with modules when the editor runs in [Module Editing Mode](/editor-configuration/module-editing-mode) `entityType: 'module'`. It lets your application open the module details dialog programmatically — for example, from a custom "Show module data" button in your interface. The `openModuleDetailsDialog` method opens the module details dialog, where the module metadata (name, description, category, tags) is displayed and edited. It mirrors the module details panel available in email mode. **Sample:** ```js window.StripoEditorApi.modulesApi.openModuleDetailsDialog(); ``` **Use Cases:** Custom "Show module data" button: Add a button in your application UI that opens the module details dialog on click. Available in Module Editing Mode: Use this method when the editor is initialized with `entityType: 'module`'. ## Dev Tools API The `devToolsApi` provides methods for interacting with developer tools of the Stripo editor. This API facilitates the handling of exceptional situations and issues with the editor. ```js window.StripoEditorApi.devToolsApi.dump() ``` --- --- url: https://plugin.stripo.email/plugin-invocations/backend-api.md --- # Backend API The Stripo Plugin Backend API allows for various operations related to compiling and managing email templates, including inlining CSS styles into HTML tags to provide the final HTML code ready to be sent to recipients. Please take a look at the available methods below. ## Compiling Email Templates This API call allows inlining CSS styles into HTML tags and provides the final HTML code of email templates that are ready to be sent to recipients. ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: Compiling Email Templates description: | This API call allows inlining CSS styles into HTML tags and provides the final HTML code of email templates that are ready to be sent to recipients. version: 1.0.0 servers: - url: https://plugins.stripo.email description: Stripo API server paths: /coediting/v1/email/compilation: get: tags: - Methods summary: Get compiled email description: Get compiled inline CSS into HTML email templates. parameters: - name: minimize in: query required: false description: >- If true then html code will be in a format of a single line without line breaks schema: type: boolean example: false - name: inlineCss in: query required: false description: > Allows you to select the type of code in the received compiled email. inlineCss: true - by default. This means that we will see CSS placed inside tags when we make a compiled form of HTML. inlineCss: false - editor will not inline the CSS in the tags, but rather write it at the head of the email. schema: type: boolean example: false - name: ES-PLUGIN-AUTH in: header required: true description: Stripo plugin auth token in the format - Bearer ${AUTH_TOKEN} schema: type: string - name: ES-PLUGIN-UI-DATA in: header required: true description: > JSON string with parameters to identify email (Same as metadata param in UI editor). For example: {"emailId": "id1"} schema: type: string responses: '200': description: Compiled email content: application/json: schema: $ref: '#/components/schemas/CompiledEmail' components: schemas: CompiledEmail: type: object properties: html: type: string description: Compiled HTML ampHtml: type: string description: Compiled AMP version of HTML ampErrors: type: array description: List of AMP errors inside AMP HTML email template items: type: string syncModules: type: array description: List of IDs of sync modules what was found in the HTML items: type: integer format: int64 conditions: type: array description: List of AMP errors inside AMP HTML email template items: $ref: '#/components/schemas/Condition' Condition: type: object properties: id: type: string description: Condition ID name: type: string description: Condition name description: type: string description: Condition description beforeScript: type: string description: Content of beforeScript section afterScript: type: string description: Content of afterScript section ``` ## Raw HTML Compilation This API call allows you to compile raw HTML and CSS into export-ready email HTML by passing the source directly in the request body. Inlines CSS styles, resolves synchronizable modules, applies display conditions, adds UTM parameters, and generates AMP HTML when AMP markup is detected.\ Use this method for legacy integrations or external workflows where email content is managed outside the Stripo editor. ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: Raw HTML Compilation version: 1.0.0 servers: - url: https://plugins.stripo.email description: Stripo plugin host paths: /api/v1/cleaner/v1/compress: post: summary: Compile raw HTML and CSS description: | Accepts raw email HTML and CSS directly and returns compiled, export-ready HTML. Unlike the session-based compilation endpoint, this method does not require an active editor session or email ID — it processes whatever HTML and CSS you provide. Suitable for legacy integrations and external workflows where email content is managed outside the Stripo editor. Performs the same compilation pipeline: inlines CSS styles, resolves synchronizable modules, applies display conditions, adds UTM parameters, and generates AMP HTML when AMP markup is detected. operationId: compileTemplateHtml tags: - Methods security: - ESPluginAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CompressRequest' example: html: "Subject

Hello

" css: "p { color: red; }" minimize: true utmSource: "newsletter" utmMedium: "email" utmCampaign: "spring_sale" responses: '200': description: HTML was compiled successfully. content: application/json: schema: $ref: '#/components/schemas/CompressResponse' example: html: "

Hello

" subject: "Subject" '400': description: Bad request, unsupported content type, missing body, missing `html`, or validation error. content: application/json: schema: $ref: '#/components/schemas/APIError' examples: missingHtml: value: message: "'html' param is required" missingBody: value: message: "Required request body is missing" cssTooLarge: value: message: "'css' must not be greater than 5 000 000 symbols" htmlTooLarge: value: message: "'html' must not be greater than 9 000 000 symbols" '401': description: Missing, invalid, or expired plugin authentication token. content: application/json: schema: $ref: '#/components/schemas/ResponseText' example: message: "ES-PLUGIN-AUTH header is invalid or token is expired. Set header 'ES-PLUGIN-AUTH: Bearer YOUR_AUTH_TOKEN'" '429': description: Rate limit reached. content: application/json: schema: $ref: '#/components/schemas/RateLimitError' example: error: RATE_LIMIT_REACHED '500': description: Unexpected service error. components: securitySchemes: ESPluginAuth: type: apiKey in: header name: ES-PLUGIN-AUTH description: Plugin JWT authentication header. Expected format is `Bearer YOUR_AUTH_TOKEN`. schemas: CompressRequest: type: object required: - html additionalProperties: false description: | Request body for compiling email template HTML. Only `html` is required by runtime validation. properties: html: type: string maxLength: 9000000 description: Source email template HTML to compile. css: type: string description: | CSS to inline into the HTML. If absent or empty, HTML is returned without CSS inlining. Non-blank CSS is rejected only when both the raw CSS and the CSS after comment removal exceed 5,000,000 characters. minimize: type: boolean nullable: true default: false description: When true, removes unnecessary whitespace and compresses the regular HTML output. utmSource: type: string description: Value to add as the `utm_source` query parameter on links. utmMedium: type: string description: Value to add as the `utm_medium` query parameter on links. utmCampaign: type: string description: Value to add as the `utm_campaign` query parameter on links. utmContent: type: string description: Value to add as the `utm_content` query parameter on links. utmTerm: type: string description: Value to add as the `utm_term` query parameter on links. customUtms: type: array description: Custom query parameters to add to links along with standard UTM parameters. items: $ref: '#/components/schemas/CustomUtm' mergeTags: type: array description: Merge tag placeholders to preserve while processing links and UTM parameters. items: type: string apiRequestData: type: string description: | Plugin request data, encoded as a JSON string, passed to synchronizable module replacement. In the editor UI this is derived from plugin `apiRequestData` only when synchronizable modules are enabled. Pass it only if the HTML contains synchronizable modules that must be resolved. CompressResponse: type: object required: - html additionalProperties: false properties: html: type: string description: Compiled HTML. subject: type: string description: Email subject extracted from the HTML title. preheader: type: string description: Hidden email preheader extracted from the HTML. ampHtml: type: string description: Generated AMP HTML. Returned only when AMP markup is detected in source HTML. ampErrors: type: array description: AMP validation errors. Empty array means AMP validation passed; absent when AMP HTML was not generated. items: type: string syncModules: type: array description: Synchronizable module IDs. items: type: integer format: int64 conditions: type: array description: Display conditions extracted from the source HTML. items: $ref: '#/components/schemas/Condition' Condition: type: object additionalProperties: false properties: id: type: string description: Condition identifier. name: type: string description: Condition name. description: type: string description: Condition description. beforeScript: type: string description: Script inserted before the conditional content. afterScript: type: string description: Script inserted after the conditional content. CustomUtm: type: object additionalProperties: false properties: name: type: string description: Custom query parameter name. value: type: string description: Custom query parameter value. APIError: type: object additionalProperties: true properties: message: type: string description: Human-readable error message. traceId: type: string description: Trace identifier, when available. code: type: string description: Application error code, when available. params: description: Additional error parameters. error: description: Structured internal error data, when available. detailedError: type: string description: Detailed error description, when available. date: type: string format: date-time description: Error timestamp, when available. ResponseText: type: object additionalProperties: false properties: message: type: string description: Error message returned by the API gateway auth filter. RateLimitError: type: object additionalProperties: false properties: error: type: string description: Rate limit error code. example: RATE_LIMIT_REACHED ``` ## Retrieving HTML and CSS (Legacy Compatibility) This API call allows you to retrieve HTML and CSS from the “reference email” in Stripo’s database. This is particularly useful for maintaining compatibility with older versions of the Stripo editor. ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: Retrieving HTML and CSS (Legacy Compatibility) description: | Returns HTML with developer markup and classes, along with CSS, to be stored within your database. This method ensures that when a customer wants to open an email with the editor (old plugin) next time, the stored HTML and CSS can be sent to the editor, maintaining compatibility with legacy systems and preserving the email's design and structure. version: 1.0.0 servers: - url: https://plugins.stripo.email description: Stripo API server paths: /coediting/v1/email/html-css: get: tags: - Methods summary: Get HTML and CSS of email template description: Get HTML with developer markup and classes, along with CSS parameters: - name: ES-PLUGIN-AUTH in: header required: true description: Stripo plugin auth token in the format - Bearer ${AUTH_TOKEN} schema: type: string - name: ES-PLUGIN-UI-DATA in: header required: true description: > JSON string with parameters to identify email (Same as metadata param in UI editor). For example: {"emailId": "id1"} schema: type: string responses: '200': description: HTML and CSS of email template content: application/json: schema: $ref: '#/components/schemas/HtmlCss' components: schemas: HtmlCss: type: object properties: html: type: string description: HTML of email template css: type: string description: CSS of email template emailId: type: integer format: int64 description: ID of email syncModules: type: array description: List of IDs of sync modules what was found in the HTML (only in case you've activated the Synchronized Modules) items: type: integer format: int64 utm: $ref: '#/components/schemas/UtmParams' description: UTM params of email template UtmParams: type: object properties: source: type: string description: UTM source medium: type: string description: UTM medium campaign: type: string description: UTM campaign content: type: string description: UTM content term: type: string description: UTM term custom: description: UTM custom properties additionalProperties: type: string nullable: true ``` ## Cloning an Email Model Create a copy of an existing email model programmatically. This API method allows you to clone an existing email model, creating a new, independent model with a unique identifier. The original email model is not modified. Use this endpoint to duplicate email models when you need to reuse structure or content in automation, integrations, or editorial workflows. ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: Email Model Clone API description: | This API method allows you to clone an existing email model and create a new one based on its structure and content. The cloned email model is created as a separate entity with a new identifier, while the original email model remains unchanged. This method is useful when you need to duplicate an email model programmatically and continue working with the copied version independently. version: 1.0.0 servers: - url: https://plugins.stripo.email description: Stripo API server paths: /coediting/v1/email/copy: post: tags: - Methods summary: Clone email model description: | Clone an existing email model and create a new email model based on it. The method copies the structure and content of the source email model and creates a new email model with a new unique identifier. The response contains the identifier of the newly created email model, which can be used for further operations in the editor or via API. parameters: - name: ES-PLUGIN-AUTH in: header required: true description: Stripo plugin auth token in the format - Bearer ${AUTH_TOKEN} schema: type: string - name: ES-PLUGIN-UI-DATA in: header required: true description: > JSON string with parameters to identify email (same as metadata parameter in the UI editor). For example: {"emailId": "id1"} schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CopyEmailRequest' responses: '200': description: | Email model cloned successfully. Returns the ID of the newly created email model. content: application/json: schema: $ref: '#/components/schemas/CopyEmailResponse' components: schemas: CopyEmailRequest: type: object properties: sourceId: type: string description: Source email ID destinationId: type: string description: Destination email ID CopyEmailResponse: type: object properties: destinationId: type: string description: Destination email ID ``` ## Deleting Email Models Delete one or multiple email models programmatically. This API method allows you to delete multiple email models by their unique identifiers in a single request. Up to 1000 email models can be removed per call; deleted models are permanently removed and cannot be restored. Use this endpoint to manage email model lifecycle in automation pipelines, integrations, or bulk cleanup workflows. ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: Bulk Delete Email Models API description: | This API specification describes the bulk deletion endpoint for deleting multiple emails. **Authentication**: Plugin-based authentication using custom headers. Requires valid plugin authentication token and plugin UI data. **Authorization**: Only users with "api" role are permitted to use this endpoint. Other roles will receive a 403 Forbidden response. **Validation**: - Number of emails to delete limited by 1000 emails version: 1.0.0 contact: name: Stripo Support url: https://stripo.email servers: - url: https://plugins.stripo.email description: Stripo Plugin API server security: - pluginAuth: [] paths: /coediting/v1/emails/bulk-delete: post: summary: Bulk delete multiple emails description: | Deletes multiple email models by their IDs. Only users with the api role can use this endpoint. operationId: bulkDeleteEmails tags: - Methods security: - pluginAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DeleteEmailsRequest' examples: singleEmail: summary: Delete a single email value: emailIds: ["email-123"] multipleEmails: summary: Delete multiple emails value: emailIds: ["email-123", "email-456", "email-789"] bulkDeletion: summary: Bulk deletion of emails value: emailIds: - "email-001" - "email-002" - "email-003" - "email-004" - "email-005" responses: '204': description: | All emails deleted successfully. No response body is returned. '400': description: | Bad request - Invalid request format, missing email IDs, array too large, or invalid email ID format detected. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: invalidBody: summary: Invalid request body format value: Message: "Invalid request body" noEmailIds: summary: No email IDs provided value: Message: "No email IDs provided" tooManyIds: summary: Too many email IDs in request value: Message: "Too many email IDs (max 1000)" invalidIdFormat: summary: Invalid email ID format value: Message: "Invalid email ID format: email\"123" '401': description: | Unauthorized - Authentication failed, invalid credentials, missing plugin ID, or plugin authentication response is invalid. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: authFailed: summary: Authentication failed value: Message: "Unauthorized" '403': description: | Forbidden - User does not have the required "api" role to perform bulk deletions. This endpoint is restricted to plugin users with "api" role only. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: insufficientPermissions: summary: User lacks required role value: Message: "Forbidden: insufficient permissions" '405': description: Method not allowed - Only POST method is supported content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: methodNotAllowed: summary: Wrong HTTP method used value: Message: "Method Not Allowed" '500': description: | Internal server error - Failed to delete emails. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: deleteFailed: summary: Deletion operation failed value: Message: "Internal server error, lookup traceId in headers for more information" components: schemas: DeleteEmailsRequest: type: object description: Request body containing email IDs to delete required: - emailIds properties: emailIds: type: array description: | List of email model IDs to delete. Min: 1, max: 1000. items: type: string minLength: 1 maxLength: 100 minItems: 1 maxItems: 1000 example: ["email-123", "email-456", "email-789"] ErrorResponse: type: object description: | Standard error response structure. required: - Message properties: Message: type: string description: Human-readable error message example: "Invalid request body" securitySchemes: pluginAuth: type: apiKey in: header name: ES-PLUGIN-AUTH description: | Plugin authentication token in the format: Bearer ${AUTH_TOKEN} **Role Requirement**: User must have "api" role to use this endpoint. The role is validated after successful authentication. tags: - name: Methods description: Email management operations ``` ## Retrieving Plugin Modules This API call allows you to retrieve a list of email template modules (also known as custom blocks) created within your Stripo Plugin environment. You can use this endpoint to fetch all modules associated with your plugin, including their metadata, icons, HTML/CSS content, categories, and synchronization status. For faster response, modules are returned without HTML/CSS content by default. If you need to include it, set the `withContent` parameter to `true` ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: Retrieving Plugin Modules API description: | This API specification describes the endpoint for retrieving plugin modules from the Stripo Email Editor plugin backend service. The endpoint allows authenticated plugins to list and filter their custom email template modules (blocks) with advanced filtering, pagination, and sorting capabilities. **Authentication**: Plugin-based authentication using the `ES-PLUGIN-AUTH` header. The authentication token must have the role set to "API" to access this endpoint. **Authorization**: Only modules belonging to the authenticated plugin are returned. Cross-plugin access is not permitted. **Performance Optimization**: Use the `withContent` parameter to include HTML and CSS content in responses. By default, content is excluded for optimal performance. **Filtering Capabilities**: Support for filtering by key, category, tags, synchronization status, and full-text search on module names. **Pagination**: Flexible pagination using either offset-based or page-based approaches with configurable page sizes. version: 1.0.0 contact: name: Stripo Support url: https://stripo.email servers: - url: https://plugins.stripo.email description: Plugin Backend Service security: - pluginAuth: [] paths: /api/v1/customblocks/v4/modules/list: get: summary: List plugin modules tags: - Methods operationId: getModulesList description: | Retrieve a paginated and filterable list of all plugin modules belonging to the authenticated plugin. **Authorization**: Only modules associated with the plugin identified by the provided authentication token are returned. The token must have "API" role. **Performance**: By default, HTML and CSS content are excluded for optimal performance. Set `withContent=true` to include HTML and CSS content in the response. **Filtering**: Combine multiple filters (key, query, categories, tags, id, synchronizable) to narrow down results. All filters are applied with AND logic. **Sorting**: Results can be sorted by `id` or `name` in ascending or descending order. **Pagination**: Use either `offset` for cursor-based pagination or `page` for page-based pagination. The `limit` parameter controls the page size (default: 20). security: - pluginAuth: [] parameters: - in: header name: ES-PLUGIN-AUTH description: | Plugin authentication token with "API" role. Must be in Bearer token format. required: true schema: type: string example: Bearer YOUR_AUTH_TOKEN - in: query name: key description: | Filter by module key (folder name). Returns modules matching the specified key. required: false schema: type: string example: "header-templates" - in: query name: query description: | Full-text search query for module name. required: false schema: type: string example: "newsletter" - in: query name: categories description: | Filter by one or more category IDs. Returns modules belonging to any of the specified categories. required: false schema: type: array items: type: integer format: int64 example: [1, 2, 5] - in: query name: tags description: | Filter by one or more tag values. Returns modules that have any of the specified tags. required: false schema: type: array items: type: string example: ["promotional", "seasonal"] - in: query name: id description: | Filter by specific module ID. When provided, returns only the module with this exact ID (if it belongs to the authenticated plugin). required: false schema: type: integer format: int64 example: 12345 - in: query name: synchronizable description: | Filter by synchronizable status. Set to `true` for synchronizable modules only, `false` for non-synchronizable modules only, or omit for all modules. required: false schema: type: boolean example: true - in: query name: withContent description: | Performance optimization flag. When set to `true`, the `html` and `css` fields will be included in the response. By default (`false`), content is excluded to reduce response size when only module metadata is needed. required: false schema: type: boolean default: false example: false - in: query name: sortingColumn description: | Column to sort results by. Options are `id` (module ID) or `name` (module name). required: false schema: type: string enum: [id, name] default: id example: "name" - in: query name: sortingAsc description: | Sort direction. Set to `true` for ascending order, `false` for descending order. required: false schema: type: boolean default: false example: true - in: query name: offset description: | Zero-based pagination offset. Specifies the starting position in the result set. Takes precedence over the `page` parameter when both are provided. required: false schema: type: integer minimum: 0 default: 0 example: 0 - in: query name: page description: | One-based page number for pagination. Used only if `offset` is not provided. Calculated as: offset = (page - 1) * limit required: false schema: type: integer minimum: 1 example: 1 - in: query name: limit description: | Maximum number of modules to return per page. Controls the page size for pagination. required: false schema: type: integer minimum: 1 maximum: 100 default: 20 example: 20 responses: '200': description: | Modules list retrieved successfully. Returns a paginated list of modules with metadata and optionally HTML/CSS content. content: application/json: schema: $ref: '#/components/schemas/ModulesListResponseDto' examples: basicList: summary: Basic module list with content (withContent=true) value: modules: - id: 12345 key: "header-templates" name: "Modern Header" html: "...
" css: ".header { padding: 20px; }" blockType: "BASIC" scope: "HEADER" icon: "https://example.com/icon.png" croppedIcon: "https://example.com/icon-cropped.png" description: "A modern header template" synchronizable: true tags: ["modern", "responsive"] tagObjects: - id: 1 value: "modern" - id: 2 value: "responsive" category: key: 1 name: "Headers" createdOn: "2024-01-15T10:30:00Z" updatedOn: "2024-01-20T14:45:00Z" total: 1 offset: 0 limit: 20 metadataOnly: summary: Module list without content (default, withContent=false) value: modules: - id: 12345 key: "header-templates" name: "Modern Header" html: null css: null blockType: "BASIC" scope: "HEADER" icon: "https://example.com/icon.png" croppedIcon: "https://example.com/icon-cropped.png" description: "A modern header template" synchronizable: true tags: ["modern", "responsive"] category: key: 1 name: "Headers" createdOn: "2024-01-15T10:30:00Z" updatedOn: "2024-01-20T14:45:00Z" total: 1 offset: 0 limit: 20 emptyList: summary: Empty result set value: modules: [] total: 0 offset: 0 limit: 20 '401': description: | Unauthorized - Authentication failed. This can occur when: - The authentication token is missing or invalid - The token has expired - The token does not have "API" role content: application/json: schema: type: object '403': description: | Forbidden - The authenticated plugin does not have permissions to access modules. This typically indicates an authorization configuration issue. content: application/json: schema: type: object components: securitySchemes: pluginAuth: type: apiKey in: header name: ES-PLUGIN-AUTH description: | Plugin authentication using Bearer token in the ES-PLUGIN-AUTH header. The token must have "API" role to access this endpoint. schemas: ModulesListResponseDto: type: object description: Response containing a paginated list of plugin modules with metadata properties: modules: type: array items: $ref: '#/components/schemas/ModuleListItemDto' description: | Array of module objects. Each module contains metadata and optionally HTML/CSS content (depending on the `withContent` parameter). total: type: integer format: int64 description: | Total count of modules matching the filter criteria (across all pages). Used for calculating pagination metadata. minimum: 0 example: 42 offset: type: integer description: | Current pagination offset (starting position in the result set). Corresponds to the `offset` parameter from the request. minimum: 0 example: 0 limit: type: integer description: | Maximum number of modules returned in this page. Corresponds to the `limit` parameter from the request. minimum: 1 example: 20 ModuleListItemDto: type: object description: Represents a single plugin module with all its metadata and optional content properties: id: type: integer format: int64 description: | Unique identifier of the module within the plugin. example: 12345 key: type: string description: | Module key (folder name). Used for organizing modules into logical groups. example: "header-templates" name: type: string description: | Human-readable name of the module. Displayed in the editor's module library. example: "Modern Header" html: type: string nullable: true description: | HTML content of the module. Contains the email template markup. This field will be `null` when `withContent=false` (default) or not specified. example: "...
" css: type: string nullable: true description: | CSS styles for the module. Applied to the HTML content when rendered. This field will be `null` when `withContent=false` (default) or not specified. example: ".header { padding: 20px; }" blockType: type: string description: | Type of block. Determines how the module behaves in the editor. example: "STRUCTURE" scope: type: string description: | Defines the section of the email where this module can be used. Modules are organized by scope in the editor's module library. enum: [INFO_AREA, HEADER, MENU, CONTENT, FOOTER] example: "HEADER" icon: type: string description: | URL to the module's icon image. Displayed as thumbnail in the module library. example: "https://example.com/icons/header-modern.png" croppedIcon: type: string description: | URL to the cropped version of the module's icon. Used for optimized display. example: "https://example.com/icons/header-modern-cropped.png" description: type: string description: | Textual description of the module. Provides additional context about the module's purpose and design. example: "A modern, responsive header template with logo and navigation" synchronizable: type: boolean description: | Indicates whether this module supports synchronization. Synchronized modules can be updated across multiple email templates automatically. example: true tags: type: array items: type: string description: | List of tag values associated with the module. Tags provide additional categorization and filtering capabilities. This is a simplified array of string values. example: ["modern", "responsive", "promotional"] tagObjects: type: array items: type: object properties: id: type: integer format: int64 description: Unique identifier of the tag example: 1 value: type: string description: Tag value (name) example: "modern" description: | List of tag objects with full details including tag IDs. Provides more complete information than the `tags` array. category: type: object properties: key: type: integer format: int64 description: | Unique identifier of the category. example: 1 name: type: string description: | Human-readable name of the category. example: "Headers" description: | Category information for the module. Categories provide high-level organization of modules in the editor's library. createdOn: type: string format: date-time description: | ISO 8601 timestamp indicating when the module was created. example: "2024-01-15T10:30:00Z" updatedOn: type: string format: date-time description: | ISO 8601 timestamp indicating when the module was last updated. example: "2024-01-20T14:45:00Z" tags: - name: Methods description: Plugin module management operations ``` ## Deleting Plugin Modules Soft-delete one or multiple plugin modules programmatically. This API method allows you to mark multiple plugin modules as deleted by their unique identifiers in a single request. Deleted modules are not physically removed from the database — they are marked as deleted and will no longer appear in the module library or API responses. Each module ID in the request is validated against the authenticated plugin — if any module does not belong to the plugin or does not exist, the entire request is rejected with a 403 Forbidden response and no modules are deleted. Use this endpoint to manage the module lifecycle in automation pipelines, integrations, or bulk cleanup workflows. ### OpenAPI Specification ```yaml openapi: 3.0.3 info: title: Delete Plugin Modules API description: | This API call bulk soft-deletes plugin modules from the module library. Modules are marked as deleted and are not physically removed from the database. **Authentication**: Plugin-based authentication using custom headers. Requires a valid plugin authentication token. **Authorization**: Only users with "API" role are permitted to use this endpoint. Other roles will receive a 403 Forbidden response. **Ownership Validation**: Each module ID in the request is validated to ensure it belongs to the authenticated plugin. If any module belongs to a different plugin, the entire request is rejected with 403 and no modules are deleted. **Behaviour**: - Modules that belong to the plugin are soft-deleted (marked as `deleted = true`). - If any `moduleId` belongs to another plugin, the request is rejected with 403 and no module is deleted. - Non-existent `moduleIds` are silently skipped. version: 1.0.0 contact: name: Stripo Support url: https://stripo.email servers: - url: https://plugins.stripo.email description: Stripo Plugin API server security: - pluginAuth: [] paths: /api/v1/customblocks/v4/modules/bulk-delete: post: summary: Bulk soft-delete modules description: | Soft-deletes a list of plugin modules by their IDs. Requires a token with the "API" role. Each module ID is verified to belong to the plugin identified by the authentication token. operationId: bulkDeleteModules tags: - Methods security: - pluginAuth: [] parameters: - name: ES-PLUGIN-AUTH in: header required: true description: "Stripo plugin auth token in the format — Bearer ${AUTH_TOKEN}. Must have the 'API' role." schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BulkDeleteModulesRequest' examples: singleModule: summary: Delete a single module value: moduleIds: [101] multipleModules: summary: Delete multiple modules value: moduleIds: [101, 102, 103] bulkDeletion: summary: Bulk deletion of modules value: moduleIds: - 101 - 102 - 103 - 104 - 105 responses: '204': description: | All modules soft-deleted successfully. No response body is returned. '400': description: | Bad request — moduleIds must not be empty or array too large. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: noModuleIds: summary: No module IDs provided value: message: "moduleIds must not be empty" tooManyIds: summary: Too many module IDs in request value: message: "Too many module IDs (max 1000)" '401': description: | Unauthorized — ES-PLUGIN-AUTH header is missing or the token is invalid or expired. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: authFailed: summary: Authentication failed value: message: "Unauthorized" '403': description: | Forbidden — either the token does not have the "API" role, or at least one of the provided moduleIds belongs to another plugin. In the latter case no module is deleted. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: insufficientPermissions: summary: User lacks required role value: message: "Forbidden: insufficient permissions" foreignModule: summary: Module belongs to another plugin value: message: "Forbidden: one or more modules do not belong to the authenticated plugin" '500': description: | Internal server error — Failed to delete modules. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: deleteFailed: summary: Deletion operation failed value: message: "Internal server error, lookup traceId in headers for more information" components: securitySchemes: pluginAuth: type: apiKey in: header name: ES-PLUGIN-AUTH description: | Plugin authentication using Bearer token in the ES-PLUGIN-AUTH header. The token must have "API" role to access this endpoint. schemas: BulkDeleteModulesRequest: type: object description: Request body containing module IDs to soft-delete. required: - moduleIds properties: moduleIds: type: array description: | List of module IDs to soft-delete. Min: 1, max: 1000. All IDs must belong to the plugin identified by the authentication token. Non-existent IDs are silently skipped. items: type: integer format: int64 minItems: 1 maxItems: 1000 example: [101, 102, 103] ErrorResponse: type: object properties: message: type: string description: Human-readable error description example: "moduleIds must not be empty" tags: - name: Methods description: Plugin module management operations ``` ## Updating Timer Block Links This API call allows you to update timer links in the HTML code when copying an email template outside the Stripo editor. It ensures that the timers are correctly cloned and updated in the new template. ### OpenAPI Specification ```yaml openapi: 3.0.1 info: title: Timer Clone API description: This API call allows you to update timer links in the HTML code when copying an email template outside the Stripo editor. It ensures that the timers are correctly cloned and updated in the new template. version: 1.0.0 servers: - url: https://plugins.stripo.email paths: /api/v1/timers/clone: post: tags: - Methods summary: Updating Timer Block Links description: Clone timers and update timer links in HTML requestBody: required: true content: application/json: schema: type: object properties: html: type: string description: HTML code containing timer links example: 'Timer link: ...' required: - html parameters: - in: header name: ES-PLUGIN-AUTH required: true schema: type: string description: Authorization token example: Bearer YOUR_AUTH_TOKEN responses: '200': description: Successful response with updated HTML and timer mapping content: application/json: schema: type: object properties: html: type: string description: HTML code with updated timer links example: 'Timer link: ...' timersMap: type: object description: Mapping of old timer IDs to new timer IDs and URLs. Keys are old timer IDs, values are objects containing new timer information. additionalProperties: type: object properties: id: type: integer description: New timer ID in the cloned template example: 152752 url: type: string format: uri description: URL of the new timer image example: 'https://cdt-timer-plugins.stripocdn.email/api/v1/images/s5exXSdnyicX1pCXcz83Faw-C7kFWaTlkeHuxpPS8rs' required: - id - url example: '152702': id: 152752 url: 'https://cdt-timer-plugins.stripocdn.email/api/v1/images/s5exXSdnyicX1pCXcz83Faw-C7kFWaTlkeHuxpPS8rs' '152701': id: 152751 url: 'https://cdt-timer-plugins.stripocdn.email/api/v1/images/VxPC8zhLbJuBOfRINW0LODFkYw7WWRpi5WgOOq4AGsI' ``` ## Access to Stripo Templates Stripo provides access to a variety of email templates that you can use based on your subscription plan. If you have registered your account and integrated your plugin application, you can access these templates. :::success **Template Access Based on Plan:** * **FREE Plan:** Access to basic templates only. * **STARTUP Plan:** Access to basic templates and those marked as FREE. * **BUSINESS and ENTERPRISE Plans:** Access to basic, free, and PREMIUM templates. ::: ### How to Access Templates After making the initial request to get an array of the available templates, you will need to make individual calls for each template to retrieve its HTML and CSS. ### Step-by-Step Process 1. **Get List of Templates:** Make a request to retrieve an array of available templates based on your plan.\ **Endpoint:**\ `GET /bapi/plugin-templates/v1/templates` 2. **Get HTML and CSS for Each Template:** For each template in the array, make separate requests to retrieve its HTML and CSS.\ **Endpoint:**\ `GET /bapi/plugin-templates/v1/templates/{templateId}` ### OpenAPI Specification ```yaml openapi: 3.0.0 info: title: Stripo Email Template API description: | Stripo provides access to a variety of email templates that you can use based on your subscription plan. If you have registered your account and integrated your plugin application, you can access these templates. After making the initial request to get an array of the available templates, you will need to make individual calls for each template to retrieve its HTML and CSS. version: 1.0.0 servers: - url: https://my.stripo.email/bapi/plugin-templates paths: /v1/templates: get: tags: - Methods summary: Get Templates description: Get templates matching the specified search criteria. parameters: - name: type in: query required: true schema: type: string enum: [BASIC, FREE, PREMIUM] description: Specifies the variety of templates to be returned. - name: sort in: query schema: type: string enum: [NEW, ACTUAL] default: ACTUAL description: Defines how the templates are sorted in response. - name: limit in: query schema: type: integer description: Regulates how many items should be returned per page (suggested limit of no more than 50). - name: page in: query schema: type: integer description: Defines the number of the page. - name: templateTypes in: query schema: type: array items: type: integer description: Filters templates that specifically pertain to relevant categories. - name: templateSeasons in: query schema: type: array items: type: integer description: Restricts the selection of templates to those associated with seasonal events categories. - name: templateFeatures in: query schema: type: array items: type: integer description: Ensures that only templates related to specific feature categories are retrieved. - name: templateIndustries in: query schema: type: array items: type: integer description: Narrows down template selection to include only those featuring industry-specific categories. responses: '200': description: A list of templates matching the search criteria. content: application/json: schema: type: object properties: total: type: integer data: type: array items: type: object properties: templateId: type: integer name: type: string logo: type: string premium: type: boolean hasAmp: type: boolean updatedAt: type: integer createdTime: type: integer templateTypes: type: array items: type: object properties: id: type: integer name: type: string templateSeasons: type: array items: type: object properties: id: type: integer name: type: string templateFeatures: type: array items: type: object properties: id: type: integer name: type: string templateIndustries: type: array items: type: object properties: id: type: integer name: type: string security: - ES-PLUGIN-AUTH: [] /v1/templates/{templateId}: get: tags: - Methods summary: Get Template Details description: Get the metadata, HTML, and CSS code for a specific template by ID. parameters: - name: templateId in: path required: true schema: type: integer responses: '200': description: Metadata, HTML, and CSS code for the specified template. content: application/json: schema: type: object properties: templateId: type: integer name: type: string logo: type: string premium: type: boolean hasAmp: type: boolean updatedAt: type: integer createdTime: type: integer templateTypes: type: array items: type: object properties: id: type: integer name: type: string templateSeasons: type: array items: type: object properties: id: type: integer name: type: string templateFeatures: type: array items: type: object properties: id: type: integer name: type: string templateIndustries: type: array items: type: object properties: id: type: integer name: type: string html: type: string css: type: string security: - ES-PLUGIN-AUTH: [] /v1/templates/types: get: tags: - Methods summary: Get Template Types description: Get available values for the templateTypes parameter. responses: '200': description: Array of available values for the templateTypes parameter. content: application/json: schema: type: array items: type: object properties: id: type: integer name: type: string security: - ES-PLUGIN-AUTH: [] /v1/templates/seasons: get: tags: - Methods summary: Get Template Seasons description: Get available values for the templateSeasons parameter. responses: '200': description: Array of available values for the templateSeasons parameter. content: application/json: schema: type: array items: type: object properties: id: type: integer name: type: string security: - ES-PLUGIN-AUTH: [] /v1/templates/features: get: tags: - Methods summary: Get Template Features description: Get available values for the templateFeatures parameter. responses: '200': description: Array of available values for the templateFeatures parameter. content: application/json: schema: type: array items: type: object properties: id: type: integer name: type: string security: - ES-PLUGIN-AUTH: [] /v1/templates/industries: get: tags: - Methods summary: Get Template Industries description: Get available values for the templateIndustries parameter. responses: '200': description: Array of available values for the templateIndustries parameter. content: application/json: schema: type: array items: type: object properties: id: type: integer name: type: string security: - ES-PLUGIN-AUTH: [] components: securitySchemes: ES-PLUGIN-AUTH: type: apiKey in: header name: ES-PLUGIN-AUTH description: Bearer YOUR_AUTH_TOKEN ``` ### Detailed Explanation #### **Get Templates** * **Summary:** Get templates matching the specified search criteria. * **Parameters:** * `type` (required, query): Specifies the variety of templates to be returned (`BASIC`, `FREE`, `PREMIUM`). * `sort` (query): Defines how the templates are sorted in response (`NEW`, `ACTUAL`). * `limit` (query): Regulates how many items should be returned per page (suggested limit of no more than 50). * `page` (query): Defines the number of the page. * `templateTypes` (query): Filters templates that specifically pertain to relevant categories. * `templateSeasons` (query): Restricts the selection of templates to those associated with seasonal events categories. * `templateFeatures` (query): Ensures that only templates related to specific feature categories are retrieved. * `templateIndustries` (query): Narrows down template selection to include only those featuring industry-specific categories. #### **Get Template Details** `/v1/templates/{templateId}` * **Summary:** Get the metadata, HTML, and CSS code for a specific template by ID. * **Parameters:** * `templateId` (required, path): The ID of the template. #### **Get Template Types** `/v1/templates/types` * **Summary:** Get available values for the `templateTypes` parameter. #### **Get Template Seasons** `/v1/templates/seasons` * **Summary:** Get available values for the `templateSeasons` parameter. #### **Get Template Features** `/v1/templates/features` * **Summary:** Get available values for the `templateFeatures` parameter. #### **Get Template Industries** `/v1/templates/industries` * **Summary:** Get available values for the `templateIndustries` parameter. --- --- url: https://plugin.stripo.email/extensions.md --- # Introduction ## What is Stripo Extensions SDK? The Stripo Extensions SDK is a powerful JavaScript/TypeScript framework that empowers developers to extend and customize the Stripo email editor with tailored functionality. Whether you're building custom content blocks, integrating with your existing systems, or creating a fully branded email design experience, the SDK provides the tools and APIs you need to make the Stripo editor truly yours. Built on a modern, immutable architecture designed for real-time collaboration, the SDK enables you to: * **Create custom content blocks** with drag-and-drop functionality * **Integrate with external services**, such as image libraries, video libraries, AI assistants, and additional third-party systems * **Customize the editor interface** with custom controls, panels, and styling ## Why Use the Extensions SDK? ### Unlimited Customization Go beyond the default editor capabilities. Create blocks, controls, and workflows that match your exact requirements. From simple custom buttons to complex, multi-step configuration wizards, the SDK gives you complete control. ### Seamless Integration Connect the Stripo editor to your existing ecosystem. Replace default components with your own implementations—use your image library, connect to your CRM's merge tags, integrate your AI service, or sync with your product catalog. ### Production-Ready Architecture Built on battle-tested patterns for collaborative editing, the SDK handles the complex challenges of real-time synchronization, undo/redo, and template versioning automatically. You focus on building features, not infrastructure. ### Developer-Friendly Experience Modern JavaScript/TypeScript APIs, comprehensive documentation, and real-world examples help you get started quickly and build confidently. ## What's Next? Choose your path based on your goals: **Understanding the architecture?** → Read [Core Concepts](/extensions/core-concepts) **New to Stripo Extensions?** → Start with [Getting Started](/extensions/getting-started) **Want to see working examples?** → Explore [Tutorials & Examples](/extensions/tutorials) **Need API reference?** → Browse [API Documentation](/extensions/reference) --- --- url: https://plugin.stripo.email/extensions/core-concepts.md --- # Core Concepts ## Immutability ### The Philosophy Behind Immutability Stripo Editor was designed from day one for multi-user, real-time editing. This enables efficient teamwork without conflicts or data loss. This collaboration model places specific requirements on the editor's architecture and on how an email template is modified. The system must: * Distribute changes quickly and reliably to everyone participating in the session; * Resolve conflicts correctly when multiple users edit the same part of a template at the same time; * Track a full history of changes—who changed what and when. ### Why raw HTML/CSS editing is disabled To guarantee a consistent result for all users and preserve a verifiable change history, direct editing of HTML and CSS inside the editor is disabled. All modifications are expressed as patches—atomic operations that change structure or content. These patches are broadcast to all participants and applied using a CRDT (Conflict-free Replicated Data Type) algorithm adapted by the Stripo team for HTML/CSS. This ensures that every participant converges to the same state deterministically. ### Data model in the Extensions SDK To prevent accidental direct mutations of the template, the Extensions SDK does not expose the template as DOM elements (HTMLElement). Instead, it uses special, read-only ImmutableNode objects. ImmutableNode lets you inspect structure and properties, but it does not allow direct writes to the template. ```javascript // Traditional DOM (NOT supported in extensions) const element = document.querySelector('.my-element'); element.style.color = 'red'; // ❌ Direct mutation element.setAttribute('data-id', '123'); // ❌ Direct mutation element.innerHTML = 'New'; // ❌ Direct mutation // Immutable Node System (REQUIRED in extensions) const node = this.api.getDocumentRoot().querySelector('.my-element'); const color = node.getStyle('color'); // ✅ Reading is allowed const id = node.getAttribute('data-id'); // ✅ Reading is allowed const html = node.getInnerHTML(); // ✅ Reading is allowed // node.style.color = 'red'; // ❌ This doesn't exist! ``` ### How to modify a template correctly Templates are modified through the "Template Modification System" — a set of safe, editor-validated operations that automatically sync across all participants in a session. Use this system for any changes to an email's structure or content. ## Components The extension system is built around several core components you can include in an extension. They are: 1. **Blocks**: Custom content blocks that can be added to the editor 2. **UI Elements**: Custom UI elements to extend the editor interface 3. **Controls**: Form controls for settings panels 4. **Settings Panels**: Panels for configuring blocks 5. **Context Actions**: Actions that appear in block's context menus 6. **Tag Registry**: Helps to override default UiElements with custom HTML tags ::: image-wrap ![](/img/extensions/stripo_extensions_components.png) ::: ## Lifecycle Cleanup Starting from v3.7.0, extension components can implement a `destroy()` method to release resources when the editor is reinitialized or the extension is uninstalled. Use `destroy()` to: * Remove event listeners attached outside the template * Clear timers or subscriptions * Clean up DOM artifacts added to `document.body` ## Integration Points ### Overview The Stripo Extensions SDK provides extension points that allow you to override built-in editor functionality with your own implementations. Instead of using Stripo's default UI for common tasks like selecting images, videos, or managing dynamic content, you can integrate your own systems, libraries, and services directly into the editor workflow. The SDK exposes the following interfaces for external service integration: ### External Image Library **When to Use:** * You have an existing media library * You need to enforce specific image selection rules * You want to provide curated, brand-approved images * You require custom metadata or tagging systems **Full Example Implementation:** [How to Integrate an External Image Library](./tutorials/examples/integrations/external-image-library.md) **API Reference:** [ExternalImageLibrary](./reference/integrations/ExternalImageLibrary.md) :::tip New in v3.2.0 You can now create a custom tab within the build-in Stripo image library using [ExternalImageLibraryTab](./reference/integrations/ExternalImageLibraryTab.md) to organize images from different sources (e.g., stock photos, brand assets, user uploads). Custom tab can be registered independently without requiring a full `ExternalImageLibrary` implementation, making it easier to add image sources to the editor. **Full Example Implementation:** [How to Integrate an External Image Library Tab](./tutorials/examples/integrations/external-image-library-tab.md) **API Reference:** [ExternalImageLibraryTab](./reference/integrations/ExternalImageLibraryTab.md) ::: *** ### External Video Library **When to Use:** * You host videos on your own platform * You need to maintain a curated video library **Full Example Implementation:** [How to Integrate an External Video Library](./tutorials/examples/integrations/external-video-library.md) **API Reference:** [ExternalVideosLibrary](./reference/integrations/ExternalVideosLibrary.md) *** ### External Smart Elements Library **How It Works:** Stripo handles the layout and rendering of Smart Elements, while your implementation controls what data gets inserted. By implementing the SDK's interface, you connect your data source without rebuilding the Smart Elements logic. You simply provide the data structure. **When to Use:** * You have a product catalog * You need real-time or personalized content in emails **Full Example Implementation:** [How to Integrate an External Smart Elements Library](./tutorials/examples/integrations/external-smart-elements-library.md) **API Reference:** [ExternalSmartElementsLibrary](./reference/integrations/ExternalSmartElementsLibrary.md) *** ### External AI Assistant **When to Use:** * You have specific AI service requirements or preferences * You need to align AI output with brand guidelines * You want to implement custom prompts or workflows **Full Example Implementation:** [How to Integrate an External AI Assistant](./tutorials/examples/integrations/external-ai-assistant.md) **API Reference:** [ExternalAiAssistant](./reference/integrations/ExternalAiAssistant.md) *** ### External Display Conditions Library **How It Works:** Your implementation provides the UI and logic for defining conditions, while Stripo handles the rendering and export based on those conditions. By implementing the SDK's interface, you can maintain complex conditional logic in your own system. **When to Use:** * You have existing segmentation or personalization rules * You need to sync conditions with external systems * You want to provide user-friendly condition builders * You require custom condition types beyond Stripo's defaults **Full Example Implementation:** [How to Integrate External Display Conditions](./tutorials/examples/integrations/external-display-conditions.md) **API Reference:** [ExternalDisplayConditionsLibrary](./reference/integrations/ExternalDisplayConditionsLibrary.md) *** ### External Merge Tags Selector **How It Works:** When the merge tags selector is clicked, the SDK invokes your implementation, allowing you to open a modal with your catalog of personalization variables. Once a user selects a tag, your implementation returns it via callback, and Stripo inserts it at the current cursor position. **When to Use:** * You have custom personalization fields in your system * You want to provide account-specific or context-aware merge tags * You require custom organization or categorization of variables **Full Example Implementation:** [How to Integrate External Merge Tags](./tutorials/examples/integrations/external-merge-tags-selector.md) *** ### External Custom Font **How It Works:** A new **Custom** tab appears in the Font Family dropdown with a "+ Insert custom font" button. When clicked, the SDK invokes your implementation, allowing you to open a modal where users define the font name, CSS declaration, and URL. Once confirmed, your implementation returns the font parameters via callback, and the font becomes immediately available in the dropdown. **When to Use:** * You want to give users control over font selection * You need to integrate with external font services * You have brand-specific fonts that need to be dynamically loaded **Full Example Implementation:** [How to Integrate External Custom Font](./tutorials/examples/integrations/external-custom-font.md) --- --- url: https://plugin.stripo.email/extensions/getting-started.md --- # Getting Started ## Installation ### Prerequisites Before you begin, ensure you have: * **Node.js v22.x or higher** installed on your system * **npm or yarn** package manager * **A text editor** (VS Code recommended for the best development experience) * **Basic JavaScript knowledge** and familiarity with modern web development ### Step 1: Set Up Your Project Create a new directory for your extension project and initialize it: ```bash mkdir extension-starter cd extension-starter npm init -y ``` **Expected result:** A new `package.json` file will be created in your directory. ### Step 2: Install the Extensions SDK Install the Stripo Editor Extensions SDK: ```bash npm install @stripoinc/ui-editor-extensions ``` For development, you will also need a bundler. We recommend using Vite: ```bash npm install --save-dev vite ``` **Expected result:** The packages will be installed in the `node_modules` directory and listed in your `package.json` dependencies. ### Step 3: Create Your Project Structure Create the following file structure: ```bash extension-starter/ ├── index.html ├── src/ │ ├── creds.js │ ├── index.js │ └── extension.js ├── package.json └── vite.config.js ``` Create each file with the following content: #### Create `index.html`: ```html Stripo Plugin
Stripo Plugin

⚠️ Please be advised: The header shown above is not part of the plugin. It is intended solely for demonstration purposes and can be implemented independently in any desired way.

``` #### Create `vite.config.js`: ```javascript import { defineConfig } from 'vite'; export default defineConfig({ server: { port: 3000 } }); ``` #### Create `src/creds.js`: :::warning Important Replace the placeholder values `YOUR_PLUGIN_ID` and `YOUR_SECRET_KEY` with your actual plugin credentials. ::: ```javascript export const PLUGIN_ID = 'YOUR_PLUGIN_ID'; export const SECRET_KEY = 'YOUR_SECRET_KEY'; export const EDITOR_URL = 'https://plugins.stripo.email/resources/uieditor/latest/UIEditor.js'; export const USER_ID = '1'; export const EMAIL_ID = `${PLUGIN_ID}_${USER_ID}_1`; ``` ### Step 4: Create Your First Extension #### Create `src/extension.js`: ```javascript import { ExtensionBuilder } from '@stripoinc/ui-editor-extensions'; // Create your first extension const extension = new ExtensionBuilder() // Add custom styles to change the blocks panel background .addStyles(` .block-thumb { background-color: #33CC4D } `) .build(); export default extension; ``` **What's happening here:** * We import the [ExtensionBuilder](/extensions/reference/core/ExtensionBuilder) from the SDK * We create an extension using the builder pattern for clean, readable code * We add custom CSS styles that will be injected into the editor interface * We build and export the extension for use in the editor initialization ### Step 5: Initialize the Editor #### Create `src/index.js`: ```javascript import extension from './extension.js'; import { PLUGIN_ID, SECRET_KEY, EDITOR_URL, EMAIL_ID, USER_ID } from './creds'; // Load the Stripo editor script dynamically function loadStripoEditor() { const script = document.createElement('script'); script.id = 'UiEditorScript'; script.src = EDITOR_URL; script.type = 'module'; script.onload = _initializeEditor; document.head.appendChild(script); } // Initialize the editor with a demo template function _initializeEditor() { _loadDemoTemplate(template => { _runEditor(template, extension); }); } // Run the editor with the provided template and extension function _runEditor(template, extension) { window.UIEditor.initEditor( document.querySelector('#stripoEditorContainer'), { html: template.html, css: template.css, metadata: { emailId: EMAIL_ID }, locale: 'en', onTokenRefreshRequest: function (callback) { _request('POST', 'https://plugins.stripo.email/api/v1/auth', JSON.stringify({ pluginId: PLUGIN_ID, secretKey: SECRET_KEY, userId: USER_ID, role: 'user' }), function(data) { callback(JSON.parse(data).token); } ); }, codeEditorButtonSelector: '#codeEditor', undoButtonSelector: '#undoButton', redoButtonSelector: '#redoButton', versionHistoryButtonSelector: '#versionHistoryButton', mobileViewButtonSelector: '#mobileViewButton', desktopViewButtonSelector: '#desktopViewButton', extensions: [ extension ] } ); } // Helper function to make HTTP requests function _request(method, url, data, callback) { const req = new XMLHttpRequest(); req.onreadystatechange = function () { if (req.readyState === 4 && req.status === 200) { callback(req.responseText); } else if (req.readyState === 4 && req.status !== 200) { console.error('Cannot complete request. Please check that you have entered valid PLUGIN_ID and SECRET_KEY values'); } }; req.open(method, url, true); if (method !== 'GET') { req.setRequestHeader('content-type', 'application/json'); } req.send(data); } // Load a demo template from GitHub function _loadDemoTemplate(callback) { _request('GET', 'https://raw.githubusercontent.com/ardas/stripo-plugin/master/Public-Templates/Basic-Templates/Trigger%20newsletter%20mockup/Trigger%20newsletter%20mockup.html', null, function(html) { _request('GET', 'https://raw.githubusercontent.com/ardas/stripo-plugin/master/Public-Templates/Basic-Templates/Trigger%20newsletter%20mockup/Trigger%20newsletter%20mockup.css', null, function(css) { callback({ html: html, css: css }); }); }); } // Start loading when the page is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', loadStripoEditor); } else { loadStripoEditor(); } ``` ### Step 6: Add npm Scripts Update your `package.json` with development scripts: ```json { "scripts": { "dev": "vite", "build": "vite build" } } ``` ### Step 7: Run Your Extension Start the development server: ```bash npm run dev ``` Open your browser and navigate to `http://localhost:3000`. **Expected result:** * The Stripo editor loads successfully with your custom styles applied * The blocks panel displays thumbnails with your custom background color * Your extension is properly integrated and functioning ## What You've Accomplished Congratulations! You've successfully: ✅ **Set up a development environment** for Stripo extensions\ ✅ **Created your first extension** using ExtensionBuilder\ ✅ **Added custom styles** to the editor interface\ ✅ **Initialized the editor** with your custom extension\ ✅ **Verified your changes** are reflected in real-time ## Common Issues and Solutions ### Issue: Editor Does Not Load **Possible solutions:** * Ensure the Stripo script URL is accessible and not blocked by network policies * Check the browser console for any JavaScript errors that might prevent loading * Verify that your plugin credentials are correct and valid ### Issue: Styles Do Not Appear **Possible solutions:** * Check for CSS syntax errors in your extension styles * Ensure the extension is properly registered in the editor configuration * Verify that CSS selectors are targeting the correct elements ### Issue: Module Not Found Errors **Possible solutions:** * Run `npm install` to ensure all dependencies are properly installed * Check that import paths are correct and match your file structure * Verify that the Stripo Extensions SDK is installed and up to date --- --- url: https://plugin.stripo.email/extensions/template-modification.md --- # Template Modification System ## Basic Concept Using the API call **this.api.getDocumentModifier()**, you can access the TemplateModifier object. The TemplateModifier provides a controlled way to modify templates: ```javascript this.api.getDocumentModifier() .modifyHtml(immutableNode) // Select what to modify .setInnerHtml('New content') // Define the modification .apply(description); // Apply with tracking ``` ### Modification Flow ``` 1. Get Modifier 2. Select Node 3. Chain Modifications ↓ ↓ ↓ getDocumentModifier() → modifyHtml(node) → setAttribute().setStyle()... ↓ 5. Sync 4. Apply ↓ ↓ Propagate to Users ← apply(ModificationDescription) ``` This flow ensures that all modifications are: * **Tracked** - Each change is recorded for version history * **Atomic** - Related changes are applied together * **Collaborative** - Changes are synchronized across all users * **Reversible** - Full undo/redo support is maintained ## Modification Types ### HTML Modifications The system supports various HTML modifications: #### Content Modifications ```javascript .setInnerHtml(html) // Replace inner HTML .append(html) // Add to end .prepend(html) // Add to beginning .replaceWith(html) // Replace entire element ``` #### Attribute Modifications ```javascript .setAttribute(name, value) // Set attribute .removeAttribute(name) // Remove attribute ``` #### Text Modifications ```javascript .setText(text) // Updates the text content of the HTML text node ``` #### Style Modifications ```javascript .setStyle(property, value) // Set CSS property .removeStyle(property) // Remove CSS property ``` #### Class Modifications ```javascript .setClass(className) // Add CSS class .removeClass(className) // Remove CSS class ``` #### Structural Modifications ```javascript .delete() // Remove current node ``` ### CSS Modifications The system also supports CSS modifications: ```javascript this.api.getDocumentModifier() .modifyCss(immutableCssNode) .setProperty('color', 'blue') // Set a CSS property .setProperty('font-size', '16px') // Set a CSS property .removeProperty('text-decoration') // Remove a CSS property .apply(description); ``` ## MultiRowStructureModifier ### Overview The `MultiRowStructureModifier` is a specialized helper interface for creating and modifying complex email layouts with multiple rows and columns. It provides high-level methods to manage email structure containers, handling the intricate details of email-compatible HTML generation. ### When to Use MultiRowStructureModifier Use the `MultiRowStructureModifier` when you need to: * **Create multi-row custom blocks** - Build responsive custom blocks with multiple structure rows * **Modify existing structures** - Change the layout while preserving content * **Manage container distribution** - Control how content is distributed across containers within the structure ### Why Use MultiRowStructureModifier Traditional email HTML is complex due to: 1. **Table-based layouts** - Email clients require nested tables for consistent rendering 2. **MSO compatibility** - Outlook needs special conditional comments and markup 3. **Responsive challenges** - Mobile and desktop layouts require different approaches The `MultiRowStructureModifier` abstracts these complexities by providing: * **Automatic table structure generation** - No need to manually create complex nested tables * **Built-in MSO/Outlook compatibility** - Handles all necessary conditional comments and markup * **Responsive design handling** - Automatically generates mobile-friendly layouts * **Smart content distribution** - Intelligently places content across containers * **Image scaling and optimization** - Ensures images render correctly across email clients ### Access and Usage Access the `MultiRowStructureModifier` through the `multiRowStructureModifier()` method: ```javascript const modifier = this.api.getDocumentModifier() .modifyHtml(structureNode) .multiRowStructureModifier(); ``` ### Methods #### updateLayoutWithContent Creates a new structure by replacing the current one with specified containers and content. ```javascript // Create a three-column layout with mixed container types modifier.multiRowStructureModifier() .updateLayoutWithContent( [ {width: '25%', contentType: 'EMPTY'}, // Empty placeholder '50%', // Content column 1 '25%' // Content column 2 ], [ `<${BlockType.BLOCK_TEXT}>

Content 1

`, `<${BlockType.BLOCK_TEXT}>

Content 2

` ] ) .apply(new ModificationDescription('Created three-column layout')); ``` **Parameters:** * `layout`: Array of `StructureLayout` defining container widths and types * `containerContent`: Array of HTML strings for content containers **Container Types:** * **Content Container** (string) - Width percentage for content-holding containers * **Empty Container** - `{width: string, contentType: 'EMPTY'}` for placeholders awaiting future content * **Spacer Container** - `{width: string, contentType: 'SPACER'}` for creating empty space #### updateLayout Modifies the container layout of an existing structure while preserving content. ```javascript // Change from two columns to three columns modifier.multiRowStructureModifier() .updateLayout([ '33%', // First column '34%', // Second column '33%' // Third column ]) .apply(new ModificationDescription('Changed to three-column layout')); ``` **Parameters:** * `layout`: New container layout configuration **Behavior:** * **Preserves existing content** when possible during layout changes * **Redistributes content** intelligently among new containers * **Manages content overflow** by automatically creating additional structure beneath the current one when needed ### Best Practices 1. **Plan Your Layout** - Design your container structure before implementation 2. **Use Meaningful Widths** - Ensure widths add up to 100% for proper rendering 3. **Test Content Distribution** - Verify content appears in the correct containers 4. **Leverage Spacers** - Use spacer containers for consistent margins and padding ## Modification Descriptions ### Purpose Every modification requires a description that: * **Documents the change** for comprehensive version history * **Provides context** for other users in collaborative editing * **Enables meaningful labels** for undo/redo operations * **Supports internationalization** for multi-language environments ### Basic Usage ```javascript .apply(new ModificationDescription('Changed text color')); ``` ### With Parameters ```javascript .apply(new ModificationDescription('Changed color to {color}') .withParams({ color: '#ff0000' })); ``` ### Internationalization ```javascript .apply(new ModificationDescription('color_changed') .withParams({ color: '#ff0000' })); // Uses translation key 'color_changed' ``` ## Transaction Model ### Atomic Operations All modifications in a single chain are atomic: ```javascript this.api.getDocumentModifier() .modifyHtml(container) .setStyle('background', 'blue') // All three .setClass('highlighted') // happen .setAttribute('data-modified', 'true') // together .apply(description); ``` ### Benefits of Transactions 1. **Consistency** - All related changes succeed together or fail as a unit 2. **Performance** - Batch processing reduces overhead and improves responsiveness 3. **History** - Single undo/redo operation for logically related changes 4. **Synchronization** - Fewer network requests improve collaborative editing performance ## Collaborative Editing Support ### How It Works When a modification is applied: 1. **Local Application**: Changes apply immediately locally 2. **Serialization**: Modifications are serialized to operations 3. **Transmission**: Operations sent to collaboration server 4. **Transformation**: Server resolves conflicts if needed 5. **Broadcast**: Changes sent to all other users 6. **Application**: Remote changes applied to all clients ### Conflict Resolution The system automatically handles conflicts: ``` User A: Changes heading color to red User B: Changes heading color to blue (simultaneously) Result: Last write wins (User B's change) Both users see: Heading is blue History shows: Both changes with timestamps ``` Complex conflicts are resolved using operational transformation: ``` User A: Inserts text at position 10 User B: Deletes text at position 5-8 System transforms User A's operation: Original: Insert at position 10 Transformed: Insert at position 7 (adjusted for deletion) ``` ## Version History Integration ### Automatic Tracking Every modification is automatically tracked: ```javascript // This single modification creates a history entry this.api.getDocumentModifier() .modifyHtml(element) .setInnerHtml('Updated content') .apply(new ModificationDescription('Updated welcome message')); // Version history shows: // - Timestamp // - "Updated welcome message" // - Username // - Restore option ``` ### Undo/Redo Support The modification system provides built-in undo/redo: ```javascript // User performs modification modifier.apply(description); // User clicks undo // System automatically reverses the modification // User clicks redo // System reapplies the modification ``` ## Performance Considerations ### Batching Strategies Batch related modifications for better performance: ```javascript // Good - Single transaction const modifier = this.api.getDocumentModifier(); elements.forEach(el => { modifier.modifyHtml(el).setStyle('color', 'blue'); }); modifier.apply(description); // Avoid - Multiple transactions elements.forEach(el => { this.api.getDocumentModifier() .modifyHtml(el) .setStyle('color', 'blue') .apply(description); }); ``` ### Debouncing For high-frequency updates (like color pickers): ```javascript let modificationTimeout; onColorChange(color) { clearTimeout(modificationTimeout); modificationTimeout = setTimeout(() => { this.applyColorChange(color); }, 100); } ``` ## Best Practices ### 1. Use Descriptive Messages ```javascript // Good - specific and informative .apply(new ModificationDescription('Changed button color to match brand')); // Avoid - too vague .apply(new ModificationDescription('Updated')); ``` ### 2. Group Related Changes Logically ```javascript // Good - related changes in a single transaction modifier .modifyHtml(button) .setStyle('background', color) .setStyle('border-color', darkenColor(color)) .apply(description); // Avoid - unrelated changes in the same transaction modifier .modifyHtml(button) .setStyle('background', color) .modifyHtml(heading) // Different element - should be separate .setInnerHtml(title) .apply(description); ``` ### 3. Minimize Unnecessary Modifications ```javascript // Good - only modify when values actually change if (newValue !== oldValue) { modifier.modifyHtml(element) .setAttribute('data-value', newValue) .apply(description); } // Avoid - modifying even when values haven't changed modifier.modifyHtml(element) .setAttribute('data-value', newValue) // Wasteful if unchanged .apply(description); ``` ### 4. Use Appropriate Methods for Each Task ```javascript // Good - use specialized methods for their intended purpose modifier.modifyHtml(element) .setDisplayCondition(condition) // For visibility control .setHiddenElementState('mobile') // For device-specific hidden state .setNodeConfig(widgetConfig) // For custom data storage .apply(description); // Avoid - misusing generic attributes for specialized functionality modifier.modifyHtml(element) .setAttribute('data-config', JSON.stringify(config)) // Use setNodeConfig instead .apply(description); ``` ## Summary The template modification system provides: * **Safety** - Immutable nodes prevent direct manipulation and ensure data integrity * **Tracking** - All changes are recorded and attributed for complete audit trails * **Collaboration** - Automatic conflict resolution and real-time synchronization * **History** - Built-in undo/redo functionality with comprehensive version tracking * **Performance** - Batched operations and optimized synchronization for scalability * **Advanced Features** - Display conditions, hidden element state, and node configurations for complex use cases * **Layout Management** - MultiRowStructureModifier for sophisticated email structures This system is fundamental to creating reliable, collaborative extensions that maintain template integrity while providing powerful modification capabilities. ## See Also * [How to Handle Template Modifications](/extensions/tutorials/how-to/template-modifications) * [TemplateModifier Reference](/extensions/reference/modification/TemplateModifier) --- --- url: https://plugin.stripo.email/extensions/components/block.md --- # Block Component ## Overview ::: image-wrap ![](/img/extensions/stripo_extensions_components.png) ::: The Block component is the fundamental building unit of the Stripo Email Editor extension system. It represents a piece of content that users can drag and drop into their email templates. Blocks can range from simple text elements to complex, interactive structures that contain other blocks. The Block component provides a powerful abstraction layer that enables developers to create custom email content while maintaining compatibility with the editor's collaborative features and template management system. ## Purpose and Core Concepts ### What is a Block? A Block in the Stripo Extensions SDK is a self-contained component that: * Defines a reusable piece of email content * Appears in the editor's blocks panel for drag-and-drop functionality * Can contain HTML structure, styling, and behavior * Integrates seamlessly with the editor's undo/redo, collaboration, and export features * Maintains state and responds to user interactions through lifecycle hooks ### Block Composition Types Blocks are categorized into three fundamental types based on their composition: 1. **Atomic Blocks** (`BlockCompositionType.BLOCK`) * Self-contained units that cannot contain other blocks * Examples: buttons, images, text paragraphs, videos, social icons * Can be inserted into containers 2. **Container Blocks** (`BlockCompositionType.CONTAINER`) * Container blocks that can hold other blocks * Examples: product cards * Can be inserted into structures 3. **Structure Blocks** (`BlockCompositionType.STRUCTURE`) * Hold containers with blocks inside them * Examples: multi-column layouts, product lists * Can be inserted into stripes ### The Immutable Architecture One of the key [architectural decisions](/extensions/core-concepts) in the Stripo Extensions SDK is the use of immutable nodes. When working with blocks: * You cannot directly modify HTML or CSS properties * All template modifications must go through the `TemplateModifier` API * This ensures proper synchronization in collaborative editing sessions * Changes are tracked, versioned, and can be undone/redone ## Key Features and Capabilities ### 1. Customizable Appearance Blocks provide complete control over their visual representation: * Custom icons for the blocks panel * Localized names and descriptions * Custom CSS classes for styling * Support for responsive design patterns ### 2. Lifecycle Management Blocks offer comprehensive lifecycle hooks that enable sophisticated behaviors: * **Document initialization** - Set up initial state when a document loads * **Selection events** - React when users select your block * **Creation events** - Initialize new block instances * **Copy operations** - Handle duplication logic * **Deletion events** - Clean up resources or update state * **Document changes** - Monitor and respond to template modifications ### 3. Custom Rendering The Block component supports custom renderers that can: * Display content differently in the editor vs. the exported email * Show merge tags with preview values * Add visual indicators for dynamic content * Create interactive editing experiences ### 4. Context Actions Blocks can define custom context menu actions: * Override default actions (copy, move, delete) * Add custom actions specific to your block * Control action behavior * Integrate with external services ### 5. Module Support Blocks of types STRUCTURE or CONTAINER can be configured to work as reusable modules: * Share modules across templates * Control whether your block can be saved as a module ### 6. Advanced Interaction Controls Fine-grained control over user interactions: * Enable/disable block based on editor state * Control inner block selection in structures and containers * Configure quick-add icons for empty containers ## Creating Custom Blocks ### Basic Configuration To create a custom block, there are two steps: 1. Create a class that extends the `Block` class 2. Register your block with the ExtensionBuilder Minimal block configuration requires the following settings: * Block identifier * Block name * Block description * Block icon * Block initial template ```javascript import {Block, ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; import blockIcon from './icons/block.svg'; class SimpleBlock extends Block { getId() { return 'simple-block'; } getIcon() { return blockIcon; } getName() { return 'Greetings' } getDescription() { return 'User greeting'; } getTemplate() { return ` Hello, user ` } } export default new ExtensionBuilder() .addBlock(SimpleBlock) .build(); ``` ### Advanced Configuration There are several advanced configuration options that can be used to customize the block's behavior. #### Block Composition Type The composition type can be one of the following: * `BlockCompositionType.BLOCK` - simple block * `BlockCompositionType.CONTAINER` - container with blocks inside * `BlockCompositionType.STRUCTURE` - structure with containers inside ```javascript import {Block, BlockCompositionType} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { getBlockCompositionType() { return BlockCompositionType.STRUCTURE; } // Other block configuration... } ``` #### Block Unique Class Name By default, the block's class name is generated based on the block's identifier and has the value: `esd-${this.getId()}`. Usually, this is sufficient, but in some cases, it may be necessary to override the default behavior. This can be overridden by implementing the `getUniqueBlockClassname` method: ```javascript import {Block} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { getUniqueBlockClassname() { return 'simple-block'; } // Other block configuration... } ``` #### Enable/Disable Block By default, the block is enabled and visible in the blocks panel. This can be overridden by implementing the `isEnabled` method. ```javascript import {Block} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { isEnabled() { return false; } // Other block configuration... } ``` #### Save as a Module Containers and structures can be saved to the modules library. By default, this is disabled but can be enabled by implementing the `canBeSavedAsModule` method. ```javascript import {Block} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { canBeSavedAsModule() { return true; } // Other block configuration... } ``` #### Quick-Add Block Icon Empty containers contain quick-add icons that allow users to quickly add a new block to the container. Your custom block icon can be added to the quick-add icons array with the `shouldDisplayQuickAddIcon` method. ```javascript import {Block} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { shouldDisplayQuickAddIcon() { return true; } // Other block configuration... } ``` #### Interaction with Internal Blocks When you create a block of type STRUCTURE or CONTAINER, you can control the behavior of the inner blocks. By default: * Inner blocks like Text, Button, and Image are selectable * Other blocks can be dragged and dropped into the container This behavior can be overridden by implementing the `allowInnerBlocksSelection` and `allowInnerBlocksDND` methods: ```javascript import {Block} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { allowInnerBlocksSelection() { return false; } allowInnerBlocksDND() { return false; } // Other block configuration... } ``` ### Template Aliases To ensure correct email rendering across various email clients, Stripo uses specially adapted template markup. To save extension developers from having to learn the standard block markup, special aliases have been introduced. When a block is created, these aliases are automatically converted into the correct markup. Currently, the following types of aliases are supported in the `BlockType` enum: * BlockType.BLOCK\_IMAGE - alias for image block * BlockType.BLOCK\_TEXT - alias for text block * BlockType.BLOCK\_BUTTON - alias for button block * BlockType.CONTAINER - alias for container block * BlockType.EMPTY\_CONTAINER - alias for empty container block * BlockType.STRUCTURE - alias for structure block [//]: # "Read the [Template Aliases](/extensions/core-concepts/template-aliases) documentation for more information." ### Lifecycle Hooks Blocks can define lifecycle hooks that enable sophisticated behaviors: #### Document Initialization Called when the editor document is initialized. You can perform any template checks here and modify the template if necessary. Here is an example of a hook to ensure only one instance of the block exists in the template: ```javascript import {Block, BlockType, ModificationDescription} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { onDocumentInit() { const blocks = this.api.getDocumentRoot() .querySelectorAll(`.${this.getUniqueBlockClassname()}`); this.api.setViewOnly(!!blocks.length); if (blocks.length > 1) { const modifier = this.api.getDocumentModifier(); for (let i = 1; i < blocks.length; i++) { modifier.modifyHtml(blocks[i]).replaceWith(`<${BlockType.EMPTY_CONTAINER}/>`); } modifier.apply(new ModificationDescription('Removed extra blocks on init')); } } // Other block configuration... } ``` #### Selection Events Called when the user selects the block in the editor. Here is an example of a hook to interact with the user when a block is selected: ```javascript import {Block, ModificationDescription} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { onSelect(node) { const message = window.prompt(`Hello, dear ${this.api.getEditorConfig().metadata?.username || 'user'}. \nProvide a prompt message:`, ''); if (message) { this.api.getDocumentModifier() .modifyHtml(node.querySelector('p')) .setInnerHtml(message) .apply(new ModificationDescription(`Set prompt message to ${message}`)); } } // Other block configuration... } ``` #### Creation Events Called when the user creates a new block instance in all cases: * When the user drags a block from the blocks panel * When the user drags a block from the modules library * When the user creates a block from the code editor Here is an example of a hook to replace all block call-to-action links with a link to your website: ```javascript import {Block, ModificationDescription} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { onCreated(node) { this.api.getDocumentModifier() .modifyHtml(node.querySelector('a')) .setAttribute('href', 'https://stripo.email') .apply(new ModificationDescription('Set link href to https://stripo.email')); } // Other block configuration... } ``` #### Copy Operations Called when the user copies a block instance. This can be useful if your block needs to include unique data and, after copying, you need to reconfigure the copied block. Here is an example of a hook to copy a block and reset the 'event-id' attribute on the copied block link: ```javascript import {Block, ModificationDescription} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { onCopy(targetNode, sourceNode) { if (!!targetNode.querySelector('a').getAttribute('event-id')) { this.api.getDocumentModifier() .modifyHtml(targetNode.querySelector('a')) .removeAttribute('event-id') .apply(new ModificationDescription('Removed eventID attribute on copied block')); } } // Other block configuration... } ``` #### Deletion Events Called when the user deletes a block instance. Here is an example of a hook to remove extra CSS code from the template related to the deleted block: ```javascript import {Block, ModificationDescription} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { onDelete(node) { this.api.getDocumentModifier() .modifyCss(this.api.getDocumentRootCssNode().querySelector('.simple-block-instance-2')) .removeRule() .apply(new ModificationDescription('Removed simple-block CSS class')); } // Other block configuration... } ``` #### Document Changes Called when the user modifies the template. Here is an example of a hook to add a summary block to the template: ```javascript import {Block, ModificationDescription} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { onDocumentChanged(node) { const templateContent = this.api.getDocumentRoot().querySelectorAll('.esd-block-text') .map(node => node.getInnerHTML()) .join('\n\n\n'); const summary = templateContent.substring(0, 100); // Put your AI summary logic here this.api.getDocumentModifier() .modifyHtml(node) .setInnerHtml("

Summary: " + summary + "

") .apply(new ModificationDescription('Updated template summary')); } // Other block configuration... } ``` ### Custom Rendering It is often necessary for the template markup to differ from what the user sees in the editor. For example: * The block's markup may be completely empty (``), but visually the block displays a message prompting the user to set up the initial configuration * The block's markup may include merge tags, but on the screen, real text and images should be shown To enable this, a system called the Block Renderer was created. In practice, the `BlockRenderer` class contains only one method, `getPreviewInnerHtml`, which takes the actual block node and returns the visual representation of the block as an HTML string. Example: ```javascript import {Block, BlockRenderer} from '@stripoinc/ui-editor-extensions'; class SimpleBlockRenderer extends BlockRenderer { getPreviewInnerHtml(node) { return node.getInnerHTML().replace(`#{NAME}`, this.api.getEditorConfig().metadata?.username || 'user'); } } export class SimpleBlock extends Block { getTemplate() { return ` Hello, #{NAME} ` } getCustomRenderer() { return SimpleBlockRenderer; } // Other block configuration... } ``` Here's a more complex example where the visual representation of the block depends on the block's node configuration: ```javascript import {Block, BlockRenderer, Control} from '@stripoinc/ui-editor-extensions'; class BlockConfigurationControl extends Control { onRender() { this.api.onValueChanged('configurationText', (newValue, oldValue) => { this.api.getDocumentModifier() .modifyHtml(this.currentNode) .setInnerHtml(`

${newValue}

`) .setNodeConfig({initialized: true}) .apply(new ModificationDescription(`Block initialized with ${newValue}`)); }); } // Other control configuration... } class SimpleBlockRenderer extends BlockRenderer { getPreviewInnerHtml(node) { if (node.getNodeConfig().initialized) { return node.getInnerHTML(); } else { return `
Please, complete block configuration
` } } } export class SimpleBlock extends Block { getTemplate() { return ` ` } getCustomRenderer() { return SimpleBlockRenderer; } // Other block configuration... } ``` **IMPORTANT**: When using custom renderers for blocks of type `STRUCTURE` or `CONTAINER`, inner blocks are not available for selection and drag & drop. ### Context Actions Every block has default context actions: copy, move, and delete. You can reorder existing actions or add new ones specific to your block. To do this, you need to: 1. Create a class that extends the `ContextAction` superclass 2. Register your action class with the ExtensionBuilder 3. Add your action ID to the `getContextActionsIds` method of your block Here is an example where a new context action is created. Also, the `delete` action is no longer available for this type of block: ```javascript import {Block, ContextAction, ContextActionType, ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; import magicIcon from './icons/magic.svg'; class MagicContextAction extends ContextAction { getId() { return 'magic-context-action'; }; getIconClass() { return magicIcon; } getLabel() { return 'Magic'; } onClick(node) { alert(`Magic action clicked. Block content: ${node.getInnerHTML()}`); } } class SimpleBlock extends Block { getContextActionsIds() { return [ ContextActionType.COPY, ContextActionType.MOVE, 'magic-context-action' ]; } // Other block configuration... } export default new ExtensionBuilder() .addBlock(SimpleBlock) .addContextAction(MagicContextAction) .build(); ``` ## Block API The Block API allows you to access: * Block behavior management * The internationalization system * The template modification system * Editor configuration * Editor state and subscription to its changes * Custom font management * Handling mouse clicks outside the block * Managing Emoji and AI popovers You can find more practical examples in the section [Tutorials. Working with Components APIs](/extensions/tutorials/how-to/working-with-apis). --- --- url: https://plugin.stripo.email/extensions/components/settings-panel.md --- # Settings Panel ## Overview ::: image-wrap ![](/img/extensions/stripo_extensions_components.png) ::: The Settings Panel component is the central hub for block configuration in the Stripo Email Editor. It provides a structured interface where users can modify block properties, adjust styling, and configure behavior. Through the [SettingsPanelRegistry](/extensions/reference/settings-panel/SettingsPanelRegistry), developers can customize which controls appear for specific blocks, organize controls into logical tabs, and create intuitive configuration experiences that match their business requirements. ## Purpose and Core Concepts ### What is a Settings Panel? The Settings Panel in the Stripo Extensions SDK is a customizable configuration interface that: * Is displayed when users select blocks in the editor * Organizes controls into logical tabs (Settings, Styles, Data, etc.) * Can be customized per block type — both built-in and custom * Maintains consistency across the editor while allowing deep customization * Adapts to different block types automatically ### Settings Panel Registry The [SettingsPanelRegistry](/extensions/reference/settings-panel/SettingsPanelRegistry) is the central mechanism for customizing settings panels: * Define which controls appear for specific blocks * Organize controls into tabs with custom labels * Add, remove, or reorder controls * Extend built-in blocks with custom controls * Create completely custom settings interfaces ### Settings Panel Tabs Controls are organized into tabs using [SettingsPanelTab](/extensions/reference/settings-panel/SettingsPanelTab) instances: * Each tab has a unique identifier and label * Controls within tabs are ordered sequentially * Standard tabs (Settings, Styles, Data) provide consistency * Custom tabs can be created for specialized workflows ## Key Features and Capabilities ### 1. Per-Block Customization Settings panels can be tailored to specific block types: * Different controls for text blocks vs. image blocks * Custom configurations for extension blocks * Complete control over built-in block settings ### 2. Tab Organization Controls are organized into intuitive tab groups: * Standard tabs (`SettingsTab.SETTINGS`, `SettingsTab.STYLES`, `SettingsTab.DATA`) * Custom tabs with localized labels ### 3. Dynamic Control Management Fine-grained control over which controls appear: * Add custom controls to existing tabs * Remove unwanted built-in controls * Reorder controls within tabs * Conditional control visibility based on state ### 4. Localization Support Full internationalization capabilities: * Translate tab labels using the `api.translate()` method * Support for all editor languages * Fallback to default labels when translations are missing ## Creating Custom Settings Panels ### Basic Configuration To customize a settings panel: 1. Create a class that extends `SettingsPanelRegistry`. 2. Implement the `registerBlockControls()` method in your class: * Use the block ID as the key in the `controls` map. * Add one or more `SettingsPanelTab` objects to define the tabs you want. * For each `SettingsPanelTab`, specify a tab identifier (name) and an ordered list of control IDs to include in that tab. 3. Register your registry with the `ExtensionBuilder`. Minimal configuration for a custom block: ```javascript import {SettingsPanelRegistry, SettingsPanelTab, SettingsTab, ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; class CustomBlockSettingsRegistry extends SettingsPanelRegistry { registerBlockControls(controls) { // Add settings for your custom block controls['my-custom-block'] = [ new SettingsPanelTab( SettingsTab.SETTINGS, [ 'background-color-control', 'padding-control' ]) ]; } } export default new ExtensionBuilder() .withSettingsPanelRegistry(CustomBlockSettingsRegistry) .build(); ``` **Note:** If all controls within a Settings Panel Tab are hidden, the tab itself will automatically be hidden as well. For a comprehensive guide to configuration scenarios and advanced use cases, refer to the [Configure the Settings Panel](/extensions/tutorials/how-to/settings-panel) tutorial. --- --- url: https://plugin.stripo.email/extensions/components/ui-element.md --- # UI Element ## Overview ::: image-wrap ![](/img/extensions/stripo_extensions_components.png) ::: UI Elements are the foundational building blocks for creating custom user interface components in the Stripo Email Editor. They enable you to extend the editor's interface with custom inputs, pickers, dropdowns, and specialized interactive components that go beyond standard HTML form elements. UI Elements can be embedded within controls and settings panels to provide rich, interactive functionality tailored to your specific needs. ## Purpose and Core Concepts ### What is a UI Element? A UI Element in the Stripo Extensions SDK is a reusable UI component that: * Defines custom interactive interface elements (color pickers, dropdowns, custom inputs, etc.) * Can be embedded within controls * Maintains state and responds to external attribute changes * Integrates seamlessly with the editor's control system * Provides lifecycle hooks for initialization and cleanup * Communicates value changes to parent controls ### Built-in vs Custom UI Elements The SDK provides a rich set of built-in UI elements for common use cases, but you can also create custom UI elements when you need specialized functionality: **Built-in UI Elements** - Ready to use without additional configuration: * Buttons, checkboxes, radio buttons * Text inputs, textareas, date pickers * Color pickers, font family selectors * Dropdown selects, switchers, counters * Labels, messages, icons * And more (see the [Built-in UI Elements table](#built-in-ui-elements-reference)) **Custom UI Elements** - When built-in elements don't meet your needs: * Brand-specific color palette pickers * Advanced file uploaders with preview * Custom merge tag selectors * Specialized input validators * Integration with external services * Complex multi-field components ## Creating Custom UI Elements To create a custom UI element, follow these steps: 1. Create a class that extends the `UIElement` class 2. Register your UI element with the `ExtensionBuilder` UI element configuration requires: * Unique element identifier (`getId()`) * This identifier is used as a tag name in control templates * It must be unique across all UI elements in the extension * HTML template structure (`getTemplate()`) * Render logic (`onRender()`) * Register all event listeners and other UI element-specific logic here * Cleanup logic (`onDestroy()`) * Remove all event listeners and other UI element-specific logic here * Getter and setter methods to maintain UI element state from parent control * (Optional) Attribute update handler (`onAttributeUpdated()`) * This method is called when an attribute of the UI element is updated from the parent control Once registered, you can use your custom UI element in control templates by referencing its ID: ```javascript import {UIElement, ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; class BrandColorPickerUIElement extends UIElement { getId() { return 'brand-color-picker'; } getTemplate() { return `
`; } onRender(container) { this.palette = container.querySelector('.brand-color-picker-palette'); this.palette.addEventListener('click', this.paletteClickHandler.bind(this)); } onDestroy() { this.palette.removeEventListener('click', this.paletteClickHandler.bind(this)); } paletteClickHandler(e) { const selectedColor = e.target.getAttribute('data-value'); // Trigger value change to parent control. // The 'setValue' method will be called automatically. this.api.triggerValueChange(selectedColor); } getValue() { return this.palette.querySelector('button.selected')?.getAttribute('data-value'); } setValue(_value) { this.palette.querySelector('button.selected')?.classList.remove('selected'); this.palette.querySelector(`button[data-value="${_value}"]`)?.classList.add('selected'); } onAttributeUpdated(_name, _value) { // Handle attribute updates from parent control console.log(`Attribute updated: ${_name} = `, _value); } } class BrandControl extends Control { getId() { return 'brand-control'; } getTemplate() { return ``; } onRender() { this.api.onValueChanged('brandColorPicker', (newValue, oldValue) => { // Handle value changes from 'brandColorPicker' UI element console.log('brandColorPicker value changed:', newValue); }); } onTemplateNodeUpdated(node) { // Update UI to reflect template state this.api.updateValues({ 'brandColorPicker': '#43e97b' // Get actual value from node }); // Optionally, set UI element state with attribute values this.api.setUIEAttribute('brandColorPicker', 'preferred-color', '#53f97a'); } } export default new ExtensionBuilder() .addUiElement(BrandColorPickerUIElement) .addControl(BrandControl) .build(); ``` ## Built-in UI Elements ### Supported Built-in UI Elements The following image and table list all built-in UI elements available in the Stripo Extensions SDK: ![](/img/extensions/ui-elements.png) | Tag Name | Alias | Description | Common Attributes | |---------------------------|-------------------------------|------------------------------------------------|----------------------------------------------------------------------------------------------------------------| | `` | `UIElementType.BUTTON` | Button element for triggering actions | `name`, `disabled`, `caption`, `icon` | | `` | `UIElementType.CHECK_BUTTONS` | Group of checkboxes as buttons | `name`, `disabled`, `buttons` | | `` | `UIElementType.CHECKBOX` | Checkbox input for boolean values | `name`, `disabled`, `caption` | | `` | `UIElementType.COLOR` | Color picker for selecting colors | `name`, `disabled` | | `` | `UIElementType.COUNTER` | Numeric input with increment/decrement buttons | `name`, `disabled`, `min-value`, `max-value`, `step` | | `` | `UIElementType.DATEPICKER` | Date picker for selecting dates | `name`, `disabled`, `placeholder`, `min-date` | | `` | `UIElementType.AMP_FORM_SERVICE_PICKER` | AMP form service picker | `name`, `disabled` | | `` | `UIElementType.EXPANDABLE` | Expandable/collapsible container | `name`, `expanded` | | `` | `UIElementType.ICON` | Icon display element | `name`, `img`, `src`, `title`, `width`, `height`, `image-class`, `hint`, `is-active`, `visibility`, `transform` | | `` | `UIElementType.LABEL` | Text label with optional hint | `name`, `text`, `hint` | | `` | `UIElementType.MESSAGE` | Message box for displaying information | `name`, `type`, `icon` | | `` | `UIElementType.MULTIPLE_SELECT` | Multiple select input | `name`, `disabled`, `placeholder` | | `` | `UIElementType.NESTED_CONTROL`| Container for nesting other controls | `name`, `disabled`, `control-id` | | `` | `UIElementType.ORDERABLE` | Reorderable list container | `name`, `icon`, `position` | | `` | `UIElementType.RADIO_BUTTONS` | Radio button group | `name`, `disabled`, `buttons` | | `` | `UIElementType.REPEATABLE` | Repeatable list container for dynamic items | `name` | | `` | `UIElementType.DRAGGABLE_BLOCK` | Draggable block selector element | `name`, `disabled`, `block-id` | | `` | `UIElementType.SCROLLABLE` | Scrollable container wrapper | | | `` | `UIElementType.SELECTPICKER` | Dropdown select picker | `name`, `disabled`, `searchable`, `multi-select`, `placeholder`, `items` | | `` | `UIElementType.SWITCHER` | Toggle switcher for boolean values | `name`, `disabled` | | `` | `UIElementType.TEXT` | Single-line text input | `name`, `disabled`, `placeholder` | | `` | `UIElementType.TEXTAREA` | Multi-line text input | `name`, `disabled`, `resizable`, `placeholder` | ### Using Built-in UI Elements All built-in UI elements can be used directly in your control templates without additional registration by referencing their tag names or aliases: ### Simple Usage ```javascript class ComprehensiveControl extends Control { getId() { return 'comprehensive-control'; } getTemplate() { return `
<${UIElementType.LABEL} ${UEAttr.LABEL.text}=""> <${UIElementType.COLOR} ${UEAttr.COLOR.name}="colorPicker">
`; } onRender() { // Listen for 'colorPicker' UI element value changes this.api.onValueChanged('colorPicker', (newValue, oldValue) => { this.api.getDocumentModifier() .modifyHtml(this.node.querySelector(`.custom-message-area`)) .setStyle('background-color', newValue) .apply(new ModificationDescription(`Updated background color to ${newValue}`)); }) } onTemplateNodeUpdated(node) { this.node = node; // Set the value for 'colorPicker' UI element const element = node.querySelector('.custom-message-area'); this.api.updateValues({ 'colorPicker': node.querySelector(`.custom-message-area`).getStyle('background-color') }); } } ``` ### Complex Usage #### Expandable UI Element Expandable UI Elements allow you to group multiple UI elements inside a collapsible section, making complex forms more organized and user-friendly. An expandable UI element is composed of three main parts: * `` — The main wrapper for the entire collapsible section * `` — The header area, typically containing the title, placed next to the expand/collapse toggle * `` — The container for the content that is shown or hidden when expanded or collapsed ```javascript class ExpandableControl extends Control { getTemplate() { return `
<${UIElementType.EXPANDABLE}> <${UIElementType.EXPANDABLE_HEADER}> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Click to expand"> <${UIElementType.EXPANDABLE_CONTENT}> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="First content row"> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Another row">
`; } // Additional configuration should be here } ``` #### Orderable UI Element The Orderable UI Element allows users to reorder items within a list by dragging and dropping them. This component is useful when you need to provide a visual interface for changing the sequence of elements, such as reordering menu items, social links, or content blocks. An orderable component is composed of three main parts: * `` — The main wrapper that manages the reorderable list * `` — Individual items that can be reordered * `` — (Optional) Custom drag handle icon for individual items Attributes **`` attributes:** * `name` — (Required) Unique identifier for the orderable element * `icon` — (Optional) Icon name for the drag handle (default: system icon) * `position` — (Optional) Position of the drag handle icon. Values: `TOP`, `LEFT` (default: `TOP`) **`` attributes:** * `name` — (Optional) Unique identifier for the item. If not provided, items will be automatically named as `item1`, `item2`, etc. **`` attributes:** * `icon` — (Required) Icon name to override the parent's drag handle icon for this specific item Value Format The orderable element returns an array of item names representing their current order: ```typescript // Example value when items are reordered ['item2', 'item1', 'item3'] ``` Usage Examples **Example 1: Default Icon with Top Positioning** Use the default drag handle icon positioned at the top of each item. Items without an explicit `name` attribute will be automatically named as `item1`, `item2`, etc. ```javascript class OrderableControl extends Control { getTemplate() { return `
<${UIElementType.ORDERABLE} name="orderableUIElement"> <${UIElementType.ORDERABLE_ITEM}> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Item 1"> <${UIElementType.ORDERABLE_ITEM}> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Item 2">
`; } onRender() { this.api.onValueChanged('orderableUIElement', (newValue, oldValue) => { // oldValue -> ['item1', 'item2'] // newValue -> ['item2', 'item1'] console.log('Items reordered:', newValue); }); } // Additional configuration should be here } ``` **Example 2: Custom Icon with Named Items** Use a custom icon (`dots-3`) for the drag handle while explicitly naming each item for better tracking: ```javascript class OrderableControl extends Control { getTemplate() { return `
<${UIElementType.ORDERABLE} name="orderableUIElement" icon="dots-3"> <${UIElementType.ORDERABLE_ITEM} name="orderableItem1"> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Item 1"> <${UIElementType.ORDERABLE_ITEM} name="orderableItem2"> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Item 2">
`; } onRender() { this.api.onValueChanged('orderableUIElement', (newValue, oldValue) => { // oldValue -> ['orderableItem1', 'orderableItem2'] // newValue -> ['orderableItem2', 'orderableItem1'] console.log('Items reordered:', newValue); }); } // Additional configuration should be here } ``` **Example 3: Left-Positioned Icon with Per-Item Customization** Position the drag handle on the left side and override the icon for specific items: ```javascript class OrderableControl extends Control { getTemplate() { return `
<${UIElementType.ORDERABLE} name="orderableUIElement" icon="dots-3" position="LEFT"> <${UIElementType.ORDERABLE_ITEM} name="orderableItem1"> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Item 1"> <${UIElementType.ORDERABLE_ITEM} name="orderableItem2"> <${UIElementType.ORDERABLE_ICON} icon="dots-6"> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Item 2">
`; } onRender() { this.api.onValueChanged('orderableUIElement', (newValue, oldValue) => { // oldValue -> ['orderableItem1', 'orderableItem2'] // newValue -> ['orderableItem2', 'orderableItem1'] console.log('Items reordered:', newValue); }); } // Additional configuration should be here } ``` Key Concepts * **Automatic Naming**: If items don't have a `name` attribute, they're automatically assigned names like `item1`, `item2`, etc. * **Icon Positioning**: The `position` attribute controls where the drag handle appears (`TOP` or `LEFT`) * **Per-Item Icons**: Use `` within an item to override the parent's drag handle icon * **Value Tracking**: The component's value is always an array of item names in their current order * **Nested Content**: Items can contain any combination of UI elements, not just labels #### Nested UI Element The Nested UI Element allows you to embed one control inside another control's template. This enables powerful composition patterns such as grouping multiple controls within an Expandable section or creating dynamic lists of controls with the Orderable component. **Attributes:** * `control-id` — (Required) The unique identifier of the control to be nested **Key Use Cases:** * Grouping multiple controls in expandable sections * Creating reorderable lists of controls * Building complex, hierarchical control layouts **Usage Example: Nested Control in Expandable Section** ```javascript class ExpandableNestedControl extends Control { getId() { return 'expandable-nested-control'; } getTemplate() { return `
<${UIElementType.EXPANDABLE}> <${UIElementType.EXPANDABLE_HEADER}> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Advanced Settings"> <${UIElementType.EXPANDABLE_CONTENT}> <${UIElementType.NESTED_CONTROL} ${UEAttr.NESTED_CONTROL.controlId}="my-custom-control-1-id"> <${UIElementType.NESTED_CONTROL} ${UEAttr.NESTED_CONTROL.controlId}="my-custom-control-2-id">
`; } // Additional configuration should be here } ``` **Usage Example: Orderable List of Controls** ```javascript class OrderableNestedControlList extends Control { getId() { return 'orderable-nested-list'; } getTemplate() { return `
<${UIElementType.ORDERABLE} name="controlsList"> <${UIElementType.ORDERABLE_ITEM} name="item1"> <${UIElementType.NESTED_CONTROL} ${UEAttr.NESTED_CONTROL.controlId}="my-custom-control-1-id"> <${UIElementType.ORDERABLE_ITEM} name="item2"> <${UIElementType.NESTED_CONTROL} ${UEAttr.NESTED_CONTROL.controlId}="my-custom-control-2-id">
`; } onRender() { this.api.onValueChanged('controlsList', (newValue, oldValue) => { // Handle reordering of nested controls console.log('Controls reordered:', newValue); }); } // Additional configuration should be here } ``` #### Repeatable UI Element :::tip Version Availability This element is available starting from v3.5.0 ::: The Repeatable UI Element allows you to dynamically repeat a section of UI elements N times, creating dynamic forms and lists. This is particularly useful for building interfaces where users need to manage multiple similar items, such as filter groups, gallery items or any collection of structured data. **Attributes:** * `name` — (Required) Unique identifier for the repeatable element **Key Features:** * Dynamic creation/removal of repeated sections * Each repeated item maintains its own state * Flexible value addressing for individual items or all items Value Format The repeatable element returns an array of objects, where each object contains the values of all UI elements within that repeated section: ```typescript // Example value structure [ { text: 'one', text2: 'first' }, { text: 'two', text2: 'second' }, { text: 'three', text2: 'third' } ] ``` API Methods The Repeatable UI Element supports several addressing patterns for interacting with values: **1. Listen to all items changes:** ```javascript this.api.onValueChanged('items', (newValue, oldValue) => { // newValue = [ // { text: 'one', text2: 'first' }, // { text: 'two', text2: 'second' } // ] }); ``` **2. Listen to a specific field across all items:** ```javascript this.api.onValueChanged('items.text2', (newValue, oldValue) => { // newValue = { // idx: 2, // index of the changed item (0-based) // value: 'updated value' // } }); ``` **3. Listen to a specific field in a specific item:** ```javascript this.api.onValueChanged('items[1].text2', (newValue, oldValue) => { // newValue = 'updated value' }); ``` **4. Update all items at once:** ```javascript this.api.updateValues({ items: [ { text: 'one', text2: 'first' }, { text: 'two', text2: 'second' } ] }); ``` **5. Update a specific field in a specific item:** ```javascript this.api.updateUIElementValue('items[0].text', 'updated value'); ``` **6. Set attributes on specific items:** ```javascript // Disable a specific field in a specific item this.api.setUIEAttribute('items[0].text2', 'disabled', true); // Disable a field across all repeated items this.api.setUIEAttribute('items.text2', 'disabled', true); ``` Usage Examples **Example 1: Simple Repeatable List** ```javascript class SimpleRepeatableControl extends Control { getId() { return 'simple-repeatable-control'; } getTemplate() { return `
<${UIElementType.REPEATABLE} name="items"> <${UIElementType.TEXT} ${UEAttr.TEXT.name}="text"> <${UIElementType.TEXT} ${UEAttr.TEXT.name}="text2">
`; } onRender() { // Initialize with data this.api.updateValues({ items: [ { text: 'First', text2: 'Item 1' }, { text: 'Second', text2: 'Item 2' } ] }); // Listen for changes this.api.onValueChanged('items', (newValue, oldValue) => { console.log('Items updated:', newValue); }); } } ``` **Example 2: Dynamic Filter Builder** A comprehensive example showing a filter builder with multiple filter groups: ```javascript import { Control, UEAttr, UIElementType } from '@stripoinc/ui-editor-extensions'; class FilterBuilderControl extends Control { FILTER_GROUPS_DATA = [ { name: 'Filter Group 1', filters: [ { type: 'standard', attribute: 'item_id', operator: 'is_exactly', value: '' }, { type: 'standard', attribute: 'item_id', operator: 'contains', value: 'blue', connectionType: 'and' } ] }, { name: 'Filter Group 2', filters: [ { type: 'standard', attribute: 'item_id', operator: 'is_exactly', value: '' } ] } ]; activeFilterGroup = 0; getId() { return 'filter-builder-control'; } getTemplate() { return `
<${UIElementType.RADIO_BUTTONS} ${UEAttr.RADIO_BUTTONS.name}="filterTabs"> <${UIElementType.BUTTON} name="addFilterGroupButton">Add Filter Group
<${UIElementType.REPEATABLE} name="items">
<${UIElementType.RADIO_BUTTONS} ${UEAttr.RADIO_BUTTONS.name}="filterConcatenation"> <${UIElementType.RADIO_ITEM} ${UEAttr.RADIO_ITEM.text}="and" ${UEAttr.RADIO_ITEM.value}="and"> <${UIElementType.RADIO_ITEM} ${UEAttr.RADIO_ITEM.text}="or" ${UEAttr.RADIO_ITEM.value}="or">
<${UIElementType.LABEL} name="filterNameLabel"> <${UIElementType.BUTTON} name="filterDeleteButton">Delete
<${UIElementType.LABEL} ${UEAttr.LABEL.text}="Filter Type"> <${UIElementType.SELECTPICKER} ${UEAttr.SELECTPICKER.name}="typeSelect"> <${UIElementType.SELECT_ITEM} ${UEAttr.SELECT_ITEM.value}="standard" ${UEAttr.SELECT_ITEM.text}="Standard"> <${UIElementType.SELECT_ITEM} ${UEAttr.SELECT_ITEM.value}="classic" ${UEAttr.SELECT_ITEM.text}="Classic"> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Attribute"> <${UIElementType.SELECTPICKER} ${UEAttr.SELECTPICKER.name}="attributeSelect"> <${UIElementType.SELECT_ITEM} ${UEAttr.SELECT_ITEM.value}="item_id" ${UEAttr.SELECT_ITEM.text}="Item Id"> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Operator"> <${UIElementType.SELECTPICKER} ${UEAttr.SELECTPICKER.name}="operatorSelect"> <${UIElementType.SELECT_ITEM} ${UEAttr.SELECT_ITEM.value}="is_exactly" ${UEAttr.SELECT_ITEM.text}="Is Exactly"> <${UIElementType.SELECT_ITEM} ${UEAttr.SELECT_ITEM.value}="contains" ${UEAttr.SELECT_ITEM.text}="Contains"> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Value"> <${UIElementType.TEXT} ${UEAttr.TEXT.name}="filterInputValue">
`; } #updateFilterTabs() { this.api.setUIEAttribute( 'filterTabs', UEAttr.RADIO_BUTTONS.buttons, this.FILTER_GROUPS_DATA.map((group, idx) => ({ [UEAttr.RADIO_ITEM.text]: group.name, [UEAttr.RADIO_ITEM.value]: idx })) ); } onRender() { // Setup filter group tabs this.api.setVisibility('items[0].shouldConcatenationBeVisible', false); this.#updateFilterTabs(); this.api.updateValues({ filterTabs: this.activeFilterGroup }); // Handle adding new filter group this.api.onValueChanged('addFilterGroupButton', () => { this.FILTER_GROUPS_DATA.push({ name: `Filter Group ${this.FILTER_GROUPS_DATA.length + 1}`, filters: [{ type: 'standard', attribute: 'item_id', operator: 'is_exactly', value: '' }] }); this.#updateFilterTabs(); }); // Handle filter deletion this.api.onValueChanged('items.filterDeleteButton', (newValue) => { this.FILTER_GROUPS_DATA[this.activeFilterGroup].filters.splice(newValue.idx, 1); this.#loadActiveFilterGroup(); }); // Handle tab switching this.api.onValueChanged('filterTabs', (newValue) => { this.activeFilterGroup = newValue; this.#loadActiveFilterGroup(); }); // Load initial data this.#loadActiveFilterGroup(); // Listen to all filter changes this.api.onValueChanged('items', (newValue) => { // newValue contains array of all filter values console.log('Filters updated:', newValue); }); } #loadActiveFilterGroup() { this.api.updateValues({ items: this.FILTER_GROUPS_DATA[this.activeFilterGroup].filters.map((filter, idx) => ({ filterNameLabel: `Filter ${idx + 1}`, typeSelect: filter.type, attributeSelect: filter.attribute, operatorSelect: filter.operator, filterInputValue: filter.value, filterConcatenation: filter.connectionType })) }); } onTemplateNodeUpdated(node) { this.node = node; } } ``` Key Concepts * **Dynamic Items**: The number of repeated items is determined by the array length passed to `updateValues` * **Indexed Access**: Use bracket notation like `items[0].text` to access specific items * **Field Access**: Use dot notation like `items.text` to listen to a field across all items * **Conditional Rendering**: Use `setVisibility` API method to show/hide items * **Full Control API Support**: All standard Control API methods work with repeatable elements * **Nested UI Elements**: Items can contain any combination of UI elements including other complex elements #### Draggable Block UI Element ::::tip Version Availability This element is available starting from v3.7.0 :::: The Draggable Block UI Element lets you expose a draggable block card inside a control template. Use it to create module libraries or curated block lists in custom panels. **Attributes:** * `name` — (Required) Unique identifier for the draggable element * `block-id` — (Required) Block ID to bind to the draggable element * `disabled` — (Optional) Disable drag-and-drop **Usage Example:** ```javascript class DraggableBlockControl extends Control { getTemplate() { return `
<${UIElementType.DRAGGABLE_BLOCK} ${UEAttr.DRAGGABLE_BLOCK.name}="promoBlock" ${UEAttr.DRAGGABLE_BLOCK.blockId}="promo-block">
`; } } ``` #### AMP Form Service Picker UI Element :::::tip Version Availability This element is available starting from v3.8.0 ::::: The AMP Form Service Picker UI Element exposes a built-in picker for AMP form service configuration flows. For more details about configuring AMP services, see [Initialization Settings: AMP Form Services](https://plugin.stripo.email/editor-configuration/initialization-settings#amp-form-services). **Attributes:** * `name` - (Required) Unique identifier for the UI element * `disabled` - (Optional) Disable interaction **Usage Example:** ```javascript class AmpFormControl extends Control { getTemplate() { return ` <${UIElementType.AMP_FORM_SERVICE_PICKER} ${UEAttr.AMP_FORM_SERVICE_PICKER.name}="servicePicker"> `; } } ``` #### Multiple Select UI Element :::::tip Version Availability This element is available starting from v3.8.0 ::::: The Multiple Select UI Element provides a built-in control for choosing several values from the same field. **Attributes:** * `name` - (Required) Unique identifier for the UI element * `placeholder` - (Optional) Placeholder text * `disabled` - (Optional) Disable interaction **Usage Example:** ```javascript class TagsControl extends Control { getTemplate() { return ` <${UIElementType.MULTIPLE_SELECT} ${UEAttr.MULTIPLE_SELECT.name}="selectedTags" ${UEAttr.MULTIPLE_SELECT.placeholder}="Choose tags"> `; } } ``` #### Scrollable UI Element :::::tip Version Availability This element is available starting from v3.8.0 ::::: The Scrollable UI Element provides a scrollable wrapper for overflow-heavy layouts inside custom controls and tabs. ### Overriding Built-in UI Elements The [UIElementTagRegistry](/extensions/reference/ui-elements/UIElementTagRegistry) allows you to replace built-in UI elements with custom implementations throughout your extension. This is particularly useful when you want to: * Apply consistent branding to all UI elements * Add custom functionality to standard elements * Integrate with external services * Enforce specific validation or formatting rules ### Creating a Tag Registry To override built-in UI elements: 1. Create a class that extends `UIElementTagRegistry` 2. Implement the `registerUiElements()` method 3. Map tags to your custom UI element IDs 4. Register the registry with `ExtensionBuilder` ```javascript import {UIElementTagRegistry, UIElementType, ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; class CustomTagRegistry extends UIElementTagRegistry { registerUiElements(uiElementsTagsMap) { // Override the built-in color picker with custom implementation uiElementsTagsMap[UIElementType.COLOR] = 'brand-color-picker'; } } export default new ExtensionBuilder() .addUiElement(BrandColorPickerUIElement) .withUiElementTagRegistry(CustomTagRegistry) .build(); ``` ## UI Element API The UI Element API, accessible through `this.api`, provides method to notify the parent control when the UI element's value changes, ensuring proper synchronization and state management. For complete API reference, see [UIElementApi](/extensions/reference/api/UIElementApi). --- --- url: https://plugin.stripo.email/extensions/components/control.md --- # Control Component ## Overview ::: image-wrap ![](/img/extensions/stripo_extensions_components.png) ::: The Control component is the fundamental building block for creating custom settings interfaces in the Stripo Email Editor. Controls appear in the settings panel when users select blocks, allowing them to configure properties like colors, sizes, fonts, and custom behaviors. The Control API enables developers to create rich, interactive configuration interfaces that integrate seamlessly with the editor's template modification system, respond to state changes, and provide intuitive user experiences. ## Purpose and Core Concepts ### What is a Control? A Control in the Stripo Extensions SDK is a configurable UI component that: * Appears in the settings panel when users select blocks * Contains one or more UI elements (inputs, dropdowns, color pickers, etc.) * Communicates with the template modification system to apply changes * Can be conditionally shown or hidden based on block state * Responds to template updates and syncs its state accordingly * Provides a bridge between the UI and the underlying template structure ### Control Lifecycle Controls follow a well-defined lifecycle that enables sophisticated behaviors: 1. **Initialization** - The control is instantiated when added to a settings panel 2. **Rendering** - The `onRender()` hook is called once the control's template is inserted into the DOM 3. **Updates** - The `onTemplateNodeUpdated()` hook is called whenever the selected template node changes 4. **Visibility** - The `isVisible()` method is evaluated on each node change 5. **Destruction** - The `onDestroy()` hook is called when the control is removed ### Control vs Built-in Control The SDK provides two approaches for creating controls: * **Custom Controls** - Extend the [Control](/extensions/reference/controls/Control) class to create new settings interfaces from scratch * **Built-in Controls** - Extend `BuiltInControl` classes to customize existing editor controls with minimal code ## Key Features and Capabilities ### 1. Rich UI Composition Controls can use any combination of UI elements: * Color pickers, text inputs, dropdowns, checkboxes * Custom UI elements created with the UIElement class * Complex layouts with nested controls * Expandable sections for organized interfaces ### 2. Template Modification Controls have full access to the template modification system: * Modify HTML structure and attributes * Update CSS rules and styles * Apply changes atomically with undo/redo support * Chain multiple modifications together ### 3. Dynamic Visibility Controls can show or hide based on block state: * Check block attributes or classes * Evaluate custom conditions * Respond to configuration changes * Context-sensitive interface adaptation ### 4. State Synchronization Controls stay synchronized with template state: * Automatically update when template changes * Extract values from selected blocks * Maintain UI consistency with block properties ## Creating Custom Controls ### Basic Configuration To create a custom control: 1. Create a class that extends the `Control` class 2. Implement the required methods (`getId()`, `getTemplate()`) 3. Register the control with the ExtensionBuilder 4. Add the control to a settings panel via SettingsPanelRegistry Minimal control configuration: ```javascript import {Control, UIElementType, UEAttr, ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; class CustomColorControl extends Control { getId() { return 'custom-color-control'; } getTemplate() { return `
<${UIElementType.LABEL} ${UEAttr.LABEL.text}="Background Color"> <${UIElementType.COLOR} ${UEAttr.DEFAULT.name}="bgColor">
`; } // Additional configuration } export default new ExtensionBuilder() .addControl(CustomColorControl) .build(); ``` ### Communicating with UI Elements Controls interact with UI elements through the Control API: #### Reading Values ```javascript class CustomControl extends Control { getTemplate() { return `
<${UIElementType.LABEL} ${UEAttr.LABEL.text}="Background Color"> <${UIElementType.COLOR} ${UEAttr.DEFAULT.name}="bgColor">
`; } onRender() { // Get all values at once const values = this.api.getValues(); console.log(values.bgColor); // Or listen to individual changes this.api.onValueChanged('bgColor', (newValue, oldValue) => { console.log(`Color changed from ${oldValue} to ${newValue}`); }); } // Additional configuration } ``` #### Setting Values ```javascript class CustomControl extends Control { getTemplate() { return `
<${UIElementType.LABEL} ${UEAttr.LABEL.text}="Background Color"> <${UIElementType.COLOR} ${UEAttr.DEFAULT.name}="bgColor">
`; } onTemplateNodeUpdated(node) { // Extract current color from node const currentColor = node.getStyle('background-color'); // Update UI element this.api.updateValues({ bgColor: currentColor }); } // Additional configuration } ``` #### Controlling UI Element Properties ```javascript class CustomControl extends Control { getTemplate() { return `
<${UIElementType.LABEL} ${UEAttr.LABEL.text}="Background Color"> <${UIElementType.COLOR} ${UEAttr.DEFAULT.name}="bgColor">
`; } onRender() { // Show/hide UI elements this.api.setVisibility('bgColor', false); // Set UI element attributes this.api.setUIEAttribute('bgColor', 'disabled', true); } // Additional configuration } ``` ### Applying Template Modifications Controls can modify templates using the TemplateModifier API: ```javascript import {Control, ModificationDescription} from '@stripoinc/ui-editor-extensions'; class BackgroundControl extends Control { getId() { return 'background-control'; } getTemplate() { return ` <${UIElementType.COLOR} ${UEAttr.DEFAULT.name}="bgColor"> `; } onRender() { this.api.onValueChanged('bgColor', (newColor) => { // Create and apply modification this.api.getDocumentModifier() .modifyHtml(this.currentNode) .setStyle('background-color', newColor) .apply(new ModificationDescription('Update background color')); }); } onTemplateNodeUpdated(node) { this.currentNode = node; // Sync UI with current template state const currentColor = node.getStyle('background-color'); this.api.updateValues({ bgColor: currentColor }); } } ``` ### Conditional Visibility Controls can be shown or hidden based on block state: ```javascript class ConditionalControl extends Control { isVisible(node) { // Hide if block has data-locked attribute if (node.getAttribute('data-locked') === 'true') { return false; } return true; } // Additional configuration } ``` ## Built-in Controls The Stripo Extensions SDK provides a comprehensive set of built-in controls for common editing tasks. These controls handle complex editor functionality like color management, sizing, spacing, and more. **IMPORTANT:** Built-in controls are designed exclusively for modifying Stripo's standard HTML markup. To ensure correct block HTML, always use [Template Aliases](/extensions/tutorials/how-to/template-aliases). If your extension uses custom HTML markup, built-in controls are not supported and may not function as expected. To use built-in controls: * Override the appropriate built-in control class * Set the id of the control * (Optional) Override control labels * (Optional) Override target nodes * (Optional) Add additional modifications * Register the control with the ExtensionBuilder * Add the control to a settings panel via SettingsPanelRegistry ### Built-in Control Types by Block Category The SDK provides a comprehensive collection of built-in controls organized by block type. Each control is designed to modify specific aspects of its corresponding block type. When extending these controls, you can customize their behavior by overriding labels, target nodes, or adding additional modifications. #### Button Block Controls Button block controls handle all aspects of button customization, from styling to positioning. ##### ButtonAlignBuiltInControl * **Description**: Controls button alignment within its container for both desktop and mobile views * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonBackgroundColorBuiltInControl * **Description**: Manages button background color modifications * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonBlockBackgroundColorBuiltInControl * **Description**: Controls the background color of the entire button block container * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonBorderBuiltInControl * **Description**: Handles button border styling including color, width, and style * **Override labels**: `ButtonBorderControlLabels` * `title` - Main control title * `titleHint` - Tooltip for the control title * `borderColorTitle` - Border color section title * `borderStyleTitle` - Border style section title * `borderStyleHint` - Tooltip for border style ##### ButtonBorderRadiusBuiltInControl * **Description**: Controls button border radius (rounded corners) * **Override labels**: `ButtonBorderRadiusControlLabels` * `title` - Main control title * `titleHint` - Tooltip for the control title ##### ButtonColorBuiltInControl * **Description**: Controls button text color modifications * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonFitToContainerBuiltInControl * **Description**: Adjusts button width to fit container width * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonFixedHeightBuiltInControl * **Description**: Controls fixed height for button blocks with alignment options * **Override labels**: `FixedHeightLabels` * `title` - Main control title * `counterTitle` - Height counter section title * `alignTitle` - Alignment section title ##### ButtonFontFamilyBuiltInControl * **Description**: Manages button text font family selection * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonHoverBorderColorBuiltInControl * **Description**: Controls button border color on hover state * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonHoverColorBuiltInControl * **Description**: Manages button background color on hover state * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonHoverTextColorBuiltInControl * **Description**: Controls button text color on hover state * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonMarginsBuiltInControl * **Description**: Manages external spacing (margins) around the button block * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonPaddingsBuiltInControl * **Description**: Controls internal spacing (padding) within the button * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonTextBuiltInControl * **Description**: Manages button text content modifications * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonTextSizeBuiltInControl * **Description**: Controls button text font size * **Override labels**: `ControlLabels` * `title` - Main control title ##### ButtonTextStyleAndFontColorBuiltInControl * **Description**: Manages button text styling (bold, italic, underline) and font color together * **Override labels**: `ButtonTextStyleAndFontColorControlLabels` * `title` - Main control title * `colorTitle` - Font color section title * `styleTitle` - Text style section title ##### ButtonVisibilityBuiltInControl * **Description**: Controls button visibility and display conditions * **Override labels**: `ControlLabels` * `title` - Main control title #### Image Block Controls Image block controls manage image properties, sizing, and spacing. ##### ImageAlignmentBuiltInControl * **Description**: Controls image alignment within the surrounding layout * **Override labels**: `ControlLabels` * `title` - Main control title ##### ImageMarginsBuiltInControl * **Description**: Manages external spacing (margins) around image blocks * **Override labels**: `ControlLabels` * `title` - Main control title ##### ImageSizeBuiltInControl * **Description**: Controls image dimensions and sizing options * **Override labels**: `ControlLabels` * `title` - Main control title ##### ImageVisibilityBuiltInControl * **Description**: Controls image visibility and display conditions * **Override labels**: `ControlLabels` * `title` - Main control title #### Text Block Controls Text block controls handle text formatting, styling, and layout. ##### TextFontFamilyBuiltInControl * **Description**: Manages font family selection for text blocks * **Override labels**: `ControlLabels` * `title` - Main control title ##### TextFixedHeightBuiltInControl * **Description**: Controls fixed height for text blocks with alignment options * **Override labels**: `FixedHeightLabels` * `title` - Main control title * `counterTitle` - Height counter section title * `alignTitle` - Alignment section title ##### TextAlignBuiltInControl * **Description**: Manages text alignment (left, center, right, justify) * **Override labels**: `ControlLabels` * `title` - Main control title ##### TextBlockBackgroundBuiltInControl * **Description**: Controls background color of the entire text block * **Override labels**: `ControlLabels` * `title` - Main control title ##### TextColorBuiltInControl * **Description**: Manages text color modifications * **Override labels**: `ControlLabels` * `title` - Main control title ##### TextLineSpacingBuiltInControl * **Description**: Controls line height and spacing between text lines * **Override labels**: `ControlLabels` * `title` - Main control title ##### TextPaddingsBuiltInControl * **Description**: Manages internal spacing (padding) within text blocks * **Override labels**: `ControlLabels` * `title` - Main control title ##### TextSizeBuiltInControl * **Description**: Controls text font size * **Override labels**: `ControlLabels` * `title` - Main control title ##### TextStyleBuiltInControl * **Description**: Manages text styling (bold, italic, underline, strikethrough) * **Override labels**: `ControlLabels` * `title` - Main control title ##### TextVisibilityBuiltInControl * **Description**: Controls text block visibility and display conditions * **Override labels**: `ControlLabels` * `title` - Main control title #### Container Block Controls Container controls manage container styling, backgrounds, and borders. ##### ContainerBackgroundColorBuiltInControl * **Description**: Controls container background color * **Override labels**: `ControlLabels` * `title` - Main control title ##### ContainerBackgroundImageBuiltInControl * **Description**: Manages container background image and its positioning * **Override labels**: `BackgroundImageControlLabels` * `title` - Main control title * `titleHint` - Tooltip for the control title * `repeat` - Background repeat option label * `repeatHint` - Tooltip for repeat option * `horizontalPosition` - Horizontal position label * `verticalPosition` - Vertical position label * `backgroundWidth` - Background width label * `backgroundHeight` - Background height label ##### ContainerBorderBuiltInControl * **Description**: Controls container border styling including color, width, and style * **Override labels**: `BorderLabels` * `title` - Main control title * `borderColorTitle` - Border color section title * `borderStyleTitle` - Border style section title * `borderStyleHint` - Tooltip for border style ##### ContainerVisibilityBuiltInControl * **Description**: Controls container visibility and display conditions * **Override labels**: `ControlLabels` * `title` - Main control title #### Structure Block Controls Structure controls manage layout structures, including responsive behavior and styling. ##### StructureAdaptBuiltInControl * **Description**: Controls structure responsive behavior and mobile adaptation settings * **Override labels**: `StructureAdaptControlLabels` * `title` - Main control title * `description` - Descriptive text for adaptation options ##### StructureBackgroundColorBuiltInControl * **Description**: Manages structure background color * **Override labels**: `ControlLabels` * `title` - Main control title ##### StructureBackgroundImageBuiltInControl * **Description**: Controls structure background image and its positioning * **Override labels**: `BackgroundImageControlLabels` * `title` - Main control title * `titleHint` - Tooltip for the control title * `repeat` - Background repeat option label * `repeatHint` - Tooltip for repeat option * `horizontalPosition` - Horizontal position label * `verticalPosition` - Vertical position label * `backgroundWidth` - Background width label * `backgroundHeight` - Background height label ##### StructureBorderBuiltInControl * **Description**: Manages structure border styling including color, width, and style * **Override labels**: `BorderLabels` * `title` - Main control title * `borderColorTitle` - Border color section title * `borderStyleTitle` - Border style section title * `borderStyleHint` - Tooltip for border style ##### StructureMarginsBuiltInControl * **Description**: Controls external spacing (margins) around structure blocks * **Override labels**: `ControlLabels` * `title` - Main control title ##### StructurePaddingsBuiltInControl * **Description**: Manages internal spacing (padding) within structure blocks * **Override labels**: `ControlLabels` * `title` - Main control title ##### StructureVisibilityBuiltInControl * **Description**: Controls structure visibility and display conditions * **Override labels**: `ControlLabels` * `title` - Main control title ### Extending Built-in Controls The `BuiltInControl` class allows you to customize existing editor controls with minimal code. This is useful when you want to: * Change which elements a control targets * Override control labels * Add additional modifications * Customize control behavior for custom blocks #### Basic Built-in Control Extension In the example below, we use the button background color control without modification. The control finds all Stripo button blocks and applies the background color to them. ```javascript import {ButtonBackgroundColorBuiltInControl} from '@stripoinc/ui-editor-extensions'; class CustomBackgroundColorControl extends ButtonBackgroundColorBuiltInControl { getId() { return 'custom-background-control'; } } ``` #### Overriding Target Nodes By default, a built-in control attempts to find all nodes of the appropriate type inside the selected one to apply changes. You can override this behavior to target specific elements: ```javascript import {BlockSelector, ButtonBackgroundColorBuiltInControl} from '@stripoinc/ui-editor-extensions'; class CustomBackgroundColorControl extends ButtonBackgroundColorBuiltInControl { getId() { return 'custom-background-control'; } getTargetNodes(root) { // Target only first button block inside the selected root node return [ root.querySelector(BlockSelector.BUTTON) ]; } } ``` #### Customizing Labels Override labels to match your custom block terminology: ```javascript import {ButtonBackgroundColorBuiltInControl} from '@stripoinc/ui-editor-extensions'; class CustomBackgroundColorControl extends ButtonBackgroundColorBuiltInControl { getId() { return 'custom-background-control'; } getLabels() { return { title: this.api.translate('Custom Background Color') }; } } ``` #### Adding Custom Modifications If you need to add custom modifications to the template, you can use the `getAdditionalModifications()` method. Describe all modifications with the modifier and return it without calling `apply()` to prevent multiple records in the undo stack and version history. If you want to override the message in the version history, you can use the `getModificationDescription()` method: ```javascript import { BlockSelector, ButtonBackgroundColorBuiltInControl, ModificationDescription } from '@stripoinc/ui-editor-extensions'; class CustomBackgroundColorControl extends ButtonBackgroundColorBuiltInControl { getId() { return 'custom-background-control'; } getAdditionalModifications(root) { const modifier = this.api.getDocumentModifier(); // Add border to button in addition to the background color modifier .modifyHtml(root.querySelector(BlockSelector.BUTTON)) .setStyle('border', '1px solid #ddd'); return modifier; } getModificationDescription() { return new ModificationDescription('Update background with border'); } } ``` #### Conditional Visibility for Built-in Controls Show or hide the control based on block state: ```javascript import { BlockSelector, ButtonBackgroundColorBuiltInControl, } from '@stripoinc/ui-editor-extensions'; class CustomBackgroundColorControl extends ButtonBackgroundColorBuiltInControl { getId() { return 'custom-background-control'; } isVisible(node) { return !!node.querySelector(BlockSelector.BUTTON).getAttribute('bgcolor'); } } ``` ## Control API The Control API provides access to: * Current node and document root access * Template modification system * UI element management (visibility, attributes, values) * Value change listeners * Editor configuration and state * Internationalization utilities You can find more practical examples in the [Working with APIs](/extensions/tutorials/how-to/working-with-apis) tutorial. ## Best Practices ### 1. Keep Controls Focused Each control should handle a single responsibility. For complex configuration requirements, consider creating multiple related controls. ### 2. Synchronize State Always implement `onTemplateNodeUpdated()` to keep UI elements synchronized with template state. ### 3. Use Descriptive IDs Control IDs should clearly describe their purpose: `product-price-color` rather than `control-1`. ### 4. Validate User Input Validate values before applying modifications to prevent invalid template states. ### 5. Provide User Feedback Use modification descriptions that clearly explain changes to provide better version history messages. ### 6. Clean Up Resources Always implement `onDestroy()` if you create timers, intervals, or external event listeners. ### 7. Leverage Built-in Controls Before creating a custom control from scratch, check if extending a built-in control would be faster and more maintainable. --- --- url: https://plugin.stripo.email/extensions/features/localization.md --- # Localization The Stripo editor is multilingual and supports full localization of the user interface. The parameter that controls the interface language is passed during initialization: ```javascript const stripoConfig = { locale: 'en', ... } window.UIEditor.initEditor(domContainer, stripoConfig); ``` To support message localization, you need to: 1. Register a map of keys and values for each supported language 2. Call the `translate` API method where needed instead of plain text, passing the translation key and optional parameters for interpolation into the translated string ## Additional Considerations When modifying a template, the `apply` method accepts a `ModificationDescription` parameter with a description of the changes. To ensure this text is displayed in the version history in the user's language, it is strongly recommended to use translation keys in `ModificationDescription` as well. ## Example ```javascript //------------en.js------------- export default { "Buy": "Buy", "Sell": "Sell {count} units.", "Set event-id": "Set event-id {eventId}" } //------------uk.js------------- export default { "Buy": "Купити", "Sell": "Продати {count} од.", "Set event-id": "Встановлено event-id {eventId}" } //------------SimpleBlock.js------------- import {Block, ModificationDescription} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { getTemplate() { return `
${this.api.translate('Buy')} ${this.api.translate('Sell', {count: 2})} ` } onCreated(node) { const eventId = '1234-56'; this.api.getDocumentModifier() .modifyHtml(node) .setAttribute('event-id', eventId) .apply(new ModificationDescription('Set event-id') .withParams({ eventId: eventId })); } // Additional block configuration methods... } //------------Extension.js------------- import uk from './uk'; import en from './en'; import {ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; import {SimpleBlock} from './blocks/SimpleBlock'; export default new ExtensionBuilder() .withLocalization({ 'en': en, 'uk': uk, }) .addBlock(SimpleBlock) .build(); ``` --- --- url: https://plugin.stripo.email/extensions/features/theming.md --- # Editor Theming & Panel Styling The Stripo Extensions SDK provides comprehensive theming capabilities that allow you to customize the visual appearance of both the editor interface and the email template preview. This includes styling blocks panels, overriding default UI styles, and adding custom preview-only styles like selection borders and context icons. ## Styles ### Overview There are two primary types of styles you can customize: 1. **UI Styles** - Applied to the editor interface (blocks panel, controls, toolbars) 2. **Preview Styles** - Applied only to the document preview area (selection borders, custom indicators) ::: image-wrap ![](/img/extensions/styles.png) ::: ### UI Styles UI styles modify the appearance of the editor interface itself, including the blocks panel, control panels, and other UI components. #### Adding UI Styles Use the `addStyles()` method to inject custom CSS into the editor interface: ```javascript import {ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; const extension = new ExtensionBuilder() .addStyles(` /* Customize blocks panel background color */ .block-panel-content { background-color: cadetblue; } `) .build(); ``` #### Multiple Style Sheets You can add multiple style sheets by calling `addStyles()` multiple times: ```javascript import {ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; import panelStyles from './styles/panel.css?raw'; import controlStyles from './styles/controls.css?raw'; const extension = new ExtensionBuilder() .addStyles(panelStyles) .addStyles(controlStyles) .build(); ``` #### Reacting to Email Template Theme Mode Use `ThemeMode` with `getEditorState()` or `onEditorStatePropUpdated()` when your extension UI needs to adapt to the email template's active light or dark theme. ```javascript import {Control, EditorStatePropertyType, ThemeMode, UEAttr, UIElementType} from '@stripoinc/ui-editor-extensions'; class ThemeAwareControl extends Control { getId() { return 'theme-aware-control'; } getTemplate() { return ` <${UIElementType.MESSAGE} ${UEAttr.MESSAGE.name}="statusMessage"> `; } onRender() { this.applyTheme(this.api.getEditorState().themeMode); this.api.onEditorStatePropUpdated( EditorStatePropertyType.themeMode, (themeMode) => this.applyTheme(themeMode) ); } applyTheme(themeMode) { this.api.setUIEAttribute( 'statusMessage', UEAttr.MESSAGE.type, themeMode === ThemeMode.DARK ? 'info' : 'warning' ); } } ``` ### Preview Styles Preview styles are applied only to the document preview area. These styles don't affect the final HTML output - they only enhance the editing experience. #### Adding Preview Styles Use the `withPreviewStyles()` method to set styles that apply only in the editor preview: ```javascript import {ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; const extension = new ExtensionBuilder() .withPreviewStyles(` .ue-preview-dragging, .ue-dots-icon { background-color: coral !important; } `) .build(); ``` ## Blocks Panel Customization For advanced blocks panel customization, use the [BlocksPanel](/extensions/reference/blocks/BlocksPanel) class. This class enables you to: * Generate HTML representation for a block item in the blocks panel * Generate HTML representation for the blocks panel header * Generate HTML representation for the modules panel in collapsed state * Control hint visibility for individual blocks * Control hint visibility for the collapsed modules panel * Set a custom delay for showing hints in milliseconds * Set the hint text for a block * Set the hint text for the modules panel * Set icons for modules tabs ::: image-wrap ![](/img/extensions/modules_panel.png) ::: ::: image-wrap ![](/img/extensions/blocks_panel.png) ::: ::: image-wrap ![](/img/extensions/modules_icon.png) ::: ```javascript import {BlocksPanel, ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; class CustomBlocksPanel extends BlocksPanel { getBlockItemHtml(block) { return `
${block.title}
`; } getBlocksPanelHeaderHtml() { return `

Blocks

`; } getHintDelay() { return 1000; } getModulesPanelHint() { return { title: this.api.translate('Modules and structures'), description: this.api.translate('Click to open the modules and structures panel.'), }; } isBlockHintVisible(block) { return !block.disabled; } isModulesPanelCollapsedHintVisible() { return true; } } const extension = new ExtensionBuilder() .withBlocksPanel(CustomBlocksPanel) .addStyles(` .block-thumb { display: flex; align-items: center; gap: 10px; width: 100%; max-width: 100%; box-sizing: border-box; } .block-thumb-label { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .blocks-panel-title { border-bottom: var(--ue-border-width-1, 1px) solid var(--ue-panels-border-color, rgba(0, 0, 0, 0.07)); display: flex; justify-content: center; } `) .build(); ``` --- --- url: https://plugin.stripo.email/extensions/tutorials.md --- # Tutorials and Examples Welcome to the Stripo Extensions SDK tutorials! This section provides practical how-to guides and real-world examples to help you build powerful extensions for the Stripo email editor. ## Available How-To Guides ### [Template Modifications](/extensions/tutorials/how-to/template-modifications) Learn how to programmatically modify email templates at runtime: * **Dynamic attribute updates** - Add tracking parameters and modify link attributes * **HTML node manipulation** - Add, remove, or replace template elements * **Text content manipulation** - Update placeholders and dynamic content * **Node configuration storage** - Store custom metadata in template nodes * **Conditional display logic** - Control visibility based on user data * **CSS style modifications** - Programmatically update custom styles * **Structure layout adjustments** - Reorganize multi-column layouts and container widths * **Localized version history** - Provide clear, translated modification descriptions ### [Template Aliases](/extensions/tutorials/how-to/template-aliases.md) Simplify block development with template alias shortcuts: * **Cross-client compatibility** - Ensure consistent appearance across email clients * **Simple block creation** - Build single-cell blocks with standard elements * **Container creation** - Create grouping containers with interaction controls * **Structure creation** - Build multi-column layouts with flexible widths * **Empty containers** - Add placeholders for user-populated content * **Quick-add icons** - Customize available blocks in empty containers ### [Settings Panel Configuration](/extensions/tutorials/how-to/settings-panel.md) Customize the settings panel for blocks: * **Add new controls** - Insert custom controls into existing tabs * **Remove controls** - Simplify the interface by hiding unnecessary options * **Reorder controls** - Organize controls for better user experience * **Multiple tabs** - Structure controls across multiple logical groupings * **Custom tab labels** - Provide localized labels for custom tabs * **Block-specific customization** - Configure settings for both built-in and custom blocks ### [Working with APIs](/extensions/tutorials/how-to/working-with-apis.md) Master the extension API system: * **Editor configuration** - Access and use editor initialization settings * **Translation and internationalization** - Support multiple languages in extensions * **Custom font management** - Dynamically add fonts to the editor * **Editor state management** - Monitor and respond to editor state changes * **Click outside behavior** - Control deselection behavior for custom toolbars * **AI and emoji popovers** - Open built-in AI assistant and emoji picker * **Document root access** - Traverse and query the document structure * **Control API** - Manage UI element values, visibility, and attributes * **UI Element API** - Build custom UI components with value change notifications ## Real-World Examples Explore complete implementation examples organized by category: ### Custom Blocks Learn how to create custom content blocks with these comprehensive tutorials: * **[Coupon Block](/extensions/tutorials/examples/coupon-block)** - Create a custom coupon code block with styling controls ### External Integrations Connect the Stripo editor to your external systems and services: * **[External Images Library](/extensions/tutorials/examples/integrations/external-image-library)** - Integrate your image hosting service or CDN * **[External Image Library Tab](/extensions/tutorials/examples/integrations/external-image-library-tab)** - Add a custom tab in the native image gallery * **[External Video Library](/extensions/tutorials/examples/integrations/external-video-library)** - Connect video hosting platforms * **[External Smart Elements Library](/extensions/tutorials/examples/integrations/external-smart-elements-library)** - Integrate product catalogs and dynamic content * **[External Merge Tags Selector](/extensions/tutorials/examples/integrations/external-merge-tags-selector)** - Connect to CRM or customer data platforms * **[External AI Assistant](/extensions/tutorials/examples/integrations/external-ai-assistant)** - Add AI-powered text enhancement capabilities * **[External Display Conditions](/extensions/tutorials/examples/integrations/external-display-conditions)** - Create conditional content rules * **[External Custom Font](/extensions/tutorials/examples/integrations/external-custom-font)** - Add custom web fonts to the editor ## Prerequisites Before starting these tutorials, ensure you have: * Basic understanding of JavaScript and HTML/CSS * Familiarity with the [Core Concepts](/extensions/core-concepts) * Completed the [Getting Started](/extensions/getting-started) guide ## Tutorial Approach Each how-to guide is structured to: * **Focus on specific tasks** - Solve concrete problems you'll encounter * **Provide complete examples** - Include all necessary code with explanations * **Show multiple approaches** - Compare different solutions when applicable * **Highlight best practices** - Recommend optimal patterns and techniques * **Include real scenarios** - Use practical use cases from actual implementations ## Getting Help If you encounter issues while following these tutorials: * Check the [API Reference](/extensions/reference) for detailed method documentation * Review the [Core Concepts](/extensions/core-concepts) for foundational knowledge * Study the [Component Guides](/extensions/components/block) for component-specific details --- --- url: >- https://plugin.stripo.email/extensions/tutorials/how-to/template-modifications.md --- # How to Handle Template Modifications ## Understanding the Template Modification System Before exploring practical examples, we strongly recommend reviewing the [Core Concepts](/extensions/core-concepts) and [Template Modification System](/extensions/template-modification) documentation. These resources will help you understand the foundational concepts behind template modifications. ## Examples ### Dynamic Attribute Updates One of the most common tasks is adding or updating HTML attributes. For example, consider the following block content: ```html Stripo Github ``` The following example demonstrates how to add tracking attributes to all links within the selected node: ```javascript const campaignId = '123456'; const modifier = this.api.getDocumentModifier(); this.node.querySelectorAll('a').forEach(link => { const href = link.getAttribute('href'); if (href && !href.includes('utm_campaign')) { const separator = href.includes('?') ? '&' : '?'; const trackedUrl = `${href}${separator}utm_campaign=${campaignId}&utm_source=email&utm_medium=stripo`; modifier.modifyHtml(link) .setAttribute('href', trackedUrl) .setAttribute('data-campaign-id', campaignId); } }); modifier.apply(new ModificationDescription(`Added tracking parameters to links`)); ``` ### HTML Node Manipulation Template modifications often require adding or removing HTML nodes. While node addition is straightforward, node removal requires careful consideration of the template's structural integrity and user experience. There are two primary approaches to node removal: #### 1. Direct Node Deletion This method permanently removes nodes from the template using the `delete()` method: ```javascript const blocks = this.api.getDocumentRoot().querySelectorAll('.esd-block-image'); const modifier = this.api.getDocumentModifier(); blocks.forEach(block => { modifier.modifyHtml(block).delete(); }); modifier.apply(new ModificationDescription('Removed all image blocks')); ``` **Important Considerations**: Direct deletion removes the node completely from the template structure. However, this approach eliminates the drag-and-drop zone at the deleted position, creating non-interactive blank spaces. This behavior can negatively impact the user experience and template editing workflow. #### 2. Node Replacement with Empty Container (Recommended) This method replaces nodes with empty containers, preserving the template's interactive structure: ```javascript const blocks = this.api.getDocumentRoot().querySelectorAll('.esd-block-image'); const modifier = this.api.getDocumentModifier(); blocks.forEach(block => { modifier.modifyHtml(block).replaceWith(`<${BlockType.EMPTY_CONTAINER}/>`); }); modifier.apply(new ModificationDescription('Replaced image blocks with empty containers')); ``` **Advantages**: This approach maintains the template's drag-and-drop functionality by preserving interactive zones where users can add new blocks. The empty containers act as placeholders that maintain the template's structural integrity while allowing for future content additions. ### Text Content Manipulation Consider a scenario where you have a block with content like this: ```html Hello, dear {NAME} ``` and you want to replace the placeholder `{NAME}` with the actual user's name. There are several ways to achieve this. The simplest approach is to use the `setInnerHtml` method: ```javascript const originalText = this.node.getInnerHTML(); const updatedText = originalText.replace('{NAME}', 'John'); this.api.getDocumentModifier() .modifyHtml(this.node) .setInnerHtml(updatedText) .apply(new ModificationDescription(`Updated text to ${updatedText}`)) ``` The disadvantage of this approach is that it replaces the entire content of the block, which can be substantial for larger blocks. A more elegant solution is to use the `setText` method on the specific text node: ```javascript const nameMergeTagTextNode = this.node.querySelector('b').childNodes() .find(c => c.getType() === 'text' && c.getTextContent() === '{NAME}'); if (nameMergeTagTextNode) { this.api.getDocumentModifier() .modifyHtml(nameMergeTagTextNode) .setText('John') .apply(new ModificationDescription('Updated placeholder with actual name')); } ``` ### Node Configuration Storage You can store service metadata and settings for individual template nodes in JSON format to reuse them later. For example, you may want to store the campaign ID in the root node configuration storage of the selected block. This can be accomplished with the following code: ```javascript const campaignId = '123456'; const originalConfig = this.currentNode.getNodeConfig(); const newConfig = { ...originalConfig, campaignId: campaignId, }; this.api.getDocumentModifier() .modifyHtml(this.currentNode) .setNodeConfig(newConfig) .apply(new ModificationDescription('Stored campaign ID in node config')); ``` ### Conditional Display Logic Sometimes you may want to hide or show a block based on specific conditions. For example, you may want to display a block exclusively for female recipients. Here's how you can achieve this: ```javascript const nodeDisplayCondition = { id: 'id_1', name: 'Female', description: 'Only female customers will see this part of the email.', beforeScript: '{% if contact.gender == \"Female\" %}', afterScript: '{% endif %}', extraData: JSON.stringify({campaignId: '123456'}) } this.api.getDocumentModifier() .modifyHtml(this.currentNode) .setDisplayCondition(nodeDisplayCondition) .apply(new ModificationDescription('Set block visibility for females only')); ``` ### Device-Specific Hidden State You can also update the editor's built-in hide-on-device state for a node. For example, this hides the current node on mobile: ```javascript this.api.getDocumentModifier() .modifyHtml(this.currentNode) .setHiddenElementState('mobile') .apply(new ModificationDescription('Hide block on mobile')); ``` To read the current state from a block, use the block API: ```javascript const hiddenState = this.api.getHiddenElementState(this.currentNode); ``` The state can be `'desktop'`, `'mobile'`, or `undefined`. Pass `undefined` to `setHiddenElementState()` to clear the hidden state. ### CSS Style Modifications Sometimes HTML modifications are not sufficient. You can programmatically modify custom CSS styles as well. For example, you may have the following custom CSS: ```css h1 { font-family: 'Courier New', Courier, monospace; } @media only screen and (max-width: 600px) { h1 { font-family: 'Times New Roman', Times, serif; } } ``` and you want to change the font-family for all h1 tags to 'Helvetica'. You can achieve this with the following code: ```javascript const desktopH1CssNode = this.api.getDocumentRootCssNode().querySelector('h1'); const mobileH1CssNode = this.api.getDocumentRootCssNode().querySelector('@{media only screen and (max-width: 600px)} h1'); // Get current values if you need to compare them const currentDesktopH1Value = desktopH1CssNode.querySelector('{font-family}').getAttributeValue(); const currentMobileH1Value = mobileH1CssNode.querySelector('{font-family}').getAttributeValue(); this.api.getDocumentModifier() .modifyCss(desktopH1CssNode) .setProperty('font-family', 'Helvetica') .modifyCss(mobileH1CssNode) .setProperty('font-family', 'Helvetica') .apply(new ModificationDescription('Updated font family for h1')); ``` ### Structure Container Width Adjustment Sometimes you may need to change structure container dimensions. For example, you may want to change a base two-column structure from a 50%/50% layout to a 70%/30% layout. You can achieve this with the following code: ```javascript this.api.getDocumentModifier() .modifyHtml(this.currentNode) .multiRowStructureModifier() .updateLayout(['70%', '30%']) .apply(new ModificationDescription('Updated structure layout')); ``` ### Structure Layout Reorganization with Content There are cases where initially your block is not configured properly and is dragged and dropped into the template as a blank block. During configuration actions from the settings panel, it becomes a complete structure with content. In this case, you can use the following code to reorganize the structure: ```javascript this.api.getDocumentModifier() .modifyHtml(this.currentNode) .multiRowStructureModifier() .updateLayoutWithContent( ['70%', '30%'], [ `<${BlockType.BLOCK_TEXT}>

Lorem ipsum dolor sit amet

`, `<${BlockType.BLOCK_BUTTON}>Click Me`, ]) .apply(new ModificationDescription('Updated structure layout with content')); ``` ### Localized Version History Every template change requires a description to be displayed in the version history. You can add localization files to your extension configuration and use them to provide localized descriptions. For example, if you have a localization file `uk.js`: ```javascript export default { "Add campaign id {campaignId} to params": "Додано ID компанії {campaignId} до параметрів" } ``` and the following modification code: ```javascript const campaignId = this.api.translate('123456'); this.api.getDocumentModifier() .modifyHtml(this.currentNode) .setAttribute('data-campaign-id', campaignId) .apply(new ModificationDescription('Add campaign id {campaignId} to params') .withParams({ campaignId: campaignId })); ``` then the resulting message in the version history for Ukrainian users will look like: ```text Додано ID компанії 123456 до параметрів ``` --- --- url: https://plugin.stripo.email/extensions/tutorials/how-to/working-with-apis.md --- # Working with Components APIs ## Core API System The Stripo Extensions SDK provides a hierarchical API system where each extension component (Block, Control, UIElement, etc.) has access to specific API methods through the `this.api` property. ### API Inheritance Hierarchy ```javascript BaseApi ├── BlockApi (extends BaseApi + BaseModifierApi) ├── ControlApi (extends BaseApi + BaseModifierApi) ├── UIElementApi (extends BaseApi) ├── ContextActionApi (extends BaseApi + BaseModifierApi) ├── BlockRendererApi (extends BaseApi) ├── SettingsPanelApi (extends BaseApi) └── BlocksPanelApi (extends BaseApi) ``` ## Examples ### General API Usage #### Getting Editor Configuration One of the most common tasks is configuring custom blocks based on the editor configuration. In this example, we will use the following editor configuration: ```javascript window.UIEditor.initEditor( document.querySelector('#stripoEditorContainer'), { html: template.html, css: template.css, metadata: { emailId: `email_1`, username: 'Demo User' }, ..., extensions: [ ... ], simpleBlockConfig: { enabled: true, item: 'IPhone 17' } } ); //=========================================================== import {Block} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { isEnabled() { return this.api.getEditorConfig().simpleBlockConfig?.enabled || true; } getBlockCompositionType() { return BlockCompositionType.STRUCTURE; } getTemplate() { return ` <${BlockType.STRUCTURE}> <${BlockType.CONTAINER}> <${BlockType.BLOCK_TEXT}> ${this.api.getEditorConfig().simpleBlockConfig?.item || 'Unknown'} ` } // Additional block configuration methods... } ``` #### Translation and Internationalization Use the translation API to support multiple languages: ```javascript // uk.js - Ukrainian translations export default { "Buy": "Купити", "Set event-id {eventId}": "Встановлено event-id {eventId}" } //=============================================================== import uk from './uk'; import {Block, ModificationDescription} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { getTemplate() { return ` ${this.api.translate('Buy')} ` } onCreated(node) { const eventId = '1234-56'; this.api.getDocumentModifier() .modifyHtml(node) .setAttribute('event-id', eventId) .apply(new ModificationDescription('Set event-id {eventId}').withParams({ eventId: eventId })); } // Additional block configuration methods... } export default new ExtensionBuilder() .withLocalization({ 'uk': uk, }) .addBlock(SimpleBlock) .build(); ``` #### Custom Font Management Dynamically add custom fonts to the editor: ```javascript import {Block} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { onCreated(node) { // Add a Google Font this.api.addCustomFont({ name: 'Roboto Slab', fontFamily: 'Roboto Slab, serif', url: 'https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@400;700' }); } // Additional block configuration methods... } ``` #### Editor State Management Monitor and respond to editor state changes: ```javascript import {Block, EditorStatePropertyType, PreviewDeviceMode} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { onCreated(node) { // Get current editor state const state = this.api.getEditorState(); console.log('Current device:', state.previewDeviceMode); console.log('Panel position:', state.panelPosition); // Subscribe to device mode changes this.api.onEditorStatePropUpdated( EditorStatePropertyType.previewDeviceMode, (newMode, oldMode) => { if (newMode === PreviewDeviceMode.MOBILE) { console.log('Switching to mobile view'); } else { console.log('Switching to desktop view'); } } ); } // Additional block configuration methods... } ``` #### Sending Custom Editor Events Use `sendEvent()` to emit fire-and-forget events from blocks, controls, or UI elements: ```javascript class AnalyticsAwareBlock extends Block { onSelect(node) { this.api.sendEvent('extensions.analyticsAwareBlock.selected', { blockId: this.getId(), nodeId: node.getAttribute('id') }); } } ``` #### Click Outside Behavior Control how the editor handles clicks outside of blocks. This is particularly useful for custom toolbars and popup windows: ```javascript import {Block, EditorStatePropertyType, PreviewDeviceMode} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { onSelect(node) { // Prevent deselection when clicking outside this.api.ignoreClickOutside(true); // Show custom toolbar this.showCustomToolbar(); } showCustomToolbar() { const toolbar = this.createToolbar(); document.body.appendChild(toolbar); // Re-enable normal behavior when done toolbar.addEventListener('close', () => { this.api.ignoreClickOutside(false); toolbar.remove(); }); } // Additional block configuration methods... } ``` #### AI and Emoji Popovers Open the default AI assistant and emoji picker popovers: ```javascript import {ExtensionPopoverType, PopoverSide, UIElement} from '@stripoinc/ui-editor-extensions'; class SimplePopoverUIElement extends UIElement { getId() { return 'popovers-example'; } getTemplate() { return `
` } onRender(container) { this.emojiButton = container.querySelector('.emoji-button'); this.aiButton = container.querySelector('.ai-button'); this.emojiButton.addEventListener('click', this.showEmojiPopover.bind(this)); this.aiButton.addEventListener('click', this.showAiPopover.bind(this)); } showEmojiPopover() { this.api.openEmojiPopover({ targetElement: this.emojiButton, preferredSides: [PopoverSide.LEFT], onResult: (result) => { console.log(result); } }) } showAiPopover() { this.api.openAIPopover({ targetElement: this.aiButton, value: 'Hello world', preferredSides: [PopoverSide.LEFT], type: ExtensionPopoverType.AI_TEXT, onResult: (result) => { console.log(result); } }) } onDestroy() { this.emojiButton.removeEventListener('click', this.showEmojiPopover.bind(this)); this.aiButton.removeEventListener('click', this.showAiPopover.bind(this)); } } ``` #### Document Root Access Access and traverse the document structure: ```javascript import {Block} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { onDocumentInit() { const linksCount = this.api.getDocumentRootHtmlNode() .querySelectorAll(`a`).length; const h1FontFamily = this.api.getDocumentRootCssNode() .querySelector('h1 {font-family}').getAttributeValue(); } // Additional block configuration methods... } ``` ### Modification API Usage Use the [Template Modification System](/extensions/tutorials/how-to/template-modifications) to make template changes: ```javascript const modifier = this.api.getDocumentModifier(); ``` ### Block API Usage #### View-Only Mode Control the block's state. In view-only mode, the block cannot be added to the template via drag and drop, but can still be edited using the editor's UI: ```javascript import {Block} from '@stripoinc/ui-editor-extensions'; class SimpleBlock extends Block { onDocumentInit() { const blocks = this.api.getDocumentRoot() .querySelectorAll(`.${this.getUniqueBlockClassname()}`); this.api.setViewOnly(!!blocks.length); } // Additional block configuration methods... } ``` ### Control API Usage #### Managing UI Element Values Control and synchronize UI element values using the following methods: * `this.api.getValues` - Retrieve current values * `this.api.updateValues` - Update multiple values at once * `this.api.onValueChanged` - Listen for value changes * `this.api.setVisibility` - Show/hide UI elements * `this.api.setUIEAttribute` - Set UI element attributes ```javascript import {Control, UEAttr, UIElementType} from '@stripoinc/ui-editor-extensions'; export class AdvancedControl extends Control { getId() { return 'advanced-control'; } getTemplate() { return `
<${UIElementType.LABEL} ${UEAttr.LABEL.text}="Settings:"> <${UIElementType.SWITCHER} ${UEAttr.SWITCHER.name}="enableFeature">
<${UIElementType.COUNTER} ${UEAttr.COUNTER.name}="itemCount" ${UEAttr.COUNTER.minValue}="1" ${UEAttr.COUNTER.maxValue}="10" ${UEAttr.COUNTER.step}="1"> <${UIElementType.SELECTPICKER} ${UEAttr.SELECTPICKER.name}="styleSelect" style="float: right"> <${UIElementType.SELECT_ITEM} ${UEAttr.SELECT_ITEM.value}="modern" ${UEAttr.SELECT_ITEM.text}="Modern"> <${UIElementType.SELECT_ITEM} ${UEAttr.SELECT_ITEM.value}="classic" ${UEAttr.SELECT_ITEM.text}="Classic">
`; } onRender() { // Set initial values for all UI elements this.api.updateValues({ 'enableFeature': true, 'itemCount': 5, 'styleSelect': 'modern' }); // Listen to individual value changes this.api.onValueChanged('enableFeature', (enabled, wasEnabled) => { // Show/hide related controls based on toggle this.api.setVisibility('itemCount', enabled); this.api.setVisibility('styleSelect', enabled); if (enabled) { this.getAllValues(); // Log current values } }); this.api.onValueChanged('styleSelect', (newValue, oldValue) => { // Set counter max value based on selected style const maxValue = newValue === 'modern' ? 10 : 20; this.api.setUIEAttribute('itemCount', UEAttr.COUNTER.maxValue, maxValue); }); } getAllValues() { // Get all current values at once const values = this.api.getValues(); console.log('Current control state:', values); return values; } } ``` ### UI Element API The UI Element API provides methods for custom UI components. #### Value Change Notification Notify the editor when UI element values change: ```javascript import {UIElement} from '@stripoinc/ui-editor-extensions'; export class CustomSlider extends UIElement { getId() { return 'custom-slider'; } getTemplate() { return `
50
`; } onRender(container) { this.slider = container.querySelector('.slider'); this.display = container.querySelector('.value-display'); this.slider.addEventListener('input', (event) => { const value = parseInt(event.target.value); this.display.textContent = value; // Notify the editor of the value change this.api.onValueChanged(value); }); } getValue() { return parseInt(this.slider.value); } setValue(value) { this.slider.value = value; this.display.textContent = value; } } ``` --- --- url: https://plugin.stripo.email/extensions/tutorials/how-to/template-aliases.md --- # Template Aliases Usage Guide ## What is a Template Alias? Template aliases are special markup shortcuts designed to simplify extension development by eliminating the need to learn complex standard block markup. When a block is created, these aliases are automatically converted into the appropriate HTML markup. We strongly recommend using template aliases for the following reasons: * **Cross-client compatibility**: Aliases ensure consistent appearance across different email clients * **Built-in functionality**: Leverage pre-configured standard controls that work seamlessly with the generated markup * **Reduced complexity**: Eliminate the need to manually implement layout logic, such as padding adjustments * **Maintenance efficiency**: Simplify code maintenance and reduce potential markup errors ## Examples ### Creating a Simple Block Template aliases can be used within simple blocks to create structured content elements. When using aliases in this context, wrap them in a `` tag, as each alias generates a `` root element. ```javascript import {Block, BlockAttr, BlockCompositionType, BlockType} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { getBlockCompositionType() { return BlockCompositionType.BLOCK; } getTemplate() { const {BLOCK_IMAGE, BLOCK_TEXT, BLOCK_BUTTON} = BlockType; return ` ` } // Additional block configuration methods... } ``` ### Creating a Container You can create container markup using template aliases to group related content elements. The `allowInnerBlocksSelection()` and `allowInnerBlocksDND()` methods provide fine-grained control over user interactions within the container: * `allowInnerBlocksSelection()`: Controls whether users can select individual blocks within the container * `allowInnerBlocksDND()`: Controls whether users can drag and drop blocks within the container ```javascript import {Block, BlockAttr, BlockCompositionType, BlockType} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { getBlockCompositionType() { return BlockCompositionType.CONTAINER; } getTemplate() { const {CONTAINER, BLOCK_TEXT, BLOCK_BUTTON} = BlockType; return ` <${CONTAINER}> <${BLOCK_TEXT}>

Lorem ipsum dolor sit amet

<${BLOCK_BUTTON} ${BlockAttr.BLOCK_BUTTON.href}="https://stripo.email"> Click me ` } allowInnerBlocksSelection() { return false; // Disable selection of blocks inside the container } allowInnerBlocksDND() { return false; // Disable drag and drop of blocks inside the container } // Additional block configuration methods... } ``` ### Creating a Structure Structures allow you to create multi-column layouts using template aliases. When creating structures, ensure that the combined width of all containers within the structure totals 100% for proper layout rendering. The `allowInnerBlocksSelection()` and `allowInnerBlocksDND()` methods provide the same interaction controls as containers, allowing you to manage user behavior within the structure's containers. ```javascript import {Block, BlockAttr, BlockCompositionType, BlockType} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { getBlockCompositionType() { return BlockCompositionType.STRUCTURE; } getTemplate() { const {STRUCTURE, CONTAINER, BLOCK_TEXT, BLOCK_BUTTON} = BlockType; // Create a two-column structure: // - First column: 50% width containing a Text Block // - Second column: 50% width containing a Button Block return ` <${STRUCTURE}> <${CONTAINER} ${BlockAttr.CONTAINER.widthPercent}="50"> <${BLOCK_TEXT}>

Lorem ipsum dolor sit amet

<${CONTAINER} ${BlockAttr.CONTAINER.widthPercent}="50"> <${BLOCK_BUTTON} ${BlockAttr.BLOCK_BUTTON.href}="https://stripo.email"> Click me ` } allowInnerBlocksSelection() { return true; // Allow selection of blocks inside the structure } allowInnerBlocksDND() { return false; // Disable drag and drop of blocks inside the structure } // Additional block configuration methods... } ``` #### Adding an Empty Container Empty containers serve as placeholders that users can populate with content. You can define empty containers using template aliases to create flexible layout structures. ```javascript import {Block, BlockAttr, BlockCompositionType, BlockType} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { getBlockCompositionType() { return BlockCompositionType.STRUCTURE; } // Create a two-column structure: // - First column: 50% width containing an Empty Container // - Second column: 50% width containing a Button Block getTemplate() { const {STRUCTURE, CONTAINER, EMPTY_CONTAINER, BLOCK_BUTTON} = BlockType; return ` <${STRUCTURE}> <${EMPTY_CONTAINER} ${BlockAttr.EMPTY_CONTAINER.widthPercent}="50"> <${CONTAINER} ${BlockAttr.CONTAINER.widthPercent}="50"> <${BLOCK_BUTTON} ${BlockAttr.BLOCK_BUTTON.href}="https://stripo.email"> Click me ` } allowInnerBlocksSelection() { return true; // Allow selection of blocks inside the structure } allowInnerBlocksDND() { return true; // Allow drag and drop of blocks inside the structure } // Additional block configuration methods... } ``` #### Customizing Quick-Add Icons You can customize the quick-add icons that appear inside empty containers to provide users with specific block options. This is accomplished by specifying block IDs in the `blocks` attribute of the empty container. ```javascript import {Block, BlockAttr, BlockCompositionType, BlockType} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { getId() { return 'simple-block'; } getBlockCompositionType() { return BlockCompositionType.STRUCTURE; } getTemplate() { const {STRUCTURE, EMPTY_CONTAINER, BLOCK_IMAGE, BLOCK_TEXT} = BlockType; // Specify block IDs that will appear as quick-add icons in the empty container return ` <${STRUCTURE}> <${EMPTY_CONTAINER} ${BlockAttr.EMPTY_CONTAINER.widthPercent}="100" ${BlockAttr.EMPTY_CONTAINER.blocks}="${BLOCK_IMAGE}, ${BLOCK_TEXT}, simple-block"> ` } allowInnerBlocksSelection() { return true; // Allow selection of blocks inside the structure } allowInnerBlocksDND() { return true; // Allow drag and drop of blocks inside the structure } // Additional block configuration methods... } ``` --- --- url: https://plugin.stripo.email/extensions/tutorials/how-to/settings-panel.md --- # Configure the Settings Panel ## What is a Settings Panel? The Settings Panel in the Stripo Extensions SDK is a customizable configuration interface that: * Displays when users select blocks in the editor * Organizes controls into logical tabs (Settings, Styles, Data, etc.) * Can be customized per block type - both built-in and custom * Maintains consistency across the editor while allowing deep customization * Adapts to different block types automatically ## Examples ### Add New Control Insert a custom control into a specific position within a tab. ```javascript import {SettingsPanelRegistry, SettingsPanelTab, SettingsTab, BlockType} from '@stripoinc/ui-editor-extensions'; class ExtendedButtonSettings extends SettingsPanelRegistry { registerBlockControls(controls) { // Insert the custom control 'my-analytics-control' into the Styles tab // of the Button block at position 1 (second position, after the first control) controls[BlockType.BLOCK_BUTTON] .find(tab => tab.getTabId() === SettingsTab.STYLES) .addControl('my-analytics-control', 1); } } ``` ### Remove Existing Control Remove a control from a block's settings panel. ```javascript class SimplifiedTextSettings extends SettingsPanelRegistry { registerBlockControls(controls) { // Remove 'Right to Left Text Direction' control from Text block settings controls[BlockType.BLOCK_TEXT] .find(tab => tab.getTabId() === SettingsTab.SETTINGS) .deleteControl(TextControls.DIRECTION); } } ``` ### Reorder Controls This example demonstrates: * Reordering the 'Button Text' and 'Alignment on Desktop' controls * Moving the 'Padding' control from the 'Settings' tab to the 'Styles' tab ```javascript class ReorderedButtonSettings extends SettingsPanelRegistry { registerBlockControls(controls) { const buttonPanel = controls[BlockType.BLOCK_BUTTON]; const settingsTab = buttonPanel.find(tab => tab.getTabId() === SettingsTab.SETTINGS); const stylesTab = buttonPanel.find(tab => tab.getTabId() === SettingsTab.STYLES); // Reorder the 'Alignment on Desktop' control to position 1 settingsTab.deleteControl(ButtonControls.ALIGNMENT); settingsTab.addControl(ButtonControls.ALIGNMENT, 1); // Move the 'Padding' control to the 'Styles' tab settingsTab.deleteControl(ButtonControls.INTERNAL_INDENTS); stylesTab.addControl(ButtonControls.INTERNAL_INDENTS, 0); } } ``` ### Multiple Tabs Organize controls into multiple tabs to improve the user experience: ```javascript class ProductBlockSettings extends SettingsPanelRegistry { registerBlockControls(controls) { controls['product-block'] = [ new SettingsPanelTab( SettingsTab.SETTINGS, [ 'display-mode-control' ]), new SettingsPanelTab( 'card', [ 'price-format-control', 'currency-control', ]) .withLabel(this.api.translate('Product Card')) ]; } } ``` ### Custom Tab Labels The default tabs (`SettingsTab.SETTINGS`, `SettingsTab.STYLES`, and `SettingsTab.DATA`) include built-in localized labels.\ For custom tabs, use the `withLabel()` method to provide a translation key, ensuring proper localization across all supported languages: ```javascript class CustomRegistry extends SettingsPanelRegistry { registerBlockControls(controls) { controls['banner-block'] = [ new SettingsPanelTab( 'branding', ['logo-control', 'color-scheme-control']) .withLabel(this.api.translate('Brand Settings')) ]; } } // In your extension builder new ExtensionBuilder() .withLocalization({ 'en': { 'Brand Settings': 'Brand Settings' }, 'es': { 'Brand Settings': 'Configuración de Marca' }, 'fr': { 'Brand Settings': 'Paramètres de Marque' } }) .withSettingsPanelRegistry(CustomRegistry) .build(); ``` --- --- url: https://plugin.stripo.email/extensions/tutorials/examples/coupon-block.md --- # Create a Custom Coupon Block ## Overview The Coupon Block extension demonstrates how to create a custom content block for the Stripo Email Editor. This tutorial walks you through building a reusable coupon code block with customizable styling options, including font family, size, and color controls. ### What You'll Build In this tutorial, you'll create a fully functional custom block that: * Displays a coupon code with a placeholder for dynamic content {{COUPON\_CODE}} * Provides a custom icon for the blocks panel * Includes styling controls in the settings panel (font family, size, and color) * Supports internationalization for multi-language editors * Integrates seamlessly with the Stripo editor's existing UI ::: image-wrap ![](/img/extensions/coupon.png) ::: ### Use Cases * **E-commerce Promotions**: Add discount codes to promotional email campaigns * **Loyalty Programs**: Display member-exclusive coupon codes * **Seasonal Sales**: Highlight special offer codes in marketing emails * **Personalized Offers**: Use merge tags to show customer-specific discount codes ## Prerequisites Before starting this tutorial, ensure you have: * Node.js version 22.x or higher installed * Basic understanding of JavaScript ES6+ syntax * Familiarity with the Stripo Extensions SDK * A text editor or IDE for code development ## Understanding the Components A complete custom block extension consists of several key components: ### 1. Block Definition The core `Block` class that defines the block's structure, appearance, and behavior. ### 2. Settings Panel Registry Configuration that determines which controls appear in the settings panel when the block is selected. ### 3. Custom Controls Reusable control components that allow users to modify block properties (font, color, size, etc.). ### 4. Icon Registry Custom SVG icons that appear in the blocks panel for visual identification. ### 5. Internationalization Translation files that support multiple languages in the editor interface. ## Step 1: Project Setup Create your project structure and install dependencies according to the [Getting Started](/extensions/getting-started) guide. Your project directory structure should look like this: ```bash coupon-block/ ├── index.html ├── src/ │ ├── creds.js │ ├── index.js │ └── extension.js ├── package.json └── vite.config.js ``` ## Step 2: Create the Coupon Block Class Create a new file `src/CouponBlock.js` with the following block definition: ```javascript import {Block, BlockType} from '@stripoinc/ui-editor-extensions'; /** * Coupon Block Component * A custom block for displaying coupon codes in email templates */ export class CouponBlock extends Block { /** * Returns the unique identifier for this block * This ID is used to register the block and reference it in settings * @returns {string} Unique block identifier */ getId() { return 'coupon-block'; } /** * Returns the icon identifier for the blocks panel * This icon appears in the blocks library for users to drag and drop * @returns {string} Icon identifier registered in IconsRegistry */ getIcon() { return 'couponBlockIcon'; } /** * Returns the display name shown in the blocks panel * Uses the translation API for internationalization support * @returns {string} Translated block name */ getName() { return this.api.translate('Coupon'); } /** * Returns the description shown in the blocks panel * Provides users with information about the block's purpose * @returns {string} Translated block description */ getDescription() { return this.api.translate('Add a discount code to your email template'); } /** * Returns the HTML template structure for this block * The template defines the initial markup when the block is inserted * @returns {string} HTML template with merge tag placeholder */ getTemplate() { return ` ` } } ``` ### Key Components Explained * **getId()**: Returns a unique identifier for the block. This ID is used throughout the extension system to reference the block in settings panels, event handlers, and registries. * **getIcon()**: Specifies which icon to display in the blocks panel. The icon identifier must match a key registered in the `IconsRegistry`. * **getName()**: Provides the user-facing name displayed in the blocks panel. Uses `this.api.translate()` for internationalization support. * **getDescription()**: Offers a brief description of the block's purpose, helping users understand when to use it. * **getTemplate()**: Specifies the HTML structure inserted when users add the block to their email. In this implementation, `BlockType.BLOCK_TEXT` is placed inside ``; } /** * Sets up event listeners for a condition row * @param {HTMLElement} row - The row element * @param {string} deleteActionClass - Class for the delete button */ setupConditionRowListeners(row, deleteActionClass) { const CSS = MyExternalDisplayConditions.CSS_CLASSES; const inputElement = row.querySelector(`.${CSS.CONDITION_VALUE}`); // Add focus/hover styles to input inputElement.addEventListener('focus', function() { this.style.borderColor = '#3b82f6'; this.style.boxShadow = '0 0 0 3px rgba(59, 130, 246, 0.1)'; }); inputElement.addEventListener('blur', function() { this.style.borderColor = '#e5e7eb'; this.style.boxShadow = 'none'; }); // Clear validation error when user starts typing inputElement.addEventListener('input', () => { this.hideValidationError(); }); // Add delete button listener const deleteButton = row.querySelector('.' + deleteActionClass); if (deleteButton) { deleteButton.addEventListener('click', this.deleteConditionRow); // Add hover effects deleteButton.addEventListener('mouseenter', function() { this.style.backgroundColor = '#fee2e2'; }); deleteButton.addEventListener('mouseleave', function() { this.style.backgroundColor = 'transparent'; }); } } /** * Adds a new condition row to the table * @param {Event} e - The event object (can be null) * @param {Object} conditionValue - The condition values to populate */ addConditionRow(e, conditionValue) { const CSS = MyExternalDisplayConditions.CSS_CLASSES; if (!conditionValue) { conditionValue = MyExternalDisplayConditions.DEFAULT_CONDITION; } const deleteActionClass = CSS.DELETE_ACTION_PREFIX + Math.random().toString().replace('.', 'd'); const tr = document.createElement('tr'); tr.classList.add(CSS.CONDITION_ROW); tr.innerHTML = this.createConditionRowHTML(deleteActionClass); this.conditionsPopupElement.querySelector(`.${CSS.CONDITIONS_TABLE}`).appendChild(tr); // Set dropdown options and values this.setDropdownOptions(tr, CSS.DROPDOWN_NAME, MyExternalDisplayConditions.AVAILABLE_CONDITION_NAMES); this.setDropdownValue(tr, CSS.DROPDOWN_NAME, conditionValue.name); this.setDropdownOptions(tr, CSS.DROPDOWN_OPERATION, MyExternalDisplayConditions.AVAILABLE_CONDITION_OPERATIONS); this.setDropdownValue(tr, CSS.DROPDOWN_OPERATION, conditionValue.operation); // Set input value const inputElement = tr.querySelector(`.${CSS.CONDITION_VALUE}`); inputElement.value = conditionValue.value; // Setup event listeners this.setupConditionRowListeners(tr, deleteActionClass); this.updateDeleteActionVisibility(); } /** * Deletes a condition row * @param {Event} e - The click event */ deleteConditionRow = (e) => { const CSS = MyExternalDisplayConditions.CSS_CLASSES; const row = e.target.closest(`.${CSS.CONDITION_ROW}`); if (row) { row.remove(); this.updateDeleteActionVisibility(); } } /** * Updates visibility of delete buttons based on row count */ updateDeleteActionVisibility() { const CSS = MyExternalDisplayConditions.CSS_CLASSES; const rows = this.conditionsPopupElement.querySelectorAll(`.${CSS.CONDITIONS_TABLE} .${CSS.CONDITION_ROW}`); if (rows.length > 0) { const firstDeleteButton = rows[0].querySelector('button[class*="condition-delete-action"]'); if (firstDeleteButton) { // Hide delete button for first row if it's the only row firstDeleteButton.style.display = rows.length > 1 ? 'flex' : 'none'; } } } ``` ### Row Management Features 1. **Dynamic Row Creation**: Each condition row contains three dropdowns (field, operation) and one text input (value) 2. **Unique Delete Buttons**: Each row gets a uniquely identified delete button 3. **Visual Feedback**: Hover effects and focus states provide clear user feedback 4. **Smart Delete Visibility**: The first row's delete button is hidden when it's the only row 5. **Validation Integration**: Input changes clear any displayed validation errors ## Step 7: Implement Validation Add validation methods to ensure user input is complete: ```javascript /** * Shows a validation error message * @param {string} message - The error message to display */ showValidationError(message) { // Remove any existing error message this.hideValidationError(); // Create error element const errorDiv = document.createElement('div'); errorDiv.className = 'validation-error'; errorDiv.style.cssText = ` background-color: #fef2f2; border: 1px solid #fecaca; color: #dc2626; padding: 12px 16px; border-radius: 6px; margin-bottom: 16px; font-size: 14px; display: flex; align-items: center; gap: 8px; `; errorDiv.innerHTML = ` ${message} `; // Insert at the beginning of the content area const contentDiv = this.conditionsPopupElement.querySelector('[style*="padding: 32px"]'); if (contentDiv) { contentDiv.insertBefore(errorDiv, contentDiv.firstChild); } } /** * Hides the validation error message */ hideValidationError() { const existingError = this.conditionsPopupElement.querySelector('.validation-error'); if (existingError) { existingError.remove(); } } ``` ## Step 8: Implement Condition Application Logic Add the logic to collect, validate, and format conditions: ```javascript /** * Applies the conditions and closes the modal */ applyConditions() { const CSS = MyExternalDisplayConditions.CSS_CLASSES; const MSG = MyExternalDisplayConditions.MESSAGES; const conditions = []; const rows = this.conditionsPopupElement.querySelectorAll(`.${CSS.CONDITIONS_TABLE} .${CSS.CONDITION_ROW}`); // Collect conditions with non-empty values for (let i = 0; i < rows.length; i++) { const row = rows[i]; const value = row.querySelector(`.${CSS.CONDITION_VALUE}`).value; if (value.length) { conditions.push({ name: this.getDropdownValue(row, CSS.DROPDOWN_NAME), operation: this.getDropdownValue(row, CSS.DROPDOWN_OPERATION), value }); } } // Validation: at least one condition must have a value if (conditions.length === 0) { this.showValidationError(MSG.VALIDATION_ERROR); return; } // Build the final condition script const concatenation = this.getDropdownValue(this.conditionsPopupElement, CSS.DROPDOWN_CONCATENATION); const finalCondition = conditions.map(condition => { return condition.operation + '(\'' + condition.value + '\', ' + condition.name + ')'; }).join(' ' + concatenation + ' '); // Call the success callback this.selectConditionsCallback({ name: MSG.CONDITION_NAME, description: MSG.CONDITION_DESCRIPTION, beforeScript: '%IF ' + finalCondition + '%', afterScript: '%/IF%' }); this.closePopup(); } /** * Removes all conditions */ removeConditions() { this.selectConditionsCallback(null); this.closePopup(); } /** * Cancels the modal without applying changes */ cancelConditions() { this.onCancelCallback(); this.closePopup(); } /** * Closes the modal */ closePopup() { this.conditionsPopupElement.style.visibility = 'hidden'; this.hideValidationError(); } ``` ### Condition Script Format The `applyConditions` method generates a script in this format: **Single condition:** ``` %IF equals('test@gmail.com', $EMAIL)% ``` **Multiple conditions with AND:** ``` %IF equals('test@gmail.com', $EMAIL) && in_array('premium', $PHONE)% ``` **Multiple conditions with OR:** ``` %IF equals('test@gmail.com', $EMAIL) || equals('test@yahoo.com', $EMAIL)% ``` ## Step 9: Implement Condition Parsing Add methods to parse existing conditions when editing: ```javascript /** * Activates the conditions popup * @param {DisplayCondition} appliedCondition - Currently applied condition */ activateConditionsPopup(appliedCondition) { if (!this.conditionsPopupElement) { this.createConditionsPopup(); } this.initConditions(appliedCondition); this.conditionsPopupElement.style.visibility = 'visible'; } /** * Initializes conditions from applied condition data * @param {DisplayCondition} appliedCondition - The applied condition object */ initConditions(appliedCondition) { const CSS = MyExternalDisplayConditions.CSS_CLASSES; // Clear existing conditions const table = this.conditionsPopupElement.querySelector(`.${CSS.CONDITIONS_TABLE}`); if (table) { table.innerHTML = ''; } // Parse and add conditions const initialConditions = this.parseAppliedCondition(appliedCondition.beforeScript); initialConditions.conditions.forEach(condition => { this.addConditionRow(null, condition); }); // Set concatenation value this.setDropdownValue(this.conditionsPopupElement, CSS.DROPDOWN_CONCATENATION, initialConditions.concatenation); } /** * Parses an applied condition string into its components * @param {string} appliedCondition - The condition string * @returns {Object} Parsed condition object with conditions array and concatenation */ parseAppliedCondition(appliedCondition) { // Remove wrapper tags const str = appliedCondition .trim() .replace('%IF ', '') .replace('%/IF%', ''); // Find concatenation operator const concatenation = this.findConditionOptionValue( str, MyExternalDisplayConditions.AVAILABLE_CONDITION_CONCATENATIONS ); // Split by concatenation and parse individual conditions const conditions = str .split(concatenation) .map((conditionStr) => { // Extract value between quotes const valueMatch = conditionStr.match(/'([^']+)'/); const value = valueMatch ? valueMatch[1] : ''; return { name: this.findConditionOptionValue( conditionStr, MyExternalDisplayConditions.AVAILABLE_CONDITION_NAMES ), operation: this.findConditionOptionValue( conditionStr, MyExternalDisplayConditions.AVAILABLE_CONDITION_OPERATIONS ), value }; }); return { conditions, concatenation }; } /** * Finds the value of an option that exists in the given string * @param {string} str - The string to search in * @param {Array} options - Array of option objects with value property * @returns {string} The found option value or first option's value as default */ findConditionOptionValue(str, options) { const foundOption = options.find(option => str.includes(option.value)); return foundOption ? foundOption.value : options[0].value; } ``` ### Parsing Logic The parsing logic handles: 1. **Script Cleanup**: Removes `%IF` and `%/IF%` wrapper tags 2. **Concatenation Detection**: Identifies `&&` (AND) or `||` (OR) operators 3. **Condition Splitting**: Splits multiple conditions based on the concatenation operator 4. **Value Extraction**: Uses regex to extract values between single quotes 5. **Field Detection**: Matches field names (`$EMAIL`, `$PHONE`) from the condition string 6. **Operation Detection**: Identifies the operation (`equals`, `in_array`) used ## Step 10: Register the Extension Create `src/extension.js` to register your display conditions library with the Stripo extension system: ```javascript import {ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; import {MyExternalDisplayConditions} from './MyExternalDisplayConditions'; const extension = new ExtensionBuilder() .withExternalDisplayCondition(MyExternalDisplayConditions) .build(); export default extension; ``` ### Extension Registration Explained The `ExtensionBuilder` class provides a fluent API for registering integrations: * **`withExternalDisplayCondition()`**: Registers your custom display conditions implementation * **`build()`**: Constructs and returns the final extension object for the editor ::: tip Multiple Integrations Multiple `.with*()` methods can be chained to register different integrations within a single extension: ```javascript new ExtensionBuilder() .withExternalDisplayCondition(MyExternalDisplayConditions) .withExternalImageLibrary(MyExternalImageLibrary) .withExternalVideosLibrary(MyExternalVideosLibrary) .build(); ``` ::: ## Step 11: Enable Conditions in Editor Configuration Update your `src/index.js` to enable display conditions: ```javascript import extension from './extension.js'; import {PLUGIN_ID, SECRET_KEY, EDITOR_URL, EMAIL_ID, USER_ID} from './creds'; function _runEditor(template, extension) { window.UIEditor.initEditor( document.querySelector('#stripoEditorContainer'), { html: template.html, css: template.css, metadata: { emailId: EMAIL_ID }, locale: 'en', conditionsEnabled: true, // IMPORTANT: Enable display conditions extensions: [ extension ], // ... other configuration } ); } ``` ::: warning Important Configuration You **must** set `conditionsEnabled: true` in the editor configuration for display conditions to work. Without this flag, the display conditions UI will not appear in the editor. ::: ## Step 12: Run the Development Server Your implementation is now ready for testing. ### Start the Development Server ```bash npm run dev ``` This command will: 1. Start the Vite development server on `http://localhost:3000` 2. Automatically open your default browser 3. Load the Stripo Editor with your display conditions extension integrated ## Complete Example For a full working example, check out the complete implementation in our [GitHub repository](https://github.com/stripoinc/stripo-plugin-samples/tree/main/extensions/javascript/external-display-conditions). --- --- url: >- https://plugin.stripo.email/extensions/tutorials/examples/integrations/external-custom-font.md --- # Integrate an External Custom Font ## Overview The External Custom Font integration allows users to add and manage custom web fonts directly within the Stripo Email Editor. This integration provides a seamless experience for incorporating brand-specific typography, web fonts from CDNs, or self-hosted font files into email templates while maintaining full control over your font library. ### What You'll Build In this tutorial, you'll create a fully functional custom font selector that: * Extends the default font family dropdown with an "Add Custom Font" option * Opens a modal dialog to collect font information (name, CSS font-family, URL) * Validates all required font properties before submission * Integrates seamlessly with the editor's font management system * Provides visual feedback with modern, user-friendly interface ::: image-wrap ![](/img/plugin/new/image216.webp){width=1999 height=795} ::: ::: image-wrap ![](/img/plugin/new/image217.webp){width=580} ::: ::: image-wrap ![](/img/plugin/new/image218.webp){width=1999 height=788} ::: ### Use Cases * **Brand Typography**: Incorporate your organization's proprietary fonts into email templates * **Google Fonts Integration**: Add fonts from Google Fonts or other web font CDNs * **Self-Hosted Fonts**: Connect to fonts hosted on your own servers or CDN * **Font Marketplace Integration**: Link to third-party font providers like Adobe Fonts or Typekit * **Multi-Brand Support**: Enable different font sets for different brands or clients ## Prerequisites Before starting this tutorial, ensure you have: * Node.js version 22.x or higher installed * Basic understanding of JavaScript ES6+ syntax * Familiarity with the Stripo Extensions SDK * Understanding of CSS `@font-face` and web fonts ## Understanding the Interface The External Custom Font integration requires two key components: ### 1. Custom UI Element Create a custom UI element that extends `UIElement` to wrap the default font selector and add custom functionality: ```typescript class CustomFontFamilySelectUIElement extends UIElement { getId(): string; // Unique element ID getTemplate(): string; // HTML template wrapping original selector onRender(container: HTMLElement): void; // Setup event listeners onDestroy(): void; // Cleanup event listeners onAttributeUpdated(name: string, value: any): void; // Handle attribute changes getValue(): string; // Get current font value setValue(value: string): void; // Set font value programmatically } ``` ### 2. UI Element Tag Registry Register your custom UI element to replace the default font family selector: ```typescript class ExtensionTagRegistry extends UIElementTagRegistry { registerUiElements(uiElementsTagsMap: Record): void; } ``` ### Custom Font Object Structure When adding a custom font, your implementation must provide an object with the following structure: ```typescript { name: string; // Display name (e.g., "Montserrat Bold") fontFamily: string; // CSS font-family value (e.g., "'Montserrat', sans-serif") url: string; // Font resource URL (e.g., "https://fonts.googleapis.com/...") } ``` ::: tip Font URL Formats Font URLs can point to various resources: * **Google Fonts**: `https://fonts.googleapis.com/css2?family=Roboto:wght@400;700` * **Self-hosted**: `https://cdn.yoursite.com/fonts/custom-font.woff2` * **Adobe Fonts**: `https://use.typekit.net/abc1234.css` * Any valid web font resource that uses `@font-face` CSS rules ::: ## Step 1: Project Setup Create your project structure and install dependencies according to the [Getting Started](/extensions/getting-started) guide. Your project directory structure should look like this: ```bash external-custom-font/ ├── index.html ├── src/ │ ├── creds.js │ ├── index.js │ ├── extension.js ├── package.json └── vite.config.js ``` ## Step 2: Create the Custom Font Selector UI Element Create a new file `src/CustomFontFamilySelectUIElement.js` with the following implementation: ```javascript import {ADD_CUSTOM_FONT_OPTION, UEAttr, UIElement} from '@stripoinc/ui-editor-extensions'; export const CUSTOM_FONT_FAMILY_SELECT_UI_ELEMENT_ID = 'custom-font-family-select'; export const ORIGINAL_FONT_FAMILY_SELECT_ID = 'original-font-family-select'; export class CustomFontFamilySelectUIElement extends UIElement { getId() { return CUSTOM_FONT_FAMILY_SELECT_UI_ELEMENT_ID; } getTemplate() { return `<${ORIGINAL_FONT_FAMILY_SELECT_ID} id="originalSelect" style="width: 100%;" ${UEAttr.FONT_FAMILY_SELECT.addCustomFontOption}="+ Insert custom font"> `; } onRender(container) { this.listener = this._onChange.bind(this); this.originalSelect = container.querySelector('#originalSelect'); this.originalSelect.addEventListener('change', this.listener); } onDestroy() { this.originalSelect.removeEventListener('change', this.listener); } onAttributeUpdated(name, value) { this.originalSelect.setUIEAttribute(name, value); super.onAttributeUpdated(name, value); } getValue() { return this.originalSelect.value; } setValue(value) { this.originalSelect.value = value; } _onChange(event) { if (event.target.value !== ADD_CUSTOM_FONT_OPTION) { this.api.triggerValueChange(event.target.value); } else if (!this.dialog) { this._showDialog(); } } } ``` ### Key Components Explained * **Template Structure**: * Wraps the original font selector using `ORIGINAL_FONT_FAMILY_SELECT_ID` * Adds custom option via `UEAttr.FONT_FAMILY_SELECT.addCustomFontOption` attribute * The custom option text is "+ Insert custom font" * **Event Handling**: * `onRender()`: Attaches change listener to the select element * `_onChange()`: Distinguishes between regular font selection and custom font trigger * When `ADD_CUSTOM_FONT_OPTION` is selected, opens the custom font dialog * **Value Management**: * `getValue()` and `setValue()`: Proxy methods that delegate to the original select * `onAttributeUpdated()`: Forwards attribute changes to the wrapped element ::: warning Important Constants The SDK provides `ADD_CUSTOM_FONT_OPTION` constant to identify the special custom font option. Never hardcode this value, always import it from the SDK. ::: ## Step 3: Create the Custom Font Dialog Add the dialog creation and management methods to `CustomFontFamilySelectUIElement.js`: ### Dialog Display Method ```javascript _showDialog() { this.dialog = document.createElement('div'); this.dialog.innerHTML = this._getDialogTemplate(); this.api.ignoreClickOutside(true); document.body.appendChild(this.dialog); // Add event listeners this.dialog.querySelector('#confirm').addEventListener('click', () => this._submitDialog()); this.dialog.querySelector('#cancel').addEventListener('click', () => this._closeDialog()); // Add input event listeners to hide error message when user starts typing const inputs = this.dialog.querySelectorAll('input'); inputs.forEach(input => { input.addEventListener('input', () => { const errorMsg = this.dialog.querySelector('#error-message'); if (errorMsg) { errorMsg.style.display = 'none'; } }); }); } ``` ### Dialog Template Method ```javascript _getDialogTemplate() { return `

Add Custom Font

Configure your custom font settings

All fields are required. Please fill in all the information.

⚠️ Notice: This popup window is not part of the plugin. It is intended solely for demonstration purposes and can be implemented independently in any desired way.

`; } ``` ### Dialog Features Explained 1. **Modal Overlay**: Fixed position overlay with semi-transparent background and blur effect 2. **Form Fields**: * **Font Name**: Display name shown in the font selector dropdown * **CSS Font Family**: The actual CSS `font-family` value (e.g., "'Roboto', sans-serif") * **Font URL**: Link to the font resource or stylesheet 3. **Input Enhancement**: Focus states with blue border and shadow for better UX 4. **Error Display**: Hidden error message that shows when validation fails 5. **Animations**: Smooth fade-in animation using CSS keyframes 6. **Responsive Design**: Uses `max-width: 90vw` to ensure dialog fits on small screens ::: tip Click Outside Prevention The `this.api.ignoreClickOutside(true)` call prevents the editor from closing the dialog when clicking inside it. Remember to call `ignoreClickOutside(false)` when closing the dialog. ::: ## Step 4: Implement Dialog Submission and Validation Add the submission and validation logic to handle font addition: ### Submit Dialog Method ```javascript _submitDialog() { const nameInput = this.dialog.querySelector('#name'); const fontFamilyInput = this.dialog.querySelector('#fontFamily'); const urlInput = this.dialog.querySelector('#url'); // Reset any previous error states [nameInput, fontFamilyInput, urlInput].forEach(input => { input.style.borderColor = '#d1d5db'; }); // Validate inputs let hasError = false; if (!nameInput.value.trim()) { nameInput.style.borderColor = '#ef4444'; hasError = true; } if (!fontFamilyInput.value.trim()) { fontFamilyInput.style.borderColor = '#ef4444'; hasError = true; } if (!urlInput.value.trim()) { urlInput.style.borderColor = '#ef4444'; hasError = true; } if (hasError) { // Show error message const errorMsg = this.dialog.querySelector('#error-message'); if (errorMsg) { errorMsg.style.display = 'block'; } return; } const newFont = { name: nameInput.value.trim(), fontFamily: fontFamilyInput.value.trim(), url: urlInput.value.trim(), } this.api.addCustomFont(newFont); this._closeDialog(); } ``` ### Close Dialog Method ```javascript _closeDialog() { if (this.dialog) { this.dialog.remove(); this.dialog = undefined; this.api.ignoreClickOutside(false); this.originalSelect.value = ''; } } ``` ### Validation Logic Explained 1. **Input Retrieval**: Gets references to all three input fields 2. **State Reset**: Clears any previous error border colors 3. **Validation**: * Checks each field is not empty using `trim()` * Highlights invalid fields with red border (`#ef4444`) * Sets `hasError` flag when validation fails 4. **Error Display**: Shows error message if any field is invalid 5. **Font Registration**: If validation passes, calls `this.api.addCustomFont()` with font object 6. **Cleanup**: Closes dialog and resets the select value ::: tip Font Family Format The `fontFamily` field should follow CSS `font-family` syntax: * Single font: `'Roboto'` * With fallback: `'Roboto', sans-serif` * Multiple words: `'Open Sans', Arial, sans-serif` Always include quotes around font names with spaces. ::: ## Step 5: Create the UI Element Tag Registry Create `src/ExtensionTagRegistry.js` to register your custom font selector as a replacement for the default: ```javascript import {UIElementTagRegistry, UIElementType} from '@stripoinc/ui-editor-extensions'; import {CUSTOM_FONT_FAMILY_SELECT_UI_ELEMENT_ID, ORIGINAL_FONT_FAMILY_SELECT_ID} from './CustomFontFamilySelectUIElement'; export class ExtensionTagRegistry extends UIElementTagRegistry { registerUiElements(uiElementsTagsMap) { uiElementsTagsMap[ORIGINAL_FONT_FAMILY_SELECT_ID] = uiElementsTagsMap[UIElementType.FONT_FAMILY_SELECT]; uiElementsTagsMap[UIElementType.FONT_FAMILY_SELECT] = CUSTOM_FONT_FAMILY_SELECT_UI_ELEMENT_ID; } } ``` ### Tag Registry Logic Explained The registry performs two critical mappings: 1. **Preserve Original Selector**: ```javascript uiElementsTagsMap[ORIGINAL_FONT_FAMILY_SELECT_ID] = uiElementsTagsMap[UIElementType.FONT_FAMILY_SELECT]; ``` * Saves the original font selector implementation * Makes it available under `ORIGINAL_FONT_FAMILY_SELECT_ID` * Allows your custom element to wrap and reuse the original 2. **Replace Default Selector**: ```javascript uiElementsTagsMap[UIElementType.FONT_FAMILY_SELECT] = CUSTOM_FONT_FAMILY_SELECT_UI_ELEMENT_ID; ``` * Maps the standard font selector type to your custom implementation * Ensures all font selector instances use your enhanced version * Seamlessly integrates with the editor without modifying existing code ::: warning Order Matters The order of these two statements is critical. First save the original selector reference, then replace it with your custom implementation. Reversing the order will break the functionality. ::: ## Step 6: Register the Extension Create `src/extension.js` to register your custom font integration with the Stripo extension system: ```javascript import {ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; import {CustomFontFamilySelectUIElement} from './CustomFontFamilySelectUIElement'; import {ExtensionTagRegistry} from './ExtensionTagRegistry'; const extension = new ExtensionBuilder() .addUiElement(CustomFontFamilySelectUIElement) .withUiElementTagRegistry(ExtensionTagRegistry) .build(); export default extension; ``` ### Extension Registration Explained The `ExtensionBuilder` provides a fluent API for composing extensions: 1. **`.addUiElement(CustomFontFamilySelectUIElement)`**: * Registers your custom UI element class with the extension system * Makes the element available for use in the editor * The editor will instantiate this class when needed 2. **`.withUiElementTagRegistry(ExtensionTagRegistry)`**: * Registers your tag registry to control UI element mapping * Tells the editor to replace the default font selector with your custom one * Enables the wrapper pattern by preserving access to the original selector 3. **`.build()`**: * Constructs and returns the final extension configuration object * This object is passed to the editor during initialization ::: tip Multiple Integrations You can chain multiple integration methods within a single extension: ```javascript new ExtensionBuilder() .addUiElement(CustomFontFamilySelectUIElement) .withUiElementTagRegistry(ExtensionTagRegistry) .withExternalImageLibrary(MyExternalImageLibrary) .withExternalVideosLibrary(MyExternalVideosLibrary) .build(); ``` This allows combining custom fonts with other external integrations. ::: ## Step 7: Run the Development Server Your implementation is now ready for testing. ### Start the Development Server ```bash npm run dev ``` This command will: 1. Start the Vite development server on `http://localhost:3000` 2. Automatically open your default browser 3. Load the Stripo Editor with your custom font integration ## Complete Example For a full working example, check out the complete implementation in our [GitHub repository](https://github.com/stripoinc/stripo-plugin-samples/tree/main/extensions/javascript/external-custom-font). --- --- url: https://plugin.stripo.email/extensions/reference.md --- # Stripo Extensions SDK API Reference Comprehensive API documentation for the Stripo Email Editor Extensions SDK. ## Core Interfaces ### API Interfaces | API | Description | |---------------------------------------------------------|------------------------------------------------------------| | [BaseApi](./reference/api/BaseApi.md) | Core API functionality for all extension components | | [BaseModifierApi](./reference/api/BaseModifierApi.md) | Document modification capabilities | | [BlockApi](./reference/api/BlockApi.md) | API interface for Block implementations | | [BlockRendererApi](./reference/api/BlockRendererApi.md) | API interface for BlockRenderer implementations | | [BlocksPanelApi](./reference/api/BlocksPanelApi.md) | API interface for custom blocks panel implementations | | [ContextActionApi](./reference/api/ContextActionApi.md) | API interface for context action implementations | | [ControlApi](./reference/api/ControlApi.md) | API interface for control implementations | | [GeneralPanelTabApi](./reference/api/GeneralPanelTabApi.md) | API interface for general panel tab implementations | | [ModulesPanelTabApi](./reference/api/ModulesPanelTabApi.md) | API interface for modules panel tab implementations | | [SettingsPanelApi](./reference/api/SettingsPanelApi.md) | API interface for settings panel tab implementations | | [UIElementApi](./reference/api/UIElementApi.md) | API interface for UI element implementations | ### Block Components | API | Description | |---------------------------------------------------------|------------------------------------------------------------| | [Block](./reference/blocks/Block.md) | Base abstract class for creating custom blocks | | [BlockRenderer](./reference/blocks/BlockRenderer.md) | Base class for custom block rendering | | [BlocksPanel](./reference/blocks/BlocksPanel.md) | Base class for creating custom blocks panels | | [ContextAction](./reference/blocks/ContextAction.md) | Abstract base class for context menu actions | ### Control Components | API | Description | |---------------------------------------------------------|------------------------------------------------------------| | [Control](./reference/controls/Control.md) | Abstract base class for creating custom controls | | [GeneralPanelTab](./reference/controls/GeneralPanelTab.md) | Base class for creating general panel tabs | | [ModulesPanelTab](./reference/controls/ModulesPanelTab.md) | Base class for creating modules panel tabs | ### Core Components | API | Description | |---------------------------------------------------------|------------------------------------------------------------| | [Extension](./reference/core/Extension.md) | Base class for creating Stripo extensions | | [ExtensionBuilder](./reference/core/ExtensionBuilder.md) | Builder class for constructing extension configurations | ### Template Modification | API | Description | |--------------------------------------------------------------------------|--------------------------------------------------------------| | [TemplateModifier](./reference/modification/TemplateModifier.md) | Base interface for managing template modifications | | [HtmlNodeModifier](./reference/modification/HtmlNodeModifier.md) | Interface for modifying HTML nodes in email templates | | [CssNodeModifier](./reference/modification/CssNodeModifier.md) | Interface for modifying CSS rules and properties | | [MultiRowStructureModifier](./reference/modification/MultiRowStructureModifier.md) | Interface for creating and modifying email structure layouts | | [ModificationDescription](./reference/modification/ModificationDescription.md) | Class for providing context about template modifications | ### Node Interfaces #### Base Node Interfaces | API | Description | |-------------------------------------------------------------------|----------------------------------------------------------| | [BaseImmutableNode](./reference/nodes/BaseImmutableNode.md) | Common interface for all immutable nodes | | [BaseImmutableHtmlNode](./reference/nodes/BaseImmutableHtmlNode.md) | HTML-specific base with type casting method | | [BaseImmutableCssNode](./reference/nodes/BaseImmutableCssNode.md) | CSS-specific base with type casting and comment checking | #### HTML Node Interfaces | API | Description | |-------------------------------------------------------------------|----------------------------------------------------------| | [ImmutableHtmlNode](./reference/nodes/ImmutableHtmlNode.md) | Base interface for immutable HTML nodes | | [ImmutableHtmlElementNode](./reference/nodes/ImmutableHtmlElementNode.md) | HTML elements with attributes, styles, and methods | | [ImmutableHtmlTextNode](./reference/nodes/ImmutableHtmlTextNode.md) | Text content nodes | #### CSS Node Interfaces | API | Description | |-------------------------------------------------------------------|----------------------------------------------------------| | [ImmutableCssNode](./reference/nodes/ImmutableCssNode.md) | Base interface for immutable CSS nodes | | [ImmutableCssRuleNode](./reference/nodes/ImmutableCssRuleNode.md) | CSS rules with selectors | | [ImmutableCssAttributeNode](./reference/nodes/ImmutableCssAttributeNode.md) | CSS properties/attributes | | [ImmutableCssCommentNode](./reference/nodes/ImmutableCssCommentNode.md) | CSS comments | | [ImmutableCssDocumentNode](./reference/nodes/ImmutableCssDocumentNode.md) | Document and media query containers | ### Types and Interfaces | API | Description | |-----|-------------| | [AIPopoverOptions](./reference/types/AIPopoverOptions.md) | Configuration for AI-assisted popovers | | [BlockHint](./reference/types/BlockHint.md) | Configuration for block hints and tooltips | | [BlockItem](./reference/types/BlockItem.md) | Configuration for block items in panels | | [CustomFontFamily](./reference/types/CustomFontFamily.md) | Configuration for custom font families | | [DisplayCondition](./reference/types/DisplayCondition.md) | Configuration for conditional element visibility | | [EditorPermissions](./reference/types/EditorPermissions.md) | Interface describing the current user's editor permissions | | [EmojiPopoverOptions](./reference/types/EmojiPopoverOptions.md) | Configuration for emoji picker popovers | | [HideElementState](./reference/types/HideElementState.md) | Type for device-specific hidden element state | | [StructureLayout](./reference/types/StructureLayout.md) | Type for defining container layouts in structures | ### Constants and Enums | API | Description | |-----|------------------------------------------------------------| | [AiAssistantValueType](./reference/constants/AiAssistantValueType.md) | Enum for AI assistant value types | | [BlockAttr](./reference/constants/BlockAttr.md) | Enum for block attribute constants | | [BlockCompositionType](./reference/constants/BlockCompositionType.md) | Enum defining block composition types | | [BlockType](./reference/constants/BlockType.md) | Enum for block type definitions | | [ContextActionType](./reference/constants/ContextActionType.md) | Enum for context action type definitions | | [EditorState](./reference/constants/EditorState.md) | Interface for the active editor state object | | [EditorStatePropertyType](./reference/constants/EditorStatePropertyType.md) | Enum for observable editor state properties | | [OrderableItemIconPosition](./reference/constants/OrderableItemIconPosition.md) | Enum for orderable UI element drag handle icon positioning | | [PanelPosition](./reference/constants/PanelPosition.md) | Enum for defining panel positioning options | | [PreviewDeviceMode](./reference/constants/PreviewDeviceMode.md) | Enum for preview device mode settings | | [SettingsTab](./reference/constants/SettingsTab.md) | Enum for settings tab definitions | | [ThemeMode](./reference/constants/ThemeMode) | Enum for email template theme mode values | | [UEAttr](./reference/constants/UEAttr.md) | Enum for UI element attribute constants | | [UIElementType](./reference/constants/UIElementType.md) | Enum for UI element type definitions | ### Integrations | API | Description | |-----|----------------------------------------------------------------------| | [ExternalAiAssistant](./reference/integrations/ExternalAiAssistant.md) | Interface for integrating external AI assistants | | [ExternalDisplayConditionsLibrary](./reference/integrations/ExternalDisplayConditionsLibrary.md) | Interface for integrating external display condition systems | | [ExternalImageLibrary](./reference/integrations/ExternalImageLibrary.md) | Interface for integrating external image libraries | | [ExternalImageLibraryTab](./reference/integrations/ExternalImageLibraryTab.md) | Interface for creating a custom tab within image libraries (v3.2.0+) | | [ExternalSmartElementsLibrary](./reference/integrations/ExternalSmartElementsLibrary.md) | Interface for integrating external smart elements libraries | | [ExternalVideosLibrary](./reference/integrations/ExternalVideosLibrary.md) | Interface for integrating external video libraries | ### Settings Panel | API | Description | |-----|-------------| | [SettingsPanelRegistry](./reference/settings-panel/SettingsPanelRegistry.md) | Registry for managing settings panel tabs | | [SettingsPanelTab](./reference/settings-panel/SettingsPanelTab.md) | Base class for creating custom settings panel tabs | ### Icons Management | API | Description | |-----|-------------| | [IconsRegistry](./reference/icons/IconsRegistry.md) | Registry for managing custom SVG icons in extensions | ### UI Elements | API | Description | |-----|-------------| | [UIElement](./reference/ui-elements/UIElement.md) | Base class for creating custom UI elements | | [UIElementTagRegistry](./reference/ui-elements/UIElementTagRegistry.md) | Registry for managing custom UI element tags | --- --- url: https://plugin.stripo.email/extensions/reference/api/BaseApi.md --- # BaseApi Core interface providing fundamental API functionality for all extension components in the Stripo Email Editor Extensions SDK. ```typescript interface BaseApi ``` ## Description The `BaseApi` interface defines the fundamental API methods available to all extension components, including blocks, controls, renderers, and context actions. It provides access to editor configuration, internationalization, state management, and UI features such as popovers. ## Import ```typescript import { BaseApi } from '@stripoinc/ui-editor-extensions'; ``` ## Properties None (all functionality is provided through methods) ## Methods ### getDocumentRootHtmlNode() Retrieves the root immutable HTML node of the document template. ```typescript getDocumentRootHtmlNode(): ImmutableHtmlNode ``` #### Returns [`ImmutableHtmlNode`](../nodes/ImmutableHtmlNode.md) - The root HTML node of the email document #### Usage Notes * Provides read-only access to the entire document structure * Use for querying elements across the document * Combine with selectors to find specific nodes * Immutable nodes ensure document consistency #### Example ```typescript const rootNode = this.api.getDocumentRootHtmlNode(); const allImages = rootNode.querySelectorAll('img'); const stripeCount = rootNode.querySelectorAll('.esd-stripe').length; ``` *** ### getDocumentRootCssNode() Retrieves the root immutable CSS node of the document's stylesheet. ```typescript getDocumentRootCssNode(): ImmutableCssNode ``` #### Returns [`ImmutableCssNode`](../nodes/ImmutableCssNode.md) - The root CSS node containing all styles #### Usage Notes * Provides access to document-level CSS rules * Allows querying for specific CSS rules or media queries * Enables reading CSS properties and values * Essential for style-related controls #### Example ```typescript const desktopH1CssNode = this.api.getDocumentRootCssNode().querySelector('h1'); const mobileH1CssNode = this.api.getDocumentRootCssNode().querySelector('@{media only screen and (max-width: 600px)} h1'); ``` ### getEditorConfig() Retrieves the current configuration settings of the editor instance. ```typescript getEditorConfig(): Record ``` #### Returns `Record` - A record object containing editor configuration key-value pairs #### Usage Notes * Contains editor features, metadata, and settings * Configuration is set during editor initialization * May include custom metadata for your application * Read-only—configuration cannot be modified through this method #### Example ```typescript const config = this.api.getEditorConfig(); // Access standard configuration const locale = config.locale; const name = config.name; const emailId = config.metadata.emailId; // Access custom data const enabled = config.customBlock?.enabled; ``` *** ### getUserPermissions() :::::tip Version Availability This method is available starting from v3.10.0 ::::: Retrieves the current user's permissions in the editor. ```typescript getUserPermissions(): EditorPermissions ``` #### Returns [`EditorPermissions`](../types/EditorPermissions.md) - Object describing the user's access to editor features #### Usage Notes * Permissions are provided by your backend through the User Permissions API * Use to adapt extension UI to the current user's access level * Combine with `onUserPermissionsUpdated()` to react to permission changes * See [Permissions and Access Management](/editor-configuration/permissions-and-access-management) for configuration details #### Example ```typescript const permissions = this.api.getUserPermissions(); // Hide editing controls for read-only users if (!permissions.content?.write) { this.disableEditingControls(); } // Check module management access if (permissions.modules?.write) { this.showModuleActions(); } ``` *** ### translate() Translates a given key into the currently configured language, optionally interpolating parameters. ```typescript translate(key: string, params?: Record): string ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | key | `string` | The localization key to translate | | params | `Record` | Optional parameters for interpolation into the translated string | #### Returns `string` - The translated string #### Usage Notes * Respects the current editor language setting * Supports parameter interpolation using placeholders * Returns the key itself if translation is not found * Essential for creating multilingual extensions #### Example ```typescript // Simple translation const title = this.api.translate('block.product.title'); // Translation with parameters const message = this.api.translate('block.product.count', { count: 5, total: 10 }); // If translation is "Showing {count} of {total} products" // Result: "Showing 5 of 10 products" // Using in block names public getName(): string { return this.api.translate('blocks.myBlock.name'); } ``` ### addCustomFont() Adds a new font family to the editor configuration. ```typescript addCustomFont(font: CustomFontFamily): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | font | [`CustomFontFamily`](../types/CustomFontFamily.md) | Font family configuration object | #### Usage Notes * Makes font available in all font selection dropdowns * Font CSS is automatically loaded if URL is provided * Useful for brand-specific or custom fonts * Fonts are available for the entire editor session #### Example ```typescript // Add a custom brand font this.api.addCustomFont({ name: 'Brand Font', fontFamily: 'BrandFont, Arial, sans-serif', url: 'https://fonts.example.com/brand-font.css' }); ``` *** ### ignoreClickOutside() Controls whether clicking outside the current element triggers deselection. ```typescript ignoreClickOutside(ignore: boolean): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | ignore | `boolean` | If true, disables deselection on outside clicks; if false, enables it | #### Usage Notes * Useful when showing custom UI that users need to interact with * Prevents loss of context when using external controls * Remember to reset (set to false) when interaction is complete * Only affects the current selection context #### Example ```typescript public onSelect(node: ImmutableHtmlNode): void { // Prevent deselection while configuration dialog is open this.api.ignoreClickOutside(true); this.showConfigurationDialog({ onClose: () => { // Re-enable normal deselection behavior this.api.ignoreClickOutside(false); } }); } // Another example with external panel private openExternalPanel(): void { this.api.ignoreClickOutside(true); const panel = document.getElementById('external-panel'); panel.style.display = 'block'; panel.addEventListener('close', () => { this.api.ignoreClickOutside(false); }, { once: true }); } ``` *** ### getEditorState() Returns information about the active state of the editor. ```typescript getEditorState(): EditorState ``` #### Returns [`EditorState`](../constants/EditorState) - Object containing: | Property | Type | Description | |----------|-------------------------------------------------------|--------------------------------| | previewDeviceMode | [`PreviewDeviceMode`](../constants/PreviewDeviceMode) | Current preview mode | | panelPosition | [`PanelPosition`](../constants/PanelPosition) | Position of the settings panel | | themeMode | [`ThemeMode`](../constants/ThemeMode) | Current email template theme | #### Usage Notes * Use to adapt behavior based on preview mode * State reflects current user interface configuration * Useful for responsive behavior in extensions * Use `themeMode` to keep extension UI styling aligned with the email template theme #### Example ```typescript const state = this.api.getEditorState(); // Adapt to preview mode if (state.previewDeviceMode === PreviewDeviceMode.MOBILE) { // Simplified behavior for mobile preview this.useMobileSettings(); } else { // Full desktop functionality this.useDesktopSettings(); } // Check panel position if (state.panelPosition === PanelPosition.BLOCKS_SETTINGS) { // Adjust UI accordingly } // Match extension UI to the email template theme if (state.themeMode === ThemeMode.DARK) { this.useDarkThemeStyles(); } ``` *** ### onUserPermissionsUpdated() :::::tip Version Availability This method is available starting from v3.10.0 ::::: Subscribes to user permission changes in the editor. ```typescript onUserPermissionsUpdated( callback: (newPermissions: EditorPermissions, oldPermissions?: EditorPermissions) => void ): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | callback | `Function` | Function called when permissions are updated, receiving new permissions and optionally old permissions | #### Usage Notes * Callback is triggered whenever the user's permissions are updated * `oldPermissions` may be `undefined` on the initial update * Useful for keeping extension UI in sync with access changes without reloading the editor * No automatic cleanup - subscriptions persist for component lifetime #### Example ```typescript public onDocumentInit(): void { this.api.onUserPermissionsUpdated((newPermissions, oldPermissions) => { console.log('Permissions updated', newPermissions, oldPermissions); // Toggle editing UI based on the new access level this.setEditingEnabled(!!newPermissions.content?.write); }); } ``` *** ### onEditorStatePropUpdated() Subscribes to changes in specific editor state properties. ```typescript onEditorStatePropUpdated( prop: EditorStatePropertyType, callback: (newValue: unknown, oldValue: unknown) => void ): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | prop | [`EditorStatePropertyType`](../constants/EditorStatePropertyType.md) | The property to monitor for changes | | callback | `Function` | Function called when the property value changes | #### EditorStatePropertyType See [`EditorStatePropertyType`](../constants/EditorStatePropertyType.md) for available property types. #### Usage Notes * Callback is triggered whenever the specified property changes * Useful for reactive UI updates * Multiple subscriptions to the same property are supported * No automatic cleanup - subscriptions persist for component lifetime #### Example ```typescript public onDocumentInit(): void { // Monitor preview mode changes this.api.onEditorStatePropUpdated( EditorStatePropertyType.previewDeviceMode, (newMode, oldMode) => { console.log(`Preview changed from ${oldMode} to ${newMode}`); this.updateBlockDisplay(newMode); } ); // Monitor email template theme changes this.api.onEditorStatePropUpdated( EditorStatePropertyType.themeMode, (newTheme, oldTheme) => { console.log(`Theme changed from ${oldTheme} to ${newTheme}`); this.updateThemeStyles(newTheme); } ); } private updateBlockDisplay(mode: PreviewDeviceMode): void { if (mode === PreviewDeviceMode.MOBILE) { // Update for mobile preview } } ``` *** ### openAIPopover() Opens an AI assistant popover anchored to a target element. ```typescript openAIPopover(options: AIPopoverOptions): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | options | [`AIPopoverOptions`](../types/AIPopoverOptions.md) | Configuration for the AI popover | #### Usage Notes * Requires AI features to be enabled in editor configuration * Provides context-aware content generation * Popover placement auto-adjusts to stay visible * User must have appropriate permissions for AI features #### Example ```typescript import {ExtensionPopoverType, PopoverSide, UIElement} from '@stripoinc/ui-editor-extensions'; export class AIPopoverUIElement extends UIElement { getId() { return 'ai-popover-element'; } getTemplate() { return `
` } onRender(container) { this.aiButton = container.querySelector('.ai-button'); this.aiButton.addEventListener('click', this.showAiPopover.bind(this)); } showAiPopover() { this.api.openAIPopover({ targetElement: this.aiButton, value: 'Hello world', preferredSides: [PopoverSide.LEFT], type: ExtensionPopoverType.AI_TEXT, onResult: (result) => {console.log(result)} }) } onDestroy() { this.aiButton.removeEventListener('click', this.showAiPopover.bind(this)); } } ``` *** ### openEmojiPopover() Opens an emoji picker popover anchored to a target element. ```typescript openEmojiPopover(options: EmojiPopoverOptions): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | options | [`EmojiPopoverOptions`](../types/EmojiPopoverOptions.md) | Configuration for the emoji picker | #### Usage Notes * Shows native emoji picker interface * Supports search and categories * Auto-positions to stay within viewport * Returns the selected emoji character #### Example ```typescript import {ExtensionPopoverType, PopoverSide, UIElement} from '@stripoinc/ui-editor-extensions'; export class EmojiPopoverUIElement extends UIElement { getId() { return 'emoji-popover-element'; } getTemplate() { return `
` } onRender(container) { this.emojiButton = container.querySelector('.emoji-button'); this.emojiButton.addEventListener('click', this.showEmojiPopover.bind(this)); } showEmojiPopover() { this.api.openEmojiPopover({ targetElement: this.emojiButton, preferredSides: [PopoverSide.LEFT], onResult: (result) => {console.log(result)} }) } onDestroy() { this.emojiButton.removeEventListener('click', this.showEmojiPopover.bind(this)); } } ``` *** ### sendEvent() :::::tip Version Availability This method is available starting from v3.8.0 ::::: Sends a fire-and-forget event with arbitrary contextual data to the editor integration layer. Calling `sendEvent()` triggers the editor initialization callback `onEvent(type, params)`, so your host application can react to extension-originated events. See [Initialization Settings](https://plugin.stripo.email/editor-configuration/initialization-settings) for details. ```typescript sendEvent(type: string, params: Record): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | type | `string` | Event identifier | | params | `Record` | Additional event payload | #### Usage Notes * Useful for custom analytics * Does not return a value and does not wait for a response * Triggers the editor-level `onEvent(type, params)` callback configured during initialization #### Example ```typescript this.api.sendEvent('extensions.product-card.selected', { blockId: this.getId(), panel: 'settings', locale: this.api.getEditorConfig().locale }); ``` ## Implementation Context The `BaseApi` is available in various extension components through their respective API properties: * **Block**: Access via `this.api` in block methods * **Control**: Access via `this.api` in control methods * **BlockRenderer**: Access via `this.api` in renderer methods * **ContextAction**: Access via `this.api` in action methods ## Best Practices 1. **Always use translations** for user-facing text to support internationalization 2. **Check configuration** before using optional features 3. **Reset interaction states** (like `ignoreClickOutside`) when done 4. **Subscribe to state changes** early in component lifecycle 5. **Validate configuration values** before using them --- --- url: https://plugin.stripo.email/extensions/reference/api/BaseModifierApi.md --- # BaseModifierApi Interface providing document modification capabilities for extension components within the Stripo Email Editor Extensions SDK. ```typescript interface BaseModifierApi ``` ## Description The `BaseModifierApi` interface provides access to the template modification system, allowing extension components to make changes to email templates in a controlled, synchronized manner. It ensures all modifications are properly tracked, support undo/redo operations, and maintain consistency in collaborative editing sessions. ## Import ```typescript import { BaseModifierApi } from '@stripoinc/ui-editor-extensions'; ``` ## Properties None (all functionality is provided through methods) ## Methods ### getDocumentModifier() Retrieves a modifier instance for performing operations on the document's template. ```typescript getDocumentModifier(): TemplateModifier ``` #### Returns [TemplateModifier](../modification/TemplateModifier.md) - A TemplateModifier instance capable of modifying HTML and CSS nodes #### Usage Notes * Modifications are batched until `apply()` is called * Supports method chaining for multiple modifications * Maintains proper synchronization in collaborative environments #### Example ```typescript // Get a modifier instance const modifier = this.api.getDocumentModifier(); // Chain multiple modifications modifier .modifyHtml(htmlNode) .setAttribute('data-id', '123') .setStyle('color', 'blue') .modifyCss(cssNode) .setProperty('font-size', '16px') .setProperty('margin', '10px') .apply(new ModificationDescription('Updated block styling and content')); ``` --- --- url: https://plugin.stripo.email/extensions/reference/api/BlockApi.md --- # BlockApi Interface providing API access for Block implementations in the Stripo Email Editor Extensions SDK. ```typescript interface BlockApi extends BaseApi, BaseModifierApi ``` ## Description The `BlockApi` interface provides comprehensive access to editor functionalities for block implementations. It combines base API features (translation, configuration, state management) with document modification capabilities, enabling blocks to interact with and modify email templates while maintaining synchronization with the editor's collaborative features. ## Import ```typescript import { BlockApi } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Hierarchy ``` BaseApi ↓ BlockApi ← BaseModifierApi ``` ## Properties None (all functionality provided through methods) ## Methods ### setViewOnly() Sets the view-only state for the current block context. ```typescript setViewOnly(viewOnly: boolean): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | viewOnly | `boolean` | If true, interactions might be restricted | #### Usage Notes * Controls whether the block can be dragged and dropped within the template * Useful for singleton blocks or conditional editing #### Example ```typescript public onDocumentInit(): void { // Ensure only one instance can be edited const instances = this.api.getDocumentRoot() .querySelectorAll(`.${this.getUniqueBlockClassname()}`); if (instances.length > 1) { this.api.setViewOnly(true); } } ``` *** ### getHiddenElementState() Gets the current device-specific hidden state for the provided node. ```typescript getHiddenElementState(target: ImmutableHtmlNode): HideElementState ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | target | [ImmutableHtmlNode](../nodes/ImmutableHtmlNode) | The node to inspect | #### Returns [HideElementState](../types/HideElementState) - `'desktop'`, `'mobile'`, or `undefined` #### Usage Notes * The editor resolves the canonical node that owns the visibility configuration * Use this method when custom controls need to stay synchronized with the built-in hide-element state * `undefined` means the target is not hidden for a specific device mode #### Example ```typescript public onTemplateNodeUpdated(node: ImmutableHtmlNode): void { const hiddenState = this.api.getHiddenElementState(node); if (hiddenState === 'mobile') { this.api.sendEvent('extensions.block.hidden-on-mobile', { blockId: this.getId(), }); } } ``` ## Inherited Methods from BaseApi [BaseApi](./BaseApi.md) - Base API interface ## Inherited Methods from BaseModifierApi [BaseModifierApi](./BaseModifierApi.md) - Document modification API --- --- url: https://plugin.stripo.email/extensions/reference/api/BlockRendererApi.md --- # BlockRendererApi Interface providing API access for BlockRenderer implementations in the Stripo Email Editor Extensions SDK. ```typescript interface BlockRendererApi extends BaseApi ``` ## Description The `BlockRendererApi` interface provides access to editor functionalities for custom block renderer implementations. It inherits all base API functionality, giving renderers access to translation, configuration, and editor state features while rendering custom block content. ## Import ```typescript import { BlockRendererApi } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Hierarchy ``` BaseApi ↓ BlockRendererApi ``` ## Properties None (all functionality provided through inherited methods) ## Methods The `BlockRendererApi` interface currently does not define any additional methods beyond those inherited from `BaseApi`. ## Inherited Methods from BaseApi [BaseApi](./BaseApi.md) - Base API interface --- --- url: https://plugin.stripo.email/extensions/reference/api/BlocksPanelApi.md --- # BlocksPanelApi API interface for interacting with and customizing the editor's blocks panel. ```typescript interface BlocksPanelApi extends BaseApi ``` ## Description The `BlocksPanelApi` interface provides API functionality for customizing blocks panel. It extends the `BaseApi` interface, giving blocks panel implementations access to core editor features like configuration, translations, and state management. ## Import ```typescript import { BlocksPanelApi } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Hierarchy ``` BaseApi ↓ BlocksPanelApi ``` ## Properties None (inherits all properties from BaseApi) ## Methods The `BlocksPanelApi` interface currently does not define any additional methods beyond those inherited from `BaseApi`. ## Inherited Methods from BaseApi [BaseApi](./BaseApi.md) - Base API interface --- --- url: https://plugin.stripo.email/extensions/reference/api/ContextActionApi.md --- # ContextActionApi API interface for context action implementations in the Stripo Email Editor Extensions SDK. ```typescript interface ContextActionApi extends BaseApi, BaseModifierApi ``` ## Description The `ContextActionApi` interface provides API functionality for context actions - custom actions that appear in contextual menus when users interact with email elements. It combines the base API functionality with document modification capabilities, allowing context actions to both read editor state and modify the email template. ## Import ```typescript import { ContextActionApi } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance * Extends: [BaseApi](./BaseApi.md), [BaseModifierApi](./BaseModifierApi.md) ## Properties None (inherits all properties from parent interfaces) ## Methods The `ContextActionApi` interface currently does not define any additional methods beyond those inherited from `BaseApi` and `BaseModifierApi`. ## Inherited Methods from BaseApi [BaseApi](./BaseApi.md) - Base API interface ## Inherited Methods from BaseModifierApi [BaseModifierApi](./BaseModifierApi.md) - Document modification API --- --- url: https://plugin.stripo.email/extensions/reference/api/ControlApi.md --- # ControlApi API interface for control implementations in the Stripo Email Editor Extensions SDK. ```typescript interface ControlApi extends BaseApi, BaseModifierApi ``` ## Description The `ControlApi` interface provides comprehensive API functionality for custom controls in the settings panel. It extends both `BaseApi` and `BaseModifierApi`, offering methods to manage UI elements, access document nodes, handle value changes, and modify the email template. Controls use this API to create rich, interactive settings interfaces for email elements. ## Import ```typescript import { ControlApi } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance * Extends: [BaseApi](./BaseApi.md), [BaseModifierApi](./BaseModifierApi.md) ## Properties None (all functionality provided through methods) ## Methods ### setVisibility() Sets the visibility of a specific UI element within the control's scope. ```typescript setVisibility(uiElementName: string, isVisible: boolean): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | uiElementName | `string` | The ID of the UI element to show/hide | | isVisible | `boolean` | True to show the element, false to hide it | #### Usage Notes * Controls dynamic UI element visibility * Useful for conditional settings * Affects only elements within this control * Changes are immediate #### Example ```typescript // Show/hide advanced options based on toggle public onValueChanged(elementName: string, value: any): void { if (elementName === 'showAdvanced') { this.api.setVisibility('advancedSettingsSelectPicker', value); } } ``` *** ### setUIEAttribute() Sets a specific attribute on a target UI element within the control's scope. ```typescript setUIEAttribute(uiElementName: string, attribute: string, value: unknown): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | uiElementName | `string` | The ID of the target UI element | | attribute | `string` | The name of the attribute to set (use UEAttr constants) | | value | `unknown` | The value to set for the attribute | #### Usage Notes * Use with `UEAttr` constants for attribute names * Dynamically modify UI element properties * Common attributes: disabled, placeholder, min, max * Changes apply immediately to the UI #### Example ```typescript import { UEAttr } from '@stripoinc/ui-editor-extensions'; // Disable input based on condition this.api.setUIEAttribute('fontSize', UEAttr.TEXT.disabled, isLocked); // Update placeholder text this.api.setUIEAttribute('customText', UEAttr.TEXT.placeholder, this.api.translate('placeholders.enterText')); // Set min/max values this.api.setUIEAttribute('width', UEAttr.COUNTER.minValue, 100); this.api.setUIEAttribute('width', UEAttr.COUNTER.maxValue, 600); ``` *** ### updateValues() Updates the values of multiple UI elements within the control's scope at once. ```typescript updateValues(valuesMap: Record): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | valuesMap | `Record` | Object where keys are UI element IDs and values are their new values | #### Usage Notes * Efficient bulk updates * Use for setting initial values #### Example ```typescript // Set multiple values at once this.api.updateValues({ 'enableFeatureSwitcher': true, 'itemCountCounter': 5, 'styleSelect': 'modern' }); ``` *** ### updateUIElementValue() :::tip Version Availability This method is available starting from v3.5.0 ::: Updates the value of a single UI element within the control's scope using path notation. ```typescript updateUIElementValue(path: string, value: unknown): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | path | `string` | The path of the target UI element. Can include indices for repeatable elements. | | value | `unknown` | The new value to set for the UI element. | #### Usage Notes * Supports granular updates * Crucial for updating items within [REPEATABLE](../constants/UIElementType.md#repeatable) elements * Path notation: `elementName` for top-level, `elementName[index].fieldName` for nested/repeatable elements #### Example ```typescript // Update the value of the input with the name 'text' this.api.updateUIElementValue('text', 'updated value'); // Update the value of the input with name 'text' in the first item // of the REPEATABLE element with name 'items' this.api.updateUIElementValue('items[0].text', 'updated value'); ``` *** ### getValues() Returns the current values of all UI elements managed by this control. ```typescript getValues(): Record ``` #### Returns `Record` - Object with UI element IDs as keys and their current values #### Usage Notes * Get all values for saving/validation * Useful for creating presets * Includes only elements within this control * Values reflect current UI state #### Example ```typescript // Save current settings as preset public saveAsPreset(name: string): void { const currentValues = this.api.getValues(); const preset = { name: name, values: currentValues, timestamp: Date.now() }; this.savePreset(preset); } // Validate all values public validateSettings(): boolean { const values = this.api.getValues(); return values.itemCountCounter >= 1 && values.itemCountCounter <= 10; } ``` *** ### onValueChanged() Registers a callback function to be invoked when the value of a specific UI element changes. ```typescript onValueChanged( uiElementName: string, callback: (newValue: unknown, oldValue: unknown, index?: number) => void ): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | uiElementName | `string` | The ID of the UI element to listen to | | callback | `Function` | Function called when value changes, receiving new and old values, and an optional index if the element is part of a repeatable list | #### Usage Notes * `index` parameter (v3.5.0+) is provided when the changed element is part of a [REPEATABLE](../constants/UIElementType.md#repeatable) UI element. #### Example ```typescript public onRender(): void { // Basic usage this.api.onValueChanged('contentText', (newValue, oldValue) => { console.log(`Value changed from ${oldValue} to ${newValue}`); }); // v3.5.0+: Usage with repeatable elements this.api.onValueChanged('items.title', (newValue, oldValue, index) => { console.log(`Item at index ${index} changed from ${oldValue} to ${newValue}`); }); } ``` *** ### setSettingsPanelTabTitleHtml() ::::tip Version Availability This method is available starting from v3.6.0 :::: Updates a settings panel tab title using raw HTML. ```typescript setSettingsPanelTabTitleHtml(tabId: string, html: string): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | tabId | `string` | The ID of the settings panel tab to update | | html | `string` | HTML string to render as the tab title | #### Example ```typescript this.api.setSettingsPanelTabTitleHtml( 'appearance', ' Appearance' ); ``` *** ## Inherited Methods from BaseApi [BaseApi](./BaseApi.md) - Base API interface ## Inherited Methods from BaseModifierApi [BaseModifierApi](./BaseModifierApi.md) - Document modification API --- --- url: >- https://plugin.stripo.email/extensions/reference/api/ExternalDisplayConditionsApi.md --- # ExternalDisplayConditionsApi API interface for external display conditions integrations in the Stripo Email Editor Extensions SDK. ```typescript interface ExternalDisplayConditionsApi extends BaseApi ``` ## Description The `ExternalDisplayConditionsApi` interface provides access to core editor functionality for external display conditions integrations. It currently extends [BaseApi](./BaseApi.md) without adding additional methods, but serves as a dedicated type for future enhancements and improved type safety. ## Import ```typescript import { ExternalDisplayConditionsApi } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance * Extends: [BaseApi](./BaseApi.md) ## Methods Inherits all methods from [BaseApi](./BaseApi.md). No additional methods are currently defined specifically for `ExternalDisplayConditionsApi`. --- --- url: https://plugin.stripo.email/extensions/reference/api/GeneralPanelTabApi.md --- # GeneralPanelTabApi :::tip Version Availability This interface is available starting from v3.5.0 ::: API interface for General Panel Tab implementations. ```typescript interface GeneralPanelTabApi extends ControlApi ``` ## Description The `GeneralPanelTabApi` provides access to editor functionalities specifically for [GeneralPanelTab](../controls/GeneralPanelTab.md) instances. It inherits all methods from [ControlApi](./ControlApi.md), allowing tabs to manage UI elements, modify the document, and handle localization. ## Import ```typescript import { GeneralPanelTabApi } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance * Extends: [ControlApi](./ControlApi.md) ## Methods Inherits all methods from [ControlApi](./ControlApi.md). No additional methods are currently defined specifically for `GeneralPanelTabApi`, but it serves as a specialized interface for future enhancements and better type safety. --- --- url: https://plugin.stripo.email/extensions/reference/api/ModulesPanelTabApi.md --- # ModulesPanelTabApi ::::tip Version Availability This interface is available starting from v3.7.0 :::: API interface for Modules Panel Tab implementations. ```typescript interface ModulesPanelTabApi extends BaseApi, BaseModifierApi ``` ## Description The `ModulesPanelTabApi` provides access to editor functionalities specifically for [ModulesPanelTab](../controls/ModulesPanelTab.md) instances. It extends [BaseApi](./BaseApi.md) and [BaseModifierApi](./BaseModifierApi.md) to support UI element management, translation, and document modifications. ## Import ```typescript import { ModulesPanelTabApi } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance * Extends: [BaseApi](./BaseApi.md), [BaseModifierApi](./BaseModifierApi.md) ## Methods ### setVisibility() Sets the visibility of a specific UI element within the tab's scope. ```typescript setVisibility(uiElementName: string, isVisible: boolean): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | uiElementName | `string` | ID of the UI element to show/hide | | isVisible | `boolean` | True to show the element, false to hide it | *** ### setUIEAttribute() Sets a specific attribute on a target UI element within the tab's scope. ```typescript setUIEAttribute(uiElementName: string, attribute: string, value: unknown): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | uiElementName | `string` | ID of the target UI element | | attribute | `string` | Attribute name (use values from [UEAttr](../constants/UEAttr.md)) | | value | `unknown` | Attribute value | *** ### updateValues() Updates the values of multiple UI elements within the tab's scope at once. ```typescript updateValues(valuesMap: Record): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | valuesMap | `Record` | UI element IDs mapped to their new values | *** ### getValues() Returns the current values of all UI elements managed by this tab. ```typescript getValues(): Record ``` #### Returns `Record` - Current values keyed by UI element ID *** ### onValueChanged() Registers a callback function to be invoked when the value of a specific UI element changes. ```typescript onValueChanged( uiElementName: string, callback: (newValue: unknown, oldValue: unknown) => void ): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | uiElementName | `string` | ID of the UI element to listen to | | callback | `(newValue: unknown, oldValue: unknown) => void` | Callback for value changes | --- --- url: https://plugin.stripo.email/extensions/reference/api/SettingsPanelApi.md --- # SettingsPanelApi API interface for settings panel tab implementations in the Stripo Email Editor Extensions SDK. ```typescript interface SettingsPanelApi extends BaseApi ``` ## Description The `SettingsPanelApi` interface provides API functionality for custom settings panel tabs. It extends the `BaseApi` interface, giving settings panel implementations access to core editor features like configuration, translations, and state management. ## Import ```typescript import { SettingsPanelApi } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance * Extends: [BaseApi](./BaseApi.md) ## Properties None (inherits all properties from BaseApi) ## Methods The `SettingsPanelApi` interface currently does not define any additional methods beyond those inherited from `BaseApi`. ## Inherited Methods from BaseApi [BaseApi](./BaseApi.md) - Base API interface --- --- url: https://plugin.stripo.email/extensions/reference/api/UIElementApi.md --- # UIElementApi API interface for UI element implementations in the Stripo Email Editor Extensions SDK. ```typescript interface UIElementApi extends BaseApi ``` ## Description The `UIElementApi` interface provides API functionality for custom UI elements used within controls. It extends the `BaseApi` interface and adds methods for handling value changes. UI elements are the building blocks of control interfaces - inputs, toggles, selects, and other interactive components. ## Import ```typescript import { UIElementApi } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance * Extends: [BaseApi](./BaseApi.md) ## Properties None (all functionality provided through methods) ## Methods ### triggerValueChange() Function to be called by the UIElement implementation when its value changes. This signals the change to the managing control. ```typescript triggerValueChange(value: unknown): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | value | `unknown` | The new value of the UI element | #### Usage Notes * Call this whenever the element's value changes * The control receives this through its value change handlers * Triggers validation and dependent updates * Essential for two-way data binding #### Example ```typescript import {UIElement} from '@stripoinc/ui-editor-extensions'; export class CustomSlider extends UIElement { getId() { return 'custom-slider'; } getTemplate() { return `
50
`; } onRender(container) { this.slider = container.querySelector('.slider'); this.display = container.querySelector('.value-display'); this.slider.addEventListener('input', (event) => { const value = parseInt(event.target.value); this.display.textContent = value; // Notify the editor of the value change this.api.triggerValueChange(value); }); } getValue() { return parseInt(this.slider.value); } setValue(value) { this.slider.value = value; this.display.textContent = value; } } ``` *** ## Inherited Methods from BaseApi [BaseApi](./BaseApi.md) - Base API interface --- --- url: https://plugin.stripo.email/extensions/reference/blocks/Block.md --- # Block Core class for creating custom blocks in the Stripo Email Editor Extensions SDK. ```typescript class Block ``` ## Description The `Block` class is the foundation for all custom blocks in the Stripo Email Editor. It provides the essential structure and lifecycle hooks for creating reusable email content components that integrate seamlessly with the editor's drag-and-drop interface, collaboration features, and template management system. Blocks represent discrete pieces of email content, ranging from simple elements like buttons and images to complex structures containing other blocks. Each block type appears in the editor's blocks panel and can be dragged into email templates. ## Import ```typescript import { Block } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### api Provides access to editor functionalities specific to this block instance. ```typescript public api!: BlockApi ``` #### Type [BlockApi](../api/BlockApi.md) #### Usage Notes * Automatically injected by the editor when the block is instantiated * Available in all lifecycle methods * Provides access to template modification, translation, and editor state ## Methods ### getId() Retrieves the unique identifier for this block type. ```typescript public getId(): string ``` #### Returns `string` - A unique identifier for the block type #### Usage Notes * Must be unique across all registered blocks * Used for block registration and CSS class generation #### Example ```typescript public getId(): string { return 'my-custom-block'; } ``` *** ### getTemplate() Retrieves the HTML template string that defines the initial structure of this block. ```typescript public getTemplate(): string ``` #### Returns `string` - HTML template for the block #### Usage Notes * Defines the initial HTML structure when the block is dragged into the editor * Can use [template aliases](../../tutorials/how-to/template-aliases.md) for common patterns #### Example ```typescript public getTemplate(): string { return `
`; } ``` *** ### getTemplateStyles() ::::tip Version Availability This method is available starting from v3.7.0 :::: Retrieves a CSS template string that defines styles associated with this block. ```typescript public getTemplateStyles(): string ``` #### Returns `string` - CSS styles for the block. Defaults to an empty string. #### Usage Notes * Styles are injected when the block is used and removed when the last instance is deleted * Use for block-scoped styles that should travel with the block #### Example ```typescript public getTemplateStyles(): string { return ` .${this.getUniqueBlockClassname()} .cta { background: #0b5fff; color: #fff; } `; } ``` *** ### getIcon() Retrieves the URL or icon content representing this block in the editor's block panel. ```typescript public getIcon(): string ``` #### Returns `string` - Icon source (URL or data URI) #### Usage Notes * Displayed in the blocks panel * Recommended size: 24x24px for optimal display * Supports SVG, PNG, JPG formats #### Example ```typescript import blockIcon from './assets/icon.svg'; public getIcon(): string { return blockIcon; } ``` *** ### getName() Retrieves the display name of the block shown to users in the block panel. ```typescript public getName(): string ``` #### Returns `string` - Localized block name #### Usage Notes * Displayed in the blocks panel and tooltips * Should be concise and descriptive * Use `this.api.translate()` for localization support #### Example ```typescript public getName(): string { return this.api.translate('block.customBlock.name'); } ``` *** ### getDescription() Retrieves a short description of the block shown to users, often as a tooltip in the block panel. ```typescript public getDescription(): string ``` #### Returns `string` - Localized description #### Usage Notes * Displayed as tooltip in blocks panel * Should briefly explain the block's purpose * Use `this.api.translate()` for localization #### Example ```typescript public getDescription(): string { return this.api.translate('block.customBlock.description'); } ``` *** ### getSettingsPanelTitleHtml() :::tip Version Availability This method is available starting from v3.5.0 ::: Retrieves the title of the block in the settings panel. ```typescript public getSettingsPanelTitleHtml(): string ``` #### Returns `string` - Localized block name in settings panel, can contain HTML markup. #### Usage Notes * Displayed as title in the settings panel when the block is selected * Can contain HTML markup for rich formatting * If not implemented, `getName()` will be used as the default title in the settings panel #### Example ```typescript public getSettingsPanelTitleHtml(): string { return `${this.getName()}(v1.0)`; } ``` ### isEnabled() Determines if the block should be available for use in the editor. ```typescript public isEnabled(): boolean ``` #### Returns `boolean` - True if the block is enabled, false otherwise. Defaults to `true`. #### Usage Notes * Override to provide conditional availability * Can depend on editor configuration or user permissions * Disabled blocks don't appear in the blocks panel #### Example ```typescript public isEnabled(): boolean { const config = this.api.getEditorConfig(); return config.features?.customBlocks === true; } ``` *** ### canBeSavedAsModule() Determines if the block can be saved as a reusable module by the user. ```typescript public canBeSavedAsModule(): boolean ``` #### Returns `boolean` - True if the block can be saved as a module. Defaults to `false`. #### Usage Notes * Only applicable for STRUCTURE and CONTAINER composition types * Enables "Save as Module" option in context menu * Modules can be reused across different templates #### Example ```typescript public canBeSavedAsModule(): boolean { return this.getBlockCompositionType() === BlockCompositionType.STRUCTURE; } ``` *** ### getContextActionsIds() Specifies the context actions available for this block. ```typescript public getContextActionsIds(): string[] | undefined ``` #### Returns `string[] | undefined` - Array of context action IDs, or undefined to use defaults #### Usage Notes * Controls which actions appear in the block's context menu * Use `ContextActionType` enum for standard actions * Can include custom action IDs registered via `ExtensionBuilder` * Return `undefined` to use default actions #### Example ```typescript import { ContextActionType } from '@stripoinc/ui-editor-extensions'; public getContextActionsIds(): string[] { return [ ContextActionType.COPY, ContextActionType.MOVE, 'my-custom-action' ]; } ``` *** ### getCustomRenderer() Provides a custom renderer class for this block, allowing for specialized rendering logic. ```typescript public getCustomRenderer(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - Constructor for a custom renderer class #### Usage Notes * Enables different display in editor vs. actual HTML * Useful for showing merge tags as preview values * Return `undefined` to use default rendering * Custom renderers disable inner block selection for STRUCTURE/CONTAINER types #### Example ```typescript public getCustomRenderer(): ConstructorOfType { return MyCustomRenderer; } ``` *** ### getUniqueBlockClassname() Gets a unique CSS class name specifically for this block type. ```typescript public getUniqueBlockClassname(): string ``` #### Returns `string` - Unique CSS class name. Defaults to `esd-{blockId}`. #### Usage Notes * Used for CSS targeting and block identification * Applied to the block's root element * Override only if custom class naming is required #### Example ```typescript public getUniqueBlockClassname(): string { return `custom-block-${this.getId()}`; } ``` *** ### getBlockCompositionType() Determines if block is atomic or composite. ```typescript public getBlockCompositionType(): BlockCompositionType ``` #### Returns [BlockCompositionType](../constants/BlockCompositionType.md) - The composition type. Defaults to `BlockCompositionType.BLOCK`. #### Usage Notes * `BLOCK` - Atomic block * `CONTAINER` - Can contain other atomic blocks * `STRUCTURE` - Can contain containers with blocks * `STRIPE` - Stripe-level block container (top-level section) #### Example ```typescript public getBlockCompositionType(): BlockCompositionType { return BlockCompositionType.STRUCTURE; } ``` *** ### shouldDisplayQuickAddIcon() Determines if block should be included in empty container quick insert actions list. ```typescript public shouldDisplayQuickAddIcon(): boolean ``` #### Returns `boolean` - True to show quick-add icon. Defaults to `false`. #### Usage Notes * Adds block icon to quick-add menu in empty containers * Provides faster access to frequently used blocks #### Example ```typescript public shouldDisplayQuickAddIcon(): boolean { return true; } ``` *** ### shouldDisplayInBlocksPanel() ::::tip Version Availability This method is available starting from v3.7.0 :::: Determines if the block should appear in the blocks panel. ```typescript public shouldDisplayInBlocksPanel(): boolean ``` #### Returns `boolean` - True to show in the blocks panel. Defaults to `true`. #### Usage Notes * Useful when you want the block to be available for drag-and-drop not from the blocks panel, but for example from the ModulesSettingsTab using UIElementType.DRAGGABLE\_BLOCK #### Example ```typescript public shouldDisplayInBlocksPanel(): boolean { return false; } ``` *** ### allowInnerBlocksSelection() Determines if nested blocks selection is allowed in extensions of type STRUCTURE or CONTAINER. ```typescript public allowInnerBlocksSelection(): boolean ``` #### Returns `boolean` - True to allow selection. Defaults to `true`. #### Usage Notes * Only applicable for STRUCTURE and CONTAINER types * When false, prevents users from selecting inner blocks * Useful for blocks that should be edited as a whole unit *** ### allowInnerBlocksDND() Determines if nested blocks drag and drop is allowed in extensions of type STRUCTURE or CONTAINER. ```typescript public allowInnerBlocksDND(): boolean ``` #### Returns `boolean` - True to allow drag and drop. Defaults to `true`. #### Usage Notes * Only applicable for STRUCTURE and CONTAINER types * When false, prevents dragging blocks into or within the container * Useful for fixed-layout blocks *** ### allowInteractWithAMPWhenSelected() :::::tip Version Availability This method is available starting from v3.8.0 ::::: Determines if AMP content inside the selected block remains interactive. ```typescript public allowInteractWithAMPWhenSelected(): boolean ``` #### Returns `boolean` - True to allow AMP interaction while selected. Defaults to `true`. #### Usage Notes * Useful for blocks containing AMP-specific interactive elements * Override to disable interaction when your block should behave as a locked editing surface #### Example ```typescript public allowInteractWithAMPWhenSelected(): boolean { return false; } ``` ## Lifecycle Hooks ### onDocumentInit() Called when the editor document is initialized. ```typescript public onDocumentInit(): void ``` #### Usage Notes * Executes once when document loads * Useful for initial setup or validation * Can modify existing block instances in the template * Has access to the complete document via `this.api` #### Example ```typescript public onDocumentInit(): void { // Ensure only one instance exists const blocks = this.api.getDocumentRoot() .querySelectorAll(`.${this.getUniqueBlockClassname()}`); if (blocks.length > 1) { // Remove extra instances const modifier = this.api.getDocumentModifier(); for (let i = 1; i < blocks.length; i++) { modifier.modifyHtml(blocks[i]).remove(); } modifier.apply(new ModificationDescription('Removed duplicate blocks')); } } ``` *** ### onSelect() Called when an instance of this block is selected in the editor. ```typescript public onSelect(node: ImmutableHtmlNode): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | node | [ImmutableHtmlNode](../nodes/ImmutableHtmlNode.md) | The selected block instance | #### Usage Notes * Triggered each time user selects the block * Can be used to update UI or gather analytics * Has access to the specific block instance via `node` #### Example ```typescript public onSelect(node: ImmutableHtmlNode): void { console.log('Block selected:', node.getAttribute('id')); // Perform other actions } ``` *** ### onCopy() Called when an instance of this block is copied. ```typescript public onCopy(modifier: HtmlNodeModifier): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | modifier | [HtmlNodeModifier](../modification/HtmlNodeModifier.md) | The HTML node modifier for the copied block instance | #### Usage Notes * Executes on block duplication * Useful for resetting unique identifiers or state * Use the `modifier` to make changes to the copied block * The modifier is already focused on the newly copied block node #### Example ```typescript public onCopy(modifier: HtmlNodeModifier): void { // Reset unique ID on copied block modifier.setAttribute('data-id', generateUniqueId()); } ``` *** ### onDelete() Called when an instance of this block is deleted. ```typescript public onDelete(node: ImmutableHtmlNode): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | node | [ImmutableHtmlNode](../nodes/ImmutableHtmlNode.md) | The block instance being deleted | #### Usage Notes * Executes before block removal * Useful for cleanup operations * Can modify other parts of the document #### Example ```typescript public onDelete(node: ImmutableHtmlNode): void { // Clean up associated CSS rules const blockId = node.getAttribute('data-id'); const cssRule = this.api.getDocumentRootCssNode() .querySelector(`#${blockId}`); if (cssRule) { this.api.getDocumentModifier() .modifyCss(cssRule).removeRule() .apply(new ModificationDescription('Removed block styles')); } } ``` *** ### onCreated() Called after a new instance of this block is created and added to the document. ```typescript public onCreated(node: ImmutableHtmlNode): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | node | [ImmutableHtmlNode](../nodes/ImmutableHtmlNode.md) | The newly created block instance | #### Usage Notes * Triggered after drag-and-drop or programmatic insertion * Useful for initialization or default configuration #### Example ```typescript public onCreated(node: ImmutableHtmlNode): void { // Set default configuration const modifier = this.api.getDocumentModifier(); modifier.modifyHtml(node) .setAttribute('data-created', new Date().toISOString()) .setNodeConfig({ initialized: true }) .apply(new ModificationDescription('Initialized new block')); } ``` *** ### onDocumentChanged() Called when any part of the document template has changed. ```typescript public onDocumentChanged(node: ImmutableHtmlNode): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | node | [ImmutableHtmlNode](../nodes/ImmutableHtmlNode.md) | The current block instance | #### Usage Notes * Triggered frequently during editing * Use cautiously for performance-sensitive operations * Consider debouncing or throttling if needed * Useful for maintaining document-wide consistency * The `node` parameter provides access to the root template node #### Example ```typescript private changeTimeout: number; public onDocumentChanged(node: ImmutableHtmlNode): void { // Debounce updates clearTimeout(this.changeTimeout); this.changeTimeout = setTimeout(() => { // Access current node configuration const config = node.getNodeConfig(); this.updateTemplateState(config); }, 500); } ``` --- --- url: https://plugin.stripo.email/extensions/reference/blocks/BlockRenderer.md --- # BlockRenderer Base class for creating custom block renderers in the Stripo Email Editor Extensions SDK. ```typescript class BlockRenderer ``` ## Description The `BlockRenderer` class enables custom visual representation of blocks in the editor that differs from their actual HTML content. This is particularly useful for displaying merge tags as preview values, showing placeholder content for empty blocks, or creating interactive editing experiences. When a block uses a custom renderer, the editor displays the renderer's output instead of the actual HTML content, while the underlying template remains unchanged. ## Import ```typescript import { BlockRenderer } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### api Provides access to editor functionalities for the renderer. ```typescript api!: BlockRendererApi ``` #### Type [BlockRendererApi](../api/BlockRendererApi.md) #### Usage Notes * Automatically injected by the editor * Provides access to translation and configuration * Available when rendering methods are called ## Methods ### getPreviewInnerHtml() Returns custom content to be displayed inside the block's root TD element. ```typescript public getPreviewInnerHtml(node: ImmutableHtmlNode): string ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | node | [ImmutableHtmlNode](../nodes/ImmutableHtmlNode) | The current block node to render | #### Returns `string` - HTML string to display in the editor #### Usage Notes * Called whenever the block needs to be rendered in the editor * The returned HTML is displayed instead of the actual node content * Should return valid HTML that fits within a TD element * Can access node attributes and configuration via the `node` parameter #### Example ```typescript public getPreviewInnerHtml(node: ImmutableHtmlNode): string { const config = node.getNodeConfig(); if (!config.initialized) { return '
Click to configure this block
'; } // Replace merge tags with preview values const content = node.getInnerHTML(); return content.replace(/\{\{name\}\}/g, 'John Doe') .replace(/\{\{email\}\}/g, 'john@example.com'); } ``` --- --- url: https://plugin.stripo.email/extensions/reference/blocks/BlocksPanel.md --- # BlocksPanel Base class for customizing blocks panel in the Stripo Email Editor Extensions SDK. ```typescript class BlocksPanel ``` ## Description The `BlocksPanel` class enables you to customize the appearance and behavior of the blocks panel in the Stripo editor. By overriding its methods, you can define how each block is displayed, modify block hints, headers, and control the overall look and feel of the panel to better fit your extension's requirements. ## Import ```typescript import { BlocksPanel } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### api ```typescript api: BlocksPanelApi ``` #### Type [BlocksPanelApi](../api/BlocksPanelApi.md) The API instance providing access to editor configuration, translations, and other core functionality. ## Methods ### getBlockItemHtml() Generates HTML representation for a block item. Override to customize block appearance. ```typescript getBlockItemHtml(block: BlockItem): string | undefined ``` #### Parameters | Parameter | Type | Description | |-----------|--------------------------------------------|-------------| | block | [BlockItem](../types/BlockItem) | The block item to generate HTML for | #### Returns `string | undefined` - Custom HTML string or `undefined` to use default representation #### Usage Notes * Return custom HTML to override default block appearance * Return `undefined` to use the editor's default block rendering * Consider accessibility when creating custom HTML #### Example ```typescript public getBlockItemHtml(block: BlockItem): string | undefined { return `
${block.title}
`; } ``` *** ### isBlockHintVisible() Determines whether a hint (tooltip) should be displayed for the block. ```typescript isBlockHintVisible(block: BlockItem): boolean ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | block | [BlockItem](../types/BlockItem) | The block item to check hint visibility for | #### Returns `boolean` - `true` if hint should be visible, `false` otherwise #### Example ```typescript public isBlockHintVisible(block: BlockItem): boolean { // Hide hints for image block if (block.name === BlockType.BLOCK_IMAGE) { return false; } // Show hints for other blocks return true; } ``` *** ### getBlockHint() Defines the hint content for a block item. ```typescript getBlockHint(block: BlockItem): BlockHint | undefined ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | block | [BlockItem](../types/BlockItem) | The block item to get hint for | #### Returns `BlockHint | undefined` - Custom hint or `undefined` for default #### Example ```typescript public getBlockHint(block: BlockItem): BlockHint | undefined { if (block.name === BlockType.BLOCK_TEXT) { return { title: this.api.translate('blocks.text.hint.title'), description: this.api.translate('blocks.text.hint.description') }; } // Default hint for standard blocks return { title: block.title, description: block.description }; } ``` *** ### getBlocksPanelHeaderHtml() Generates HTML for the blocks panel header. ```typescript getBlocksPanelHeaderHtml(): string | undefined ``` #### Returns `string | undefined` - Custom header HTML or `undefined` for no header #### Example ```typescript public getBlocksPanelHeaderHtml(): string | undefined { const config = this.api.getEditorConfig(); const brandName = config.brand?.name || 'Blocks'; return `

${brandName} Library

`; } ``` *** ### isPanelPlacementChangeEnabled() ::::tip Version Availability This method is available starting from v3.6.0 :::: Determines whether a draggable handle should be displayed in the modules panel. ```typescript isPanelPlacementChangeEnabled(): boolean ``` #### Returns `boolean` - `true` to enable panel placement changes, `false` to hide the drag handle #### Example ```typescript public isPanelPlacementChangeEnabled(): boolean { return false; } ``` *** ### getModulesPanelCollapsedHtml() Generates HTML for the modules panel in collapsed state. ```typescript getModulesPanelCollapsedHtml(): string | undefined ``` #### Returns `string | undefined` - Custom collapsed panel HTML or `undefined` for default #### Example ```typescript public getModulesPanelCollapsedHtml(): string | undefined { return `
📦 ${this.api.translate('modules.title')}
`; } ``` *** ### isModulesPanelCollapsedHintVisible() Determines whether a hint (tooltip) should be displayed for the collapsed modules panel. ```typescript isModulesPanelCollapsedHintVisible(): boolean ``` #### Returns `boolean` - `true` to show hint, `false` to hide #### Example ```typescript public isModulesPanelCollapsedHintVisible(): boolean { // Hide hints for the collapsed modules panel const config = this.api.getEditorConfig(); if (config.hideModulesHints) { return false; } return true; } ``` *** ### getHintDelay() Gets custom delay for showing hints. ```typescript getHintDelay(): number | undefined ``` #### Returns `number | undefined` - Delay in milliseconds or `undefined` for default (1000ms) #### Example ```typescript public getHintDelay(): number | undefined { // Show hints faster for new users const config = this.api.getEditorConfig(); if (config.user?.isNew) { return 200; // 200ms for new users } return 1000; // 1 second for experienced users } ``` *** ### getModulesPanelHint() Gets hint text for modules panel. ```typescript getModulesPanelHint(): BlockHint | undefined ``` #### Returns `BlockHint | undefined` - Custom hint or `undefined` for default #### Example ```typescript public getModulesPanelHint(): BlockHint { return { title: this.api.translate('Modules and structures'), description: this.api.translate('Click to open the modules and structures panel.'), }; } ``` *** ### getModulesTabIconName() Gets the icon name for the modules tab. Override to customize tab icons. ```typescript getModulesTabIconName(modulesTab: {key: string; label: Record}): string | undefined ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | modulesTab | `{key: string; label: Record}` | The modules tab configuration containing key and localized labels | #### Returns `string | undefined` - Icon name for the tab or `undefined` to use default icon or text #### Usage Notes * Return a custom icon name to override the default tab appearance * Return `undefined` to use the default icon or text label * Icon names should correspond to available icons in the editor's icon set #### Example ```typescript public getModulesTabIconName(modulesTab: {key: string; label: Record}): string | undefined { // Custom icons for different module tabs switch (modulesTab.key) { case 'general': return 'general-modules'; case 'email': return 'email-modules'; default: return undefined; } } ``` --- --- url: https://plugin.stripo.email/extensions/reference/blocks/ContextAction.md --- # ContextAction Core class for creating context actions in the Stripo Email Editor Extensions SDK. ```typescript class ContextAction ``` ## Description The `ContextAction` class provides the foundation for creating custom context menu actions that appear when users interact with email elements. ## Import ```typescript import { ContextAction } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### api ```typescript api: ContextActionApi ``` The API instance providing access to editor functionality, document modification capabilities, and core features. Automatically injected by the framework. ## Methods ### getId() Retrieves a unique identifier for the context action. ```typescript getId(): string ``` #### Returns `string` - Unique identifier for the action #### Usage Notes * Must be unique across all context actions #### Example ```typescript public getId(): string { return 'improve-action'; } ``` *** ### getIcon() Retrieves the URL or icon content representing this action. ```typescript getIcon(): string ``` #### Returns `string` - Icon source (URL or data URI) #### Usage Notes * Recommended size: 24x24px for optimal display * Supports SVG, PNG, JPG formats #### Example ```typescript import actionIcon from './assets/icon.svg'; public getIcon(): string { return actionIcon; } ``` *** ### getLabel() Retrieves the display label for the action. ```typescript getLabel(): string ``` #### Returns `string` - Display label for the menu item #### Usage Notes * Should be localized using the translation API * Keep labels concise and action-oriented * Use sentence case (e.g., "Duplicate block", not "DUPLICATE BLOCK") #### Example ```typescript public getLabel(): string { return this.api.translate('actions.duplicate.label'); // Returns: "Duplicate block" (in current language) } ``` *** ### onClick() Handles the action execution when clicked. ```typescript onClick(node: ImmutableHtmlNode): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | node | [ImmutableHtmlNode](../nodes/ImmutableHtmlNode) | The HTML node the action was triggered on | #### Usage Notes * This is where the main action logic goes * Use the API to modify the document * Handle errors gracefully #### Example ```typescript public onClick(node: ImmutableHtmlNode): void { // Perform the action this.api.getDocumentModifier() .modifyHtml(node) .setAttribute('data-tracking-id', Math.random()) .apply(new ModificationDescription('Added tracking parameter to block')); } ``` --- --- url: https://plugin.stripo.email/extensions/reference/controls/Control.md --- # Control Core class for creating custom controls in the Stripo Email Editor Extensions SDK. ```typescript class Control ``` ## Description The `Control` class provides the foundation for creating custom settings panel controls that allow users to modify email elements. Controls appear in the settings panel when an element is selected and provide an interface for editing properties such as colors, sizes, fonts, and other attributes. By extending this class, you can create rich, interactive controls with custom UI elements and behavior. ## Import ```typescript import { Control } from '@stripoinc/ui-editor-extensions'; ``` ## Constructor The constructor is typically called by the extension framework when instantiating your control. ## Properties ### api ```typescript api: ControlApi ``` Provides access to editor functionalities specific to this control instance, including document access, UI element management, and template modification capabilities. Automatically injected by the framework. ## Methods ### getId() Retrieves the unique identifier for this control type. ```typescript getId(): string ``` #### Returns `string` - Unique identifier for the control #### Usage Notes * Must be unique across all controls * Used for registration and internal referencing #### Example ```typescript public getId(): string { return 'custom-gradient-control'; } ``` *** ### getTemplate() Retrieves the HTML template string that defines the control's UI structure. ```typescript getTemplate(): string ``` #### Returns `string` - HTML template containing UI elements #### Usage Notes * Use custom UI element tags (e.g., `<${UIElementType.LABEL}>`, `<${UIElementType.SWITCHER}>`) * Can include custom HTML for layout and styling * Template is parsed and rendered by the editor #### Example ```typescript public getTemplate(): string { return `
<${UIElementType.LABEL} ${UEAttr.LABEL.text}="Settings:"> <${UIElementType.SWITCHER} ${UEAttr.SWITCHER.name}="enableFeature">
`; } ``` *** ### onTemplateNodeUpdated() Called whenever the underlying template node associated with this control is updated. ```typescript onTemplateNodeUpdated(node: ImmutableHtmlNode): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | node | [ImmutableHtmlNode](../nodes/ImmutableHtmlNode) | The updated HTML node representing the control's context | #### Usage Notes * Called on every modification to the selected element * Use to sync control values with the actual element state * Update UI elements to reflect current node properties * Avoid heavy computations - called frequently #### Example ```typescript public onTemplateNodeUpdated(node: ImmutableHtmlNode): void { // Extract values from the template const currentCount = parseInt(node.getAttribute('data-count') || '3'); const style = node.getAttribute('data-style') || 'classic'; // // Update UI to reflect template state this.api.updateValues({ 'itemCount': currentCount, 'styleSelect': style }); } ``` ### isVisible() Determines if the control should be visible in the control panel. ```typescript public isVisible(node: ImmutableHtmlNode): boolean ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | node | [ImmutableHtmlNode](../nodes/ImmutableHtmlNode) | The selected node | #### Returns `boolean` - `true` to show control, `false` to hide #### Default Implementation Returns `true` (always visible) #### Usage Notes * Override to conditionally show/hide control * Called on every node selection and modification * Use for context-sensitive controls #### Example ```typescript public isVisible(node: ImmutableHtmlNode): boolean { // Hide for locked elements if (node.hasClass('data-locked')) { return false; } return true; } ``` *** ### onRender() Hook called when the control is initially rendered. ```typescript onRender(): void ``` #### Usage Notes * Called once after the control's template is rendered * Use for initial setup and event listener attachment #### Example ```typescript public onRender(): void { // Set up value change handlers this.api.onValueChanged('backgroundColorPicker', (newColor, oldColor) => { this.applyBackgroundColor(newColor); }); //... } ``` *** ### onDestroy() Optional cleanup hook called when the control is being destroyed. ```typescript onDestroy(): void ``` #### Usage Notes * Called when control is removed from the panel * Use to clean up resources * Prevent memory leaks by proper cleanup * Clear any timers or intervals #### Example ```typescript public onDestroy(): void { // Clear any timers if (this.updateTimer) { clearTimeout(this.updateTimer); } } ``` --- --- url: https://plugin.stripo.email/extensions/reference/controls/GeneralPanelTab.md --- # GeneralPanelTab :::tip Version Availability This class is available starting from v3.5.0 ::: Core class for creating custom tabs in the "General" panel of the Stripo Email Editor. ```typescript class GeneralPanelTab ``` ## Description The `GeneralPanelTab` class allows developers to extend the editor's "General" panel (typically found in the left sidebar) with custom tabs. Unlike standard [Controls](./Control.md) which are context-sensitive to the selected block, General Panel Tabs provide global functionality or settings that are accessible regardless of which block is currently selected. ## Import ```typescript import { GeneralPanelTab } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### api Provides access to editor functionalities specific to this tab instance. ```typescript public api!: GeneralPanelTabApi ``` #### Type [GeneralPanelTabApi](../api/GeneralPanelTabApi.md) ## Methods ### getId() Retrieves the unique identifier for this tab. ```typescript public getId(): string ``` #### Returns `string` - Unique identifier for the tab #### Usage Notes * Must be unique across all registered tabs *** ### getIcon() Retrieves the icon representing this tab in the general panel header. ```typescript public getIcon(): string ``` #### Returns `string` - Icon key from the [IconsRegistry](../icons/IconsRegistry.md) *** ### getName() Retrieves the display name of the tab shown to the user in the header hint. ```typescript public getName(): string ``` #### Returns `string` - Localized tab name #### Usage Notes * Displayed as a tooltip or label for the tab icon * Use `this.api.translate()` for localization *** ### getTabIndex() Retrieves the position/order of the tab in the General panel. ```typescript public getTabIndex(): number ``` #### Returns `number` - The index position (0-based) *** ### getTemplate() Retrieves the HTML template string that defines the initial structure of the tab's content. ```typescript public getTemplate(): string ``` #### Returns `string` - HTML template #### Usage Notes * Defines the UI structure rendered when the tab is opened * Can use standard HTML and [UI Elements](../../reference/constants/UIElementType.md) *** ### isEnabled() Determines if the tab should be available for use in the editor. ```typescript public isEnabled(): boolean ``` #### Returns `boolean` - True if enabled. Defaults to `true`. ## Lifecycle Hooks ### onRender() Optional hook called when the general panel tab is initially rendered. ```typescript public onRender(): void ``` #### Usage Notes * Use for setup tasks like attaching event listeners to template elements * Initialize UI state *** ### onDocumentChanged() Lifecycle hook called when any part of the document template has changed. ```typescript public onDocumentChanged(): void ``` #### Usage Notes * Triggered frequently during editing * Use for global tab state synchronization with the document *** ### onDestroy() Optional cleanup hook called when the general panel tab is being destroyed. ```typescript public onDestroy(): void ``` #### Usage Notes * Clean up event listeners, timers, or other resources --- --- url: https://plugin.stripo.email/extensions/reference/controls/ModulesPanelTab.md --- # ModulesPanelTab ::::tip Version Availability This class is available starting from v3.7.0 :::: Core class for creating custom tabs in the "Modules" panel of the Stripo Email Editor. ```typescript class ModulesPanelTab ``` ## Description The `ModulesPanelTab` class allows developers to extend the editor's "Modules" panel with custom tabs. These tabs are useful for module libraries, reusable content collections, or any custom module browsing experience. ## Import ```typescript import { ModulesPanelTab } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### api Provides access to editor functionalities specific to this tab instance. ```typescript public api!: ModulesPanelTabApi ``` #### Type [ModulesPanelTabApi](../api/ModulesPanelTabApi.md) ## Methods ### getId() Retrieves the unique identifier for this tab. ```typescript public getId(): string ``` #### Returns `string` - Unique identifier for the tab #### Usage Notes * Must be unique across all registered modules panel tabs *** ### getIcon() Retrieves the icon representing this tab in the modules panel header. ```typescript public getIcon(): string ``` #### Returns `string` - Icon key from the [IconsRegistry](../icons/IconsRegistry.md) *** ### getName() Retrieves the display name of the tab shown to the user in the header hint. ```typescript public getName(): string ``` #### Returns `string` - Localized tab name #### Usage Notes * Displayed as a tooltip or label for the tab icon * Use `this.api.translate()` for localization *** ### getTabIndex() Retrieves the position/order of the tab in the Modules panel. ```typescript public getTabIndex(): number ``` #### Returns `number` - The index position (0-based) *** ### getTemplate() Retrieves the HTML template string that defines the initial structure of the tab's content. ```typescript public getTemplate(): string ``` #### Returns `string` - HTML template #### Usage Notes * Defines the UI structure rendered when the tab is opened * Can use standard HTML and [UI Elements](../../reference/constants/UIElementType.md) *** ### isEnabled() Determines if the tab should be available for use in the editor. ```typescript public isEnabled(): boolean ``` #### Returns `boolean` - True if enabled. Defaults to `true`. ## Lifecycle Hooks ### onRender() Optional hook called when the modules panel tab is initially rendered. ```typescript public onRender(): void ``` #### Usage Notes * Use for setup tasks like attaching event listeners to template elements * Initialize UI state *** ### onDocumentChanged() Lifecycle hook called when any part of the document template has changed. ```typescript public onDocumentChanged(): void ``` #### Usage Notes * Triggered frequently during editing * Use for tab state synchronization with the document --- --- url: https://plugin.stripo.email/extensions/reference/core/Extension.md --- # Extension Main container class that bundles all extension components for the Stripo Email Editor. ```typescript class Extension ``` ## Description The `Extension` class is the central container that holds all the components of your Stripo extension. It packages together blocks, controls, UI elements, external integrations, localization, and styles into a single deployable unit that can be loaded into the Stripo Email Editor. Extensions are typically created using the [ExtensionBuilder](./ExtensionBuilder.md) class which provides a fluent API for configuration. Once built, the extension is registered with the editor to make its functionality available to users. ## Import ```typescript import { Extension } from '@stripoinc/ui-editor-extensions'; ``` ## Constructor The `Extension` class constructor accepts an options object with all extension components. However it's recommended to use [ExtensionBuilder](./ExtensionBuilder.md) for creating extensions. ```typescript constructor(options: ExtensionOptions) ``` ### ExtensionOptions ```typescript interface ExtensionOptions { i18n: Record> | undefined; styles: string | undefined; previewStyles?: string; uiElements: ConstructorOfType[]; generalPanelTabs: ConstructorOfType[]; modulesPanelTabs: ConstructorOfType[]; controls: ConstructorOfType[]; contextActions: ConstructorOfType[]; blocks: ConstructorOfType[]; uiElementTagRegistry: ConstructorOfType | undefined; settingsPanelRegistry: ConstructorOfType | undefined; externalSmartElementsLibrary?: ConstructorOfType; externalImageLibrary?: ConstructorOfType; externalAiAssistant?: ConstructorOfType; externalDisplayConditionsLibrary?: ConstructorOfType; externalVideoLibrary?: ConstructorOfType; blocksPanel?: ConstructorOfType; iconsRegistry?: ConstructorOfType; externalImageLibraryTab?: ConstructorOfType; } ``` ## Methods ### getI18n() Returns the internationalization configuration for the extension. ```typescript public getI18n(): Record> | undefined ``` #### Returns `Record> | undefined` - Localization strings organized by language code *** ### getStyles() Returns the CSS styles that will be injected into the editor. ```typescript public getStyles(): string | undefined ``` #### Returns `string | undefined` - CSS styles as a string *** ### getPreviewStyles() Returns the CSS styles specific to the email preview mode. ```typescript public getPreviewStyles(): string | undefined ``` #### Returns `string | undefined` - Preview-specific CSS styles *** ### getUiElements() Returns all custom UI elements registered with the extension. ```typescript public getUiElements(): ConstructorOfType[] ``` #### Returns `ConstructorOfType[]` - Array of UI element constructors *** ### getUiElementTagRegistry() Returns the registry for custom UI element tags. ```typescript public getUiElementTagRegistry(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - UI element tag registry constructor *** ### getControls() Returns all controls (custom and built-in) registered with the extension. ```typescript public getControls(): ConstructorOfType[] ``` #### Returns `ConstructorOfType[]` - Array of control constructors *** ### getSettingsPanelRegistry() Returns the registry for custom settings panels. ```typescript public getSettingsPanelRegistry(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - Settings panel registry constructor *** ### getContextActions() Returns all context actions registered with the extension. ```typescript public getContextActions(): ConstructorOfType[] ``` #### Returns `ConstructorOfType[]` - Array of context action constructors *** ### getBlocks() Returns all custom blocks registered with the extension. ```typescript public getBlocks(): ConstructorOfType[] ``` #### Returns `ConstructorOfType[]` - Array of block constructors *** ### getGeneralPanelTabs() :::tip Version Availability This method is available starting from v3.5.0 ::: Returns all custom general panel tabs registered with the extension. ```typescript public getGeneralPanelTabs(): ConstructorOfType[] ``` #### Returns `ConstructorOfType[]` - Array of general panel tab constructors *** ### getModulesPanelTabs() ::::tip Version Availability This method is available starting from v3.7.0 :::: Returns all custom modules panel tabs registered with the extension. ```typescript public getModulesPanelTabs(): ConstructorOfType[] ``` #### Returns `ConstructorOfType[]` - Array of modules panel tab constructors *** ### getExternalSmartElementsLibrary() Returns the external smart elements library integration if configured. ```typescript public getExternalSmartElementsLibrary(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - Smart elements library constructor *** ### getExternalImageLibrary() Returns the external image library integration if configured. ```typescript public getExternalImageLibrary(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - Image library constructor *** ### getExternalImageLibraryTab() :::tip Version Availability This method is available starting from v3.2.0 ::: Returns the external image library tab if configured. ```typescript public getExternalImageLibraryTab(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - Image library tab constructor *** ### getExternalAiAssistant() Returns the external AI assistant integration if configured. ```typescript public getExternalAiAssistant(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - AI assistant constructor *** ### getExternalDisplayConditionsLibrary() Returns the external display conditions library if configured. ```typescript public getExternalDisplayConditionsLibrary(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - Display conditions library constructor *** ### getExternalVideoLibrary() Returns the external video library integration if configured. ```typescript public getExternalVideoLibrary(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - Video library constructor *** ### getBlocksPanel() Returns the custom blocks panel configuration if provided. ```typescript public getBlocksPanel(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - Blocks panel constructor *** ### getIconsRegistry() Returns the custom icons registry if configured. ```typescript public getIconsRegistry(): ConstructorOfType | undefined ``` #### Returns `ConstructorOfType | undefined` - Icons registry constructor #### Usage Notes * Allows registration of custom SVG icons for use in controls and UI elements * Icons can be referenced by key throughout the extension * Useful for maintaining consistent iconography across custom components ## Usage Example While you can instantiate `Extension` directly, it's recommended to use `ExtensionBuilder`: ```typescript import { ExtensionBuilder } from '@stripoinc/ui-editor-extensions'; import { MyCustomBlock } from './blocks/MyCustomBlock'; import { MyCustomControl } from './controls/MyCustomControl'; const extension = new ExtensionBuilder() .addBlock(MyCustomBlock) .addControl(MyCustomControl) .withLocalization({ 'en': { 'my.block.title': 'My Custom Block' } }) .addStyles(` .my-custom-control { padding: 20px; background: #f0f0f0; } `) .build(); ``` --- --- url: https://plugin.stripo.email/extensions/reference/core/ExtensionBuilder.md --- # ExtensionBuilder Builder class for creating extensions using a fluent API pattern. ```typescript class ExtensionBuilder ``` ## Description `ExtensionBuilder` provides a convenient fluent interface for constructing [Extension](./Extension.md) instances. It allows you to progressively configure all aspects of your extension - from blocks and controls to external integrations and styling - using method chaining. This approach makes extension configuration more readable and maintainable compared to direct constructor calls. The builder pattern ensures that all components are properly collected and configured before creating the final Extension instance. ## Import ```typescript import { ExtensionBuilder } from '@stripoinc/ui-editor-extensions'; ``` ## Methods ### withLocalization() Sets the internationalization configuration for the extension. ```typescript public withLocalization(i18n: Record>): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | i18n | `Record>` | Localization strings organized by language code | #### Returns `this` - The builder instance for method chaining #### Example ```typescript builder.withLocalization({ 'en': { 'button.label': 'Click Me', 'block.title': 'Custom Block' }, 'es': { 'button.label': 'Haz Clic', 'block.title': 'Bloque Personalizado' } }) ``` *** ### addStyles() Adds CSS styles for editor UI to the extension. Can be called multiple times to add styles incrementally. ```typescript public addStyles(styles: string): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | styles | `string` | CSS styles to inject into the editor | #### Returns `this` - The builder instance for method chaining #### Usage Notes * Can be called multiple times to add styles incrementally * All styles are concatenated when the extension is built #### Example ```typescript builder .addStyles('.my-block { padding: 20px; }') .addStyles('.my-control { border: 1px solid #ccc; }') ``` *** ### withPreviewStyles() Sets CSS styles specific to the email template preview. ```typescript public withPreviewStyles(styles: string): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | styles | `string` | CSS styles for preview mode only | #### Returns `this` - The builder instance for method chaining #### Usage Notes * These styles are only applied in preview template frame * Useful for setting styles of selected block (borders, context actions, etc.) * Does not affect the editor interface *** ### addBlock() Registers a custom block with the extension. ```typescript public addBlock(block: ConstructorOfType): this ``` #### Parameters | Name | Type | Description | |------|----------------------------------------------------------------------------|-------------| | block | [ConstructorOfType\](../blocks/Block.md) | Block class constructor | #### Returns `this` - The builder instance for method chaining #### Example ```typescript import { MyHeroBlock } from './blocks/MyHeroBlock'; import { MyTestimonialBlock } from './blocks/MyTestimonialBlock'; builder .addBlock(MyHeroBlock) .addBlock(MyTestimonialBlock) ``` *** ### addControl() Registers a custom control or built-in control extension. ```typescript public addControl(control: ConstructorOfType): this ``` #### Parameters | Name | Type | Description | |------|-------------------------------------------------------------------------|-------------| | control | [ConstructorOfType\](../controls/Control.md) | Control class constructor | #### Returns `this` - The builder instance for method chaining *** ### addContextAction() Registers a custom context action for blocks. ```typescript public addContextAction(contextAction: ConstructorOfType): this ``` #### Parameters | Name | Type | Description | |------|----------------------------------------------------------------------------------------------|-------------| | contextAction | [ConstructorOfType\](../blocks/ContextAction.md) | Context action class constructor | #### Returns `this` - The builder instance for method chaining *** ### addUiElement() Registers a custom UI element. ```typescript public addUiElement(uiElement: ConstructorOfType): this ``` #### Parameters | Name | Type | Description | |------|---------------------------------------------------------------------------------------------|-------------| | uiElement | [ConstructorOfType\](../ui-elements/UIElement.md) | UI element class constructor | #### Returns `this` - The builder instance for method chaining *** ### withUiElementTagRegistry() Sets the UI element tag registry for custom element tags. ```typescript public withUiElementTagRegistry( uiElementTagRegistry: ConstructorOfType ): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | uiElementTagRegistry | [ConstructorOfType\](../ui-elements/UIElementTagRegistry.md) | Tag registry class constructor | #### Returns `this` - The builder instance for method chaining *** ### withSettingsPanelRegistry() Sets the settings panel registry for custom settings panels. ```typescript public withSettingsPanelRegistry( settingsPanelRegistry: ConstructorOfType ): this ``` #### Parameters | Name | Type | Description | |------|-----------------------------------------------------------------------------------------------------------------------------------|-------------| | settingsPanelRegistry | [ConstructorOfType\](../settings-panel/SettingsPanelRegistry.md) | Registry class constructor | #### Returns `this` - The builder instance for method chaining *** ### withBlocksPanel() Sets a custom blocks panel configuration. ```typescript public withBlocksPanel(blocksPanel: ConstructorOfType): this ``` #### Parameters | Name | Type | Description | |------|---------------------------------------------------------------------------------------------|-------------| | blocksPanel | [ConstructorOfType\](../blocks/BlocksPanel.md) | Blocks panel class constructor | #### Returns `this` - The builder instance for method chaining *** ### withExternalImageLibrary() Integrates an external image library. ```typescript public withExternalImageLibrary( externalImageLibrary: ConstructorOfType ): this ``` #### Parameters | Name | Type | Description | |------|------------------------------------------------------------------------------------------------------------------------------|-------------| | externalImageLibrary | [ConstructorOfType\](../integrations/ExternalImageLibrary.md) | Image library class constructor | #### Returns `this` - The builder instance for method chaining *** ### withExternalImageLibraryTab() :::tip Version Availability This method is available starting from v3.2.0 ::: Registers a custom tab within the external image library interface. ```typescript public withExternalImageLibraryTab( externalImageLibraryTab: ConstructorOfType ): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | externalImageLibraryTab | [ConstructorOfType\](../integrations/ExternalImageLibraryTab.md) | Image library tab class constructor | #### Returns `this` - The builder instance for method chaining *** ### withExternalVideosLibrary() Integrates an external video library. ```typescript public withExternalVideosLibrary( externalVideoLibrary: ConstructorOfType ): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | externalVideoLibrary | [ConstructorOfType\](../integrations/ExternalVideosLibrary.md) | Video library class constructor | #### Returns `this` - The builder instance for method chaining *** ### withExternalSmartElementsLibrary() Integrates an external smart elements library. ```typescript public withExternalSmartElementsLibrary( externalSmartElementsLibrary: ConstructorOfType ): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | externalSmartElementsLibrary | [ConstructorOfType\](../integrations/ExternalSmartElementsLibrary.md) | Smart elements library constructor | #### Returns `this` - The builder instance for method chaining *** ### withExternalAiAssistant() Integrates an external AI assistant. ```typescript public withExternalAiAssistant( externalAiAssistant: ConstructorOfType ): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | externalAiAssistant | [ConstructorOfType\](../integrations/ExternalAiAssistant.md) | AI assistant class constructor | #### Returns `this` - The builder instance for method chaining *** ### withExternalDisplayCondition() Integrates an external display conditions library. ```typescript public withExternalDisplayCondition( externalDisplayCondition: ConstructorOfType ): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | externalDisplayCondition | [ConstructorOfType\](../integrations/ExternalDisplayConditionsLibrary.md) | Display conditions library constructor | #### Returns `this` - The builder instance for method chaining *** ### withIconsRegistry() Sets a custom icons registry for registering SVG icons. ```typescript public withIconsRegistry(iconsRegistry: ConstructorOfType): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | iconsRegistry | [ConstructorOfType\](../icons/IconsRegistry.md) | Icons registry class constructor | #### Returns `this` - The builder instance for method chaining #### Usage Notes * Allows registration of custom SVG icons for use in controls and UI elements * Icons can be referenced by key throughout the extension * Useful for maintaining consistent iconography across custom components #### Example ```typescript import { MyIconsRegistry } from './icons/MyIconsRegistry'; builder.withIconsRegistry(MyIconsRegistry) ``` *** ### addGeneralPanelTab() :::tip Version Availability This method is available starting from v3.5.0 ::: Registers a custom tab within the "General" panel of the editor. ```typescript public addGeneralPanelTab(tab: ConstructorOfType): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | tab | [ConstructorOfType\](../controls/GeneralPanelTab.md) | General panel tab class constructor | #### Returns `this` - The builder instance for method chaining #### Example ```typescript import { MyGlobalSettingsTab } from './tabs/MyGlobalSettingsTab'; builder.addGeneralPanelTab(MyGlobalSettingsTab) ``` *** ### addModulesPanelTab() ::::tip Version Availability This method is available starting from v3.7.0 :::: Registers a custom tab within the "Modules" panel of the editor. ```typescript public addModulesPanelTab(tab: ConstructorOfType): this ``` #### Parameters | Name | Type | Description | |------|------|-------------| | tab | [ConstructorOfType\](../controls/ModulesPanelTab.md) | Modules panel tab class constructor | #### Returns `this` - The builder instance for method chaining #### Example ```typescript import { MyModulesTab } from './tabs/MyModulesTab'; builder.addModulesPanelTab(MyModulesTab) ``` *** ### build() Creates the final Extension instance with all configured components. ```typescript public build(): Extension ``` #### Returns `Extension` - The configured extension instance ready for registration --- --- url: https://plugin.stripo.email/extensions/reference/icons/IconsRegistry.md --- # IconsRegistry Core class for registering custom SVG icons in the Stripo Editor extension. ```typescript class IconsRegistry ``` ## Description `IconsRegistry` provides a mechanism to register custom SVG icons that can be used throughout your extension in controls, UI elements, and other components. By extending this class and implementing the `registerIconsSvg()` method, you can define a collection of SVG icons that will be available to your extension components. This is particularly useful when you need custom iconography that matches your brand or provides specific functionality not available in the default icon set. ## Import ```typescript import { IconsRegistry } from '@stripoinc/ui-editor-extensions'; ``` ## Methods ### registerIconsSvg() Registers a map of SVG icons that will be available throughout the extension. ```typescript public registerIconsSvg(iconsMap: Record): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | iconsMap | `Record` | Map of icon keys to SVG string content | #### Returns `void` #### Implementation Requirements * Must be implemented in your custom registry class * Each icon should be provided as a complete SVG string * Icon keys should be descriptive and unique * SVG content should be valid and properly formatted ## Usage ### Basic Implementation ```typescript import { IconsRegistry } from '@stripoinc/ui-editor-extensions'; export class MyIconsRegistry extends IconsRegistry { public registerIconsSvg(iconsMap: Record): void { iconsMap['my-custom-icon'] = ` `; iconsMap['my-star-icon'] = ` `; } } ``` ### Registering with Extension ```typescript import { ExtensionBuilder } from '@stripoinc/ui-editor-extensions'; import { MyIconsRegistry } from './icons/MyIconsRegistry'; const extension = new ExtensionBuilder() .withIconsRegistry(MyIconsRegistry) .build(); ``` ### Using Registered Icons Once registered, icons can be referenced by their keys in controls and UI elements: ```typescript // In a custom control export class MyCustomControl extends Control { public getIcon(): string { return 'my-custom-icon'; // References the registered icon key } } ``` ## Best Practices ### Icon Design * **Consistent sizing**: Use consistent viewBox dimensions across all icons (e.g., 24x24) * **Simple paths**: Keep SVG paths simple for better performance * **No inline styles**: Avoid inline fill/stroke colors to allow dynamic theming * **Optimize SVGs**: Minify SVG content to reduce payload size ### Naming Conventions ```typescript // Good: Descriptive, kebab-case names iconsMap['user-settings'] = '...'; iconsMap['export-pdf'] = '...'; iconsMap['color-picker'] = '...'; // Avoid: Generic or unclear names iconsMap['icon1'] = '...'; iconsMap['custom'] = '...'; ``` ### Performance Considerations ```typescript export class MyIconsRegistry extends IconsRegistry { public registerIconsSvg(iconsMap: Record): void { // Register only icons you actually use // Don't import entire icon libraries unnecessarily iconsMap['needed-icon-1'] = ICON_SVG_1; iconsMap['needed-icon-2'] = ICON_SVG_2; } } ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/constants/AiAssistantValueType.md --- # AiAssistantValueType Enum defining the types of content that can be processed by AI assistants. ```typescript enum AiAssistantValueType ``` ## Description `AiAssistantValueType` identifies different types of email content that AI assistants can generate, modify, or optimize. This enum is used when integrating external AI services to specify what type of content is being processed, allowing the AI to apply appropriate generation strategies and constraints. ## Import ```typescript import { AiAssistantValueType } from '@stripoinc/ui-editor-extensions'; ``` ## Enum Values ### SUBJECT Email subject line content. ```typescript SUBJECT = 'subject' ``` *** ### HIDDEN\_PREHEADER Hidden preheader text that appears in email previews. ```typescript HIDDEN_PREHEADER = 'hiddenPreheader' ``` **Purpose**: AI generation of preview text shown in inbox listings. *** ### TEXT\_BLOCK Body text content within email blocks. ```typescript TEXT_BLOCK = 'textBlock' ``` **Purpose**: AI generation and enhancement of email body content. --- --- url: https://plugin.stripo.email/extensions/reference/constants/BlockAttr.md --- # BlockAttr Configuration object defining supported attributes for different block types. ```typescript const BlockAttr: { EMPTY_CONTAINER: EmptyContainerAttributes; CONTAINER: ContainerAttributes; BLOCK_IMAGE: ImageAttributes; BLOCK_BUTTON: ButtonAttributes; } ``` ## Description `BlockAttr` provides a structured definition of attributes supported by various block types in the Stripo Email Editor. These attributes define the configurable properties that can be set on blocks, enabling dynamic behavior and customization. Each block type has its own set of relevant attributes that correspond to its functionality. ## Import ```typescript import { BlockAttr } from '@stripoinc/ui-editor-extensions'; ``` ## Block Attribute Definitions ### CONTAINER Attributes for container blocks. ```typescript CONTAINER: { widthPercent: 'width-percent' } ``` #### Attributes | Attribute | Type | Description | |-----------|------|-------------| | widthPercent | `string` | Container width as percentage of structure width | #### Usage Example ```typescript getTemplate() { const {STRUCTURE, CONTAINER, BLOCK_TEXT, BLOCK_BUTTON} = BlockType; // Create a two-column structure: // - First column: 50% width containing a Text Block // - Second column: 50% width containing a Button Block return ` <${STRUCTURE}> <${CONTAINER} ${BlockAttr.CONTAINER.widthPercent}="50"> <${BLOCK_TEXT}>

Lorem ipsum dolor sit amet

<${CONTAINER} ${BlockAttr.CONTAINER.widthPercent}="50"> <${BLOCK_BUTTON} ${BlockAttr.BLOCK_BUTTON.href}="https://stripo.email"> Click me ` } ``` *** ### EMPTY\_CONTAINER Attributes for empty container blocks. ```typescript EMPTY_CONTAINER: { widthPercent: 'width-percent', blocks: 'blocks' } ``` #### Attributes | Attribute | Type | Description | |-----------|------|------------------------------------------------------------------| | widthPercent | `string` | Container width as percentage of structure width | | blocks | `string` | List of blocks ids available for insertion through quick actions | #### Usage Example ```typescript getTemplate() { const {STRUCTURE, EMPTY_CONTAINER, BLOCK_IMAGE, BLOCK_TEXT} = BlockType; // Specify block IDs that will appear as quick-add icons in the empty container return ` <${STRUCTURE}> <${EMPTY_CONTAINER} ${BlockAttr.EMPTY_CONTAINER.widthPercent}="100" ${BlockAttr.EMPTY_CONTAINER.blocks}="${BLOCK_IMAGE}, ${BLOCK_TEXT}, simple-block"> ` } ``` *** ### BLOCK\_IMAGE Attributes for image blocks. ```typescript BLOCK_IMAGE: { src: 'src', alt: 'alt', href: 'href' } ``` #### Attributes | Attribute | Type | Description | |-----------|------|-------------| | src | `string` | Image URL | | alt | `string` | Alternative text for accessibility | | href | `string` | URL the image links to when clicked | #### Usage Example ```typescript getTemplate() { const {STRUCTURE, CONTAINER, BLOCK_IMAGE} = BlockType; return ` <${STRUCTURE}> <${CONTAINER} ${BlockAttr.CONTAINER.widthPercent}="100"> <${BLOCK_IMAGE} ${BlockAttr.BLOCK_IMAGE.src}="https://hpy.stripocdn.email/content/guids/CABINET_e5244175dd1729a1d6ee1f8bd0d5490f/images/50421523966142571.jpg" ${BlockAttr.BLOCK_IMAGE.alt}="Lorem ipsum" ${BlockAttr.BLOCK_IMAGE.href}="https://stripo.email"> ` } ``` *** ### BLOCK\_BUTTON Attributes for button blocks. ```typescript BLOCK_BUTTON: { href: 'href' } ``` #### Attributes | Attribute | Type | Description | |-----------|------|-------------| | href | `string` | URL the button links to when clicked | #### Usage Example ```typescript getTemplate() { const {STRUCTURE, CONTAINER, BLOCK_BUTTON} = BlockType; return ` <${STRUCTURE}> <${CONTAINER} ${BlockAttr.CONTAINER.widthPercent}="100"> <${BLOCK_BUTTON} ${BlockAttr.BLOCK_BUTTON.href}="https://stripo.email"> Click me ` } ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/constants/BlockCompositionType.md description: >- The four allowed composition values a block can declare — BLOCK, CONTAINER, STRUCTURE and STRIPE — and where each one may be placed in an email template. --- # BlockCompositionType Enumeration defining the composition types for blocks in the Stripo Email Editor Extensions SDK. ```typescript enum BlockCompositionType { BLOCK = 'BLOCK', CONTAINER = 'CONTAINER', STRUCTURE = 'STRUCTURE', STRIPE = 'STRIPE' } ``` ## Description The `BlockCompositionType` enum categorizes blocks based on their structural role and nesting capabilities within email templates. It determines how blocks interact with each other, what they can contain, and where they can be placed in the template hierarchy. ## Import ```typescript import { BlockCompositionType } from '@stripoinc/ui-editor-extensions'; ``` ## Enum Values ### BLOCK Represents an atomic block that contains content. ```typescript BlockCompositionType.BLOCK = 'BLOCK' ``` #### Characteristics * **Content-focused** - Contains actual email content (text, images, buttons, etc.) * **Can be inserted into** - Containers * **Examples** - Text blocks, image blocks, button blocks, video blocks, social icons #### Usage Example ```typescript import {Block, BlockCompositionType} from '@stripoinc/ui-editor-extensions'; export class ButtonBlock extends Block { public getBlockCompositionType(): BlockCompositionType { return BlockCompositionType.BLOCK; } public getTemplate(): string { return `
`; } // Additional block configuration methods... } ``` *** ### CONTAINER Represents a container that can hold atomic blocks. ```typescript BlockCompositionType.CONTAINER = 'CONTAINER' ``` #### Characteristics * **Holds atomic blocks** - Can contain BLOCK type components * **Layout management** - Manages arrangement of child blocks * **Can be inserted into** - Structures * **Can be saved as module** - If enabled via `canBeSavedAsModule()` * **Examples** - Product cards, feature boxes, content sections #### Usage Example ```typescript import {Block, BlockAttr, BlockCompositionType, BlockType} from '@stripoinc/ui-editor-extensions'; export class SimpleBlock extends Block { getBlockCompositionType() { return BlockCompositionType.CONTAINER; } getTemplate() { const {CONTAINER, BLOCK_TEXT, BLOCK_BUTTON} = BlockType; return ` <${CONTAINER}> <${BLOCK_TEXT}>

Lorem ipsum dolor sit amet

<${BLOCK_BUTTON} ${BlockAttr.BLOCK_BUTTON.href}="https://stripo.email"> Click me ` } // Additional block configuration methods... } ``` *** ### STRUCTURE Represents a structure that can hold containers with blocks inside them. ```typescript BlockCompositionType.STRUCTURE = 'STRUCTURE' ``` #### Characteristics * **Top-level organization** - Holds containers which hold blocks * **Multi-column layouts** - Manages complex structural arrangements * **Can be inserted into** - Stripes (email sections) * **Can be saved as module** - If enabled via `canBeSavedAsModule()` * **Examples** - Multi-column layouts, product grids, newsletter sections #### Usage Example ```typescript import {Block, BlockAttr, BlockCompositionType, BlockType} from '@stripoinc/ui-editor-extensions'; export class StructureBlock extends Block { getBlockCompositionType() { return BlockCompositionType.STRUCTURE; } getTemplate() { const {STRUCTURE, CONTAINER, BLOCK_TEXT, BLOCK_BUTTON} = BlockType; // Create a two-column structure: // - First column: 50% width containing a Text Block // - Second column: 50% width containing a Button Block return ` <${STRUCTURE}> <${CONTAINER} ${BlockAttr.CONTAINER.widthPercent}="50"> <${BLOCK_TEXT}>

Lorem ipsum dolor sit amet

<${CONTAINER} ${BlockAttr.CONTAINER.widthPercent}="50"> <${BLOCK_BUTTON} ${BlockAttr.BLOCK_BUTTON.href}="https://stripo.email"> Click me ` } allowInnerBlocksSelection() { return true; // Allow selection of blocks inside the structure } allowInnerBlocksDND() { return false; // Disable drag and drop of blocks inside the structure } // Additional block configuration methods... } ``` *** ### STRIPE Represents a stripe-level block container (top-level email section). ```typescript BlockCompositionType.STRIPE = 'STRIPE' ``` #### Characteristics * **Top-level sections** - Contains structures or containers * **Template layout** - Represents email stripes/sections * **Examples** - Header stripe, footer stripe, content stripe #### Usage Example ```typescript import {Block, BlockAttr, BlockCompositionType, BlockType} from '@stripoinc/ui-editor-extensions'; export class StripeBlock extends Block { getBlockCompositionType() { return BlockCompositionType.STRIPE; } getTemplate() { const {STRIPE, STRUCTURE, CONTAINER, BLOCK_TEXT} = BlockType; return ` <${STRIPE}> <${STRUCTURE}> <${CONTAINER}> <${BLOCK_TEXT}>

Stripe content

` } } ``` *** ## Template Hierarchy The composition types follow a strict hierarchy in email templates: ``` Document └── Stripe (BlockCompositionType.STRIPE) └── Structure (BlockCompositionType.STRUCTURE) └── Container (BlockCompositionType.CONTAINER) └── Block (BlockCompositionType.BLOCK) ``` --- --- url: https://plugin.stripo.email/extensions/reference/constants/BlockType.md --- # BlockType Comprehensive enum of all supported block types in the Stripo Email Editor. ```typescript enum BlockType ``` ## Description `BlockType` enumerates all available block types in the Stripo Email Editor, including default content blocks, container blocks, and custom block types. This enum is crucial for determining and handling the type of each block when configuring or managing controls for existing blocks in the editor. ## Import ```typescript import { BlockType } from '@stripoinc/ui-editor-extensions'; ``` ## Default Content Blocks ### BLOCK\_IMAGE Image content block. ```typescript BLOCK_IMAGE = 'BLOCK_IMAGE' ``` **Features**: Image upload, alt text, links, responsive sizing *** ### BLOCK\_TEXT Text content block. ```typescript BLOCK_TEXT = 'BLOCK_TEXT' ``` **Features**: Rich text editing, formatting, links, merge tags *** ### BLOCK\_BUTTON Call-to-action button block. ```typescript BLOCK_BUTTON = 'BLOCK_BUTTON' ``` **Features**: Customizable styling, links, hover effects *** ### BLOCK\_SPACER Vertical spacing block. ```typescript BLOCK_SPACER = 'BLOCK_SPACER' ``` **Features**: Adjustable height, responsive spacing *** ### BLOCK\_VIDEO Video content block. ```typescript BLOCK_VIDEO = 'BLOCK_VIDEO' ``` **Features**: Video embedding, thumbnails, play buttons *** ### BLOCK\_SOCIAL Social media links block. ```typescript BLOCK_SOCIAL = 'BLOCK_SOCIAL' ``` **Features**: Social icons, customizable networks, styling *** ### BLOCK\_BANNER Banner section block. ```typescript BLOCK_BANNER = 'BLOCK_BANNER' ``` **Features**: Background images, overlays, text content *** ### BLOCK\_TIMER Countdown timer block. ```typescript BLOCK_TIMER = 'BLOCK_TIMER' ``` **Features**: Dynamic countdown, customizable end time *** ### BLOCK\_MENU Navigation menu block. ```typescript BLOCK_MENU = 'BLOCK_MENU' ``` **Features**: responsive menu *** ### BLOCK\_MENU\_ITEM Individual menu item within menu block. ```typescript BLOCK_MENU_ITEM = 'BLOCK_MENU_ITEM' ``` **Features**: Link, text, styling options *** ### BLOCK\_HTML Custom HTML block. ```typescript BLOCK_HTML = 'BLOCK_HTML' ``` **Features**: Raw HTML input, code editor ## AMP Blocks ### BLOCK\_AMP\_CAROUSEL AMP carousel component. ```typescript BLOCK_AMP_CAROUSEL = 'BLOCK_AMP_CAROUSEL' ``` **Features**: AMP-compatible image carousel *** ### BLOCK\_AMP\_ACCORDION AMP accordion component. ```typescript BLOCK_AMP_ACCORDION = 'BLOCK_AMP_ACCORDION' ``` **Features**: Collapsible content sections for AMP emails *** ### BLOCK\_AMP\_FORM AMP form component. ```typescript BLOCK_AMP_FORM = 'BLOCK_AMP_FORM' ``` **Features**: Interactive forms in AMP emails ## Container Blocks ### CONTAINER Basic container for holding other blocks. ```typescript CONTAINER = 'CONTAINER' ``` **Purpose**: Column within structures, holds content blocks *** ### STRUCTURE Row structure containing containers. ```typescript STRUCTURE = 'STRUCTURE' ``` **Purpose**: Creates multi-column layouts *** ### STRIPE Top-level section container. ```typescript STRIPE = 'STRIPE' ``` **Purpose**: Full-width sections of the email *** ### EMPTY\_CONTAINER Placeholder container with no content. ```typescript EMPTY_CONTAINER = 'EMPTY_CONTAINER' ``` **Purpose**: Drop zone for adding blocks --- --- url: >- https://plugin.stripo.email/extensions/reference/constants/ContextActionType.md --- # ContextActionType Enum defining available context actions for blocks in the editor. ```typescript enum ContextActionType ``` ## Description `ContextActionType` enumerates all the context actions that can be performed on blocks within the Stripo Email Editor. These actions appear in context menus when users right-click on blocks or access block options. Extensions can implement custom handlers for these actions or add them to their custom blocks. ## Import ```typescript import { ContextActionType } from '@stripoinc/ui-editor-extensions'; ``` ## Enum Values ### SAVE\_AS\_MODULE Saves the current block as a reusable module. ```typescript SAVE_AS_MODULE = 'saveAsModule' ``` **Usage**: Allows users to save frequently used blocks as modules for reuse across different emails. *** ### IMPROVE\_WITH\_AI Enhances the block content using AI assistance. ```typescript IMPROVE_WITH_AI = 'improveWithAI' ``` **Usage**: Triggers AI-powered improvements for text, layout, or styling. *** ### MOVE Moves the block to a different position in the email. ```typescript MOVE = 'move' ``` **Usage**: Enables drag-and-drop or cut-and-paste functionality for blocks. *** ### COPY Creates a copy of the block. ```typescript COPY = 'copy' ``` **Usage**: Duplicates the block for use elsewhere in the email. *** ### REMOVE Deletes the block from the email. ```typescript REMOVE = 'remove' ``` **Usage**: Permanently removes the block from the template. *** ### CLEAR\_CONTAINER Removes all content from a container block. ```typescript CLEAR_CONTAINER = 'clearContainer' ``` **Usage**: Empties a container while preserving its structure and settings. *** ### EXTERNAL\_DISPLAY\_CONDITION Sets display conditions using an external library. ```typescript EXTERNAL_DISPLAY_CONDITION = 'externalDisplayCondition' ``` **Usage**: Integrates with external services to set conditional display rules. --- --- url: https://plugin.stripo.email/extensions/reference/constants/EditorState.md --- # EditorState :::::tip Version Availability This interface is available starting from v3.8.0 ::::: Typed object returned by `BaseApi.getEditorState()`. ```typescript interface EditorState { previewDeviceMode: PreviewDeviceMode; panelPosition: PanelPosition; themeMode: ThemeMode; } ``` ## Description The `EditorState` interface groups the observable editor UI state into a single typed object. It lets extensions inspect the current preview mode, panel layout, and active editor theme. ## Import ```typescript import { EditorState } from '@stripoinc/ui-editor-extensions'; ``` ## Properties | Property | Type | Description | |----------|------|-----------------------------------| | `previewDeviceMode` | [`PreviewDeviceMode`](./PreviewDeviceMode) | Current device preview mode | | `panelPosition` | [`PanelPosition`](./PanelPosition) | Current panel arrangement | | `themeMode` | [`ThemeMode`](./ThemeMode) | Current email template theme mode | ## Example ```typescript const state = this.api.getEditorState(); if (state.previewDeviceMode === PreviewDeviceMode.MOBILE) { this.useMobileLayout(); } if (state.themeMode === ThemeMode.DARK) { this.useDarkThemeStyles(); } ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/constants/EditorStatePropertyType.md --- # EditorStatePropertyType Enum defining observable properties of the editor's active state in the Stripo Email Editor Extensions SDK. ```typescript enum EditorStatePropertyType { previewDeviceMode = 'previewDeviceMode', panelPosition = 'panelPosition', themeMode = 'themeMode' } ``` ## Description The `EditorStatePropertyType` enum identifies specific editor state properties that extensions can monitor for changes. It enables reactive behavior in extensions by allowing them to subscribe to state changes and update their functionality accordingly. ## Import ```typescript import { EditorStatePropertyType } from '@stripoinc/ui-editor-extensions'; ``` ## Enum Values ### previewDeviceMode Property containing information about the active preview mode. ```typescript EditorStatePropertyType.previewDeviceMode = 'previewDeviceMode' ``` #### Description Represents the current device preview mode in the editor (desktop or mobile). #### Value Type When Subscribed `'DESKTOP' | 'MOBILE'` #### Usage Notes * Monitors changes between desktop and mobile preview modes * Useful for adapting block behavior based on preview context * Commonly used to show different content or layouts per device * Value changes when user switches preview mode in the editor #### Example ```typescript this.api.onEditorStatePropUpdated( EditorStatePropertyType.previewDeviceMode, (newMode, oldMode) => { console.log(`Preview changed from ${oldMode} to ${newMode}`); } ); ``` ### panelPosition Property containing information about the current panel layout configuration. ```typescript EditorStatePropertyType.panelPosition = 'panelPosition' ``` #### Description Represents the current arrangement of the blocks panel and settings panel in the editor interface. #### Value Type When Subscribed `'BLOCKS_SETTINGS' | 'SETTINGS_BLOCKS'` #### Usage Notes * Monitors changes in panel layout configuration * Useful for adapting extension UI based on panel positioning * `BLOCKS_SETTINGS`: Blocks panel on left, settings panel on right (default) * `SETTINGS_BLOCKS`: Settings panel on left, blocks panel on right * Value changes when panel layout is reconfigured in the editor #### Example ```typescript this.api.onEditorStatePropUpdated( EditorStatePropertyType.panelPosition, (newPosition, oldPosition) => { console.log(`Panel layout changed from ${oldPosition} to ${newPosition}`); } ); ``` ### themeMode Property containing information about the email template theme. ```typescript EditorStatePropertyType.themeMode = 'themeMode' ``` #### Description Represents the current email template UI theme. #### Value Type When Subscribed `'LIGHT' | 'DARK'` #### Usage Notes * Monitors changes between light and dark email template themes * Useful for adapting extension UI styles to match the email template theme * Value changes when the email template theme changes #### Example ```typescript this.api.onEditorStatePropUpdated( EditorStatePropertyType.themeMode, (newTheme, oldTheme) => { console.log(`Theme changed from ${oldTheme} to ${newTheme}`); } ); ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/constants/OrderableItemIconPosition.md --- # OrderableItemIconPosition Enum defining the position of the drag handle icon in orderable UI elements. ```typescript enum OrderableItemIconPosition ``` ## Description `OrderableItemIconPosition` specifies where the drag handle icon should be positioned within orderable list items. This affects both the visual appearance and user interaction patterns when reordering items. ## Import ```typescript import { OrderableItemIconPosition } from '@stripoinc/ui-editor-extensions'; ``` ## Values ### TOP Position the drag handle icon at the top of the item. ```typescript TOP = 'TOP' ``` *** ### LEFT Position the drag handle icon on the left side of the item. ```typescript LEFT = 'LEFT' ``` ## Usage Examples ### Using TOP Position (Default) ```javascript import { UIElementType, UEAttr } from '@stripoinc/ui-editor-extensions'; class MyControl extends Control { getTemplate() { return `
<${UIElementType.ORDERABLE} name="myList"> <${UIElementType.ORDERABLE_ITEM}> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Item 1">
`; } } ``` ### Using LEFT Position ```javascript import { UIElementType, UEAttr } from '@stripoinc/ui-editor-extensions'; class MyControl extends Control { getTemplate() { return `
<${UIElementType.ORDERABLE} name="myList" position="LEFT"> <${UIElementType.ORDERABLE_ITEM}> <${UIElementType.LABEL} ${UEAttr.LABEL.text}="Item 1">
`; } } ``` --- --- url: https://plugin.stripo.email/extensions/reference/constants/PanelPosition.md --- # PanelPosition Enum defining the layout position of panels in the Stripo Email Editor. ```typescript enum PanelPosition ``` ## Description The `PanelPosition` enum specifies how the blocks panel and settings panel are arranged in the editor interface. This determines the overall layout of the editing workspace, affecting where users find the blocks library and element settings. ## Import ```typescript import { PanelPosition } from '@stripoinc/ui-editor-extensions'; ``` ## Enum Values | Value | Description | |-------|-------------| | `BLOCKS_SETTINGS` | Blocks panel on the left, settings panel on the right | | `SETTINGS_BLOCKS` | Settings panel on the left, blocks panel on the right | ## Detailed Value Descriptions ### BLOCKS\_SETTINGS ```typescript PanelPosition.BLOCKS_SETTINGS = 'BLOCKS_SETTINGS' ``` The default and most common layout: * **Left side**: Blocks panel (drag-and-drop elements) * **Center**: Email template canvas * **Right side**: Settings panel (element properties) This layout follows the natural left-to-right workflow: select a block → drop it in the template → configure its settings. ### SETTINGS\_BLOCKS ```typescript PanelPosition.SETTINGS_BLOCKS = 'SETTINGS_BLOCKS' ``` Alternative reversed layout: * **Left side**: Settings panel (element properties) * **Center**: Email template canvas * **Right side**: Blocks panel (drag-and-drop elements) This layout may be preferred for right-to-left languages or specific workflow preferences. --- --- url: >- https://plugin.stripo.email/extensions/reference/constants/PreviewDeviceMode.md --- # PreviewDeviceMode Enumeration defining the available device preview modes in the Stripo Email Editor. ```typescript enum PreviewDeviceMode { DESKTOP = 'DESKTOP', MOBILE = 'MOBILE' } ``` ## Description The `PreviewDeviceMode` enum represents the two primary device viewing modes available in the Stripo editor. It allows extensions to identify and respond to the current preview context, enabling responsive behavior and device-specific optimizations in email templates. ## Import ```typescript import { PreviewDeviceMode } from '@stripoinc/ui-editor-extensions'; ``` ## Enum Values | Value | Description | |-------|-------------| | `DESKTOP` | Desktop view mode (typically 600px+ width) | | `MOBILE` | Mobile view mode (typically 320px-480px width) | ## Detailed Value Descriptions ### DESKTOP ```typescript PreviewDeviceMode.DESKTOP = 'DESKTOP' ``` Represents the desktop preview mode: * Full-width email template view * Default editing mode for most users * Shows how emails appear on desktop email clients * Typically renders at 600px or wider #### Use Cases * Default layout and styling * Multi-column structures * Larger images and typography * Desktop-optimized interactive elements ### MOBILE ```typescript PreviewDeviceMode.MOBILE = 'MOBILE' ``` Represents the mobile preview mode: * Narrow viewport email template view * Shows responsive behavior and mobile optimizations * Simulates mobile email client rendering * Typically renders at 320px to 480px width #### Use Cases * Single-column layouts * Touch-optimized button sizes * Simplified navigation menus * Mobile-specific content hiding/showing --- --- url: https://plugin.stripo.email/extensions/reference/constants/SettingsTab.md --- # SettingsTab Enum defining standard settings panel tab identifiers. ```typescript enum SettingsTab ``` ## Description `SettingsTab` provides standardized identifiers for the main tabs in block settings panels. These predefined tab types help maintain consistency across the editor interface and ensure that similar settings are grouped logically. While you can create custom tab identifiers, using these standard ones when appropriate improves user experience through familiar organization. ## Import ```typescript import { SettingsTab } from '@stripoinc/ui-editor-extensions'; ``` ## Enum Values ### SETTINGS General configuration and layout settings. ```typescript SETTINGS = 'settings' ``` **Purpose**: Primary settings that control block behavior and layout. *** ### STYLES Visual appearance and styling options. ```typescript STYLES = 'styles' ``` **Purpose**: Controls that affect the visual presentation. *** ### DATA Data binding and dynamic content settings. ```typescript DATA = 'data' ``` **Purpose**: Configuration for dynamic content and data sources. --- --- url: https://plugin.stripo.email/extensions/reference/constants/ThemeMode.md --- # ThemeMode :::::tip Version Availability This enum is available starting from v3.9.0 ::::: Enum defining the email template theme mode exposed through editor state. ```typescript enum ThemeMode { LIGHT = 'LIGHT', DARK = 'DARK' } ``` ## Description `ThemeMode` identifies whether the email template is currently rendered in light or dark mode. Use it with [`BaseApi.getEditorState()`](../api/BaseApi#geteditorstate) or [`BaseApi.onEditorStatePropUpdated()`](../api/BaseApi#oneditorstatepropupdated) when extension UI should adapt to the editor theme. ## Import ```typescript import { ThemeMode } from '@stripoinc/ui-editor-extensions'; ``` ## Enum Values | Value | Description | |-------|-------------| | `LIGHT` | The email template is using the light theme | | `DARK` | The email template is using the dark theme | ## Example ```typescript const {themeMode} = this.api.getEditorState(); if (themeMode === ThemeMode.DARK) { this.api.sendEvent('extensions.theme.dark-mode-active', {}); } // Use this global API to change the email template theme // StripoEditorApi.templateThemeModeApi.setTemplateThemeMode('DARK'); ``` --- --- url: https://plugin.stripo.email/extensions/reference/constants/UEAttr.md --- # UEAttr Configuration object defining supported attributes for UI elements. ```typescript const UEAttr: { DEFAULT: UIElementAttributes; BUTTON: ButtonAttributes; CHECKBOX: CheckBoxAttributes; CHECK_BUTTONS: CheckButtonsAttributes; COLOR: UIElementAttributes; COUNTER: CounterAttributes; DATEPICKER: DatePickerAttributes; LABEL: LabelAttributes; MESSAGE: MessageAttributes; RADIO_BUTTONS: RadioButtonsAttributes; SELECTPICKER: SelectAttributes; FONT_FAMILY_SELECT: FontFamilySelectAttributes; SWITCHER: UIElementAttributes; TEXT: TextAttributes; TEXTAREA: TextAreaAttributes; ICON: IconAttributes; CHECK_ITEM: CheckItemAttributes; SELECT_ITEM: SelectItemAttributes; RADIO_ITEM: RadioItemAttributes; NESTED_CONTROL: NestedControlAttributes; EXPANDABLE: ExpandableAttributes; ORDERABLE: OrderableAttributes; ORDERABLE_ITEM: OrderableItemAttributes; ORDERABLE_ICON: OrderableIconAttributes; REPEATABLE: UIElementAttributes; DRAGGABLE_BLOCK: UIElementAttributes; AMP_FORM_SERVICE_PICKER: UIElementAttributes; MULTIPLE_SELECT: TextAttributes; } ``` ## Description `UEAttr` provides comprehensive attribute definitions for all UI elements in the Stripo Email Editor. Each UI element type has specific attributes that control its appearance, behavior, and data handling. These attributes can be set in templates or modified dynamically through the UIElement API. ## Import ```typescript import { UEAttr } from '@stripoinc/ui-editor-extensions'; ``` ## Common Attributes ### DEFAULT Base attributes shared by all UI elements. ```typescript DEFAULT: { name: 'name', disabled: 'disabled' } ``` | Attribute | Type | Description | |-----------|------|-------------| | name | `string` | Element identifier | | disabled | `boolean` | Disable interaction | ## Input Element Attributes ### BUTTON Button element attributes. ```typescript BUTTON: { name: 'name', disabled: 'disabled', caption: 'caption', icon: 'icon' } ``` | Attribute | Type | Description | |-----------|------|-------------| | caption | `string` | Button text | | icon | `string` | Icon identifier | *** ### CHECKBOX Checkbox element attributes. ```typescript CHECKBOX: { name: 'name', disabled: 'disabled', caption: 'caption' } ``` | Attribute | Type | Description | |-----------|------|-------------| | caption | `string` | Checkbox label | *** ### CHECK\_BUTTONS Check button group attributes. ```typescript CHECK_BUTTONS: { name: 'name', disabled: 'disabled', buttons: 'buttons' } ``` | Attribute | Type | Description | |-----------|------|-------------| | buttons | `string` | Button configuration | *** ### COUNTER Numeric counter attributes. ```typescript COUNTER: { name: 'name', disabled: 'disabled', minValue: 'min-value', maxValue: 'max-value', step: 'step' } ``` | Attribute | Type | Description | |-----------|------|-------------| | minValue | `number` | Minimum value | | maxValue | `number` | Maximum value | | step | `number` | Increment step | *** ### DATEPICKER Date picker attributes. ```typescript DATEPICKER: { name: 'name', disabled: 'disabled', placeholder: 'placeholder', minDate: 'min-date' } ``` | Attribute | Type | Description | |-----------|------|-------------| | placeholder | `string` | Placeholder text | | minDate | `string` | Minimum selectable date | ## Text Input Attributes ### TEXT Text input attributes. ```typescript TEXT: { name: 'name', disabled: 'disabled', placeholder: 'placeholder' } ``` | Attribute | Type | Description | |-----------|------|-------------| | placeholder | `string` | Placeholder text | *** ### TEXTAREA Textarea attributes. ```typescript TEXTAREA: { name: 'name', disabled: 'disabled', placeholder: 'placeholder', resizable: 'resizable' } ``` | Attribute | Type | Description | |-----------|------|-------------| | placeholder | `string` | Placeholder text | | resizable | `boolean` | Allow resizing | ## Selection Attributes ### SELECTPICKER Select dropdown attributes. ```typescript SELECTPICKER: { name: 'name', disabled: 'disabled', searchable: 'searchable', multiSelect: 'multi-select', placeholder: 'placeholder', items: 'items' } ``` | Attribute | Type | Description | |-----------|------|-------------| | searchable | `boolean` | Enable search | | multiSelect | `boolean` | Allow multiple selection | | placeholder | `string` | Placeholder text | | items | `string` | Option items | *** ### FONT\_FAMILY\_SELECT Font family selector attributes. ```typescript FONT_FAMILY_SELECT: { // All SELECTPICKER attributes plus: addCustomFontOption: 'add-custom-font-option' } ``` | Attribute | Type | Description | |-----------|------|-------------| | addCustomFontOption | `boolean` | Show "Add custom font" option | *** ### RADIO\_BUTTONS Radio button group attributes. ```typescript RADIO_BUTTONS: { name: 'name', disabled: 'disabled', buttons: 'buttons' } ``` | Attribute | Type | Description | |-----------|------|-------------| | buttons | `string` | Radio button configuration | ## Display Element Attributes ### LABEL Label element attributes. ```typescript LABEL: { name: 'name', disabled: 'disabled', text: 'text', hint: 'hint' } ``` | Attribute | Type | Description | |-----------|------|-------------| | text | `string` | Label text | | hint | `string` | Tooltip text | *** ### MESSAGE Message element attributes. ```typescript MESSAGE: { name: 'name', disabled: 'disabled', type: 'type', icon: 'icon' } ``` | Attribute | Type | Description | |-----------|------|-------------| | type | `string` | Message type (info/warning/error) | | icon | `string` | Icon identifier (available from v3.10.0) | *** ### ICON Icon element attributes. ```typescript ICON: { name: 'name', disabled: 'disabled', img: 'img', src: 'src', title: 'title', width: 'width', height: 'height', imageClass: 'image-class', hint: 'hint', isActive: 'is-active', visibility: 'visibility', transform: 'transform' } ``` | Attribute | Type | Description | |-----------|------|-------------| | img | `string` | Image element | | src | `string` | Image source URL | | title | `string` | Icon title | | width | `number` | Icon width | | height | `number` | Icon height | | imageClass | `string` | CSS class for image | | hint | `string` | Tooltip text | | isActive | `boolean` | Active state | | visibility | `string` | Visibility state | | transform | `string` | CSS transform | ## Component Item Attributes ### CHECK\_ITEM Checkbox item attributes. ```typescript CHECK_ITEM: { name: 'name', disabled: 'disabled', text: 'text', hint: 'hint', icon: 'icon', value: 'value' } ``` | Attribute | Type | Description | |-----------|------|-------------| | text | `string` | Item text | | hint | `string` | Tooltip | | icon | `string` | Item icon | | value | `string` | Item value | *** ### RADIO\_ITEM Radio item attributes. ```typescript RADIO_ITEM: { name: 'name', disabled: 'disabled', text: 'text', hint: 'hint', icon: 'icon', value: 'value' } ``` *** ### SELECT\_ITEM Select option attributes. ```typescript SELECT_ITEM: { name: 'name', disabled: 'disabled', text: 'text', value: 'value' } ``` | Attribute | Type | Description | |-----------|------|-------------| | text | `string` | Display text | | value | `string` | Option value | *** ### NESTED\_CONTROL Nested control container attributes. ```typescript NESTED_CONTROL: { name: 'name', disabled: 'disabled', controlId: 'control-id' } ``` | Attribute | Type | Description | |-----------|------|-------------| | controlId | `string` | ID of nested control | *** ## Container Element Attributes ### EXPANDABLE Expandable container attributes. ```typescript EXPANDABLE: { name: 'name', expanded: 'expanded' } ``` | Attribute | Type | Description | |-----------|------|-------------| | expanded | `boolean` | Initial expanded state | *** ### ORDERABLE Orderable list container attributes. ```typescript ORDERABLE: { name: 'name', icon: 'icon', position: 'position' } ``` | Attribute | Type | Description | |-----------|------|-------------| | icon | `string` | Icon name for drag handle | | position | `'TOP' \| 'LEFT'` | Position of drag handle icon | *** ### ORDERABLE\_ITEM Orderable list item attributes. ```typescript ORDERABLE_ITEM: { name: 'name' } ``` | Attribute | Type | Description | |-----------|------|-------------| | name | `string` | Unique identifier for the item (optional, auto-generated if not provided) | *** ### ORDERABLE\_ICON Orderable item drag handle icon attributes. ```typescript ORDERABLE_ICON: { name: 'name', icon: 'icon' } ``` | Attribute | Type | Description | |-----------|------|-------------| | icon | `string` | Icon name to override parent's drag handle | *** ### REPEATABLE ::::tip Version Availability This element is available starting from v3.5.0 :::: Repeatable list container attributes. ```typescript REPEATABLE: { name: 'name', disabled: 'disabled' } ``` | Attribute | Type | Description | |-----------|------|-------------| | name | `string` | Element identifier | | disabled | `boolean` | Disable interaction | *** ### DRAGGABLE\_BLOCK ::::tip Version Availability This element is available starting from v3.7.0 :::: Draggable block element attributes. ```typescript DRAGGABLE_BLOCK: { name: 'name', disabled: 'disabled', blockId: 'block-id' } ``` | Attribute | Type | Description | |-----------|------|-------------| | blockId | `string` | Block ID to bind to the draggable element | | name | `string` | Element identifier | | disabled | `boolean` | Disable interaction | *** ### AMP\_FORM\_SERVICE\_PICKER :::::tip Version Availability This element is available starting from v3.8.0 ::::: AMP form service picker attributes. For more details about configuring AMP services, see [Initialization Settings: AMP Form Services](https://plugin.stripo.email/editor-configuration/initialization-settings#amp-form-services). ```typescript AMP_FORM_SERVICE_PICKER: { name: 'name', disabled: 'disabled' } ``` | Attribute | Type | Description | |-----------|------|-------------| | name | `string` | Element identifier | | disabled | `boolean` | Disable interaction | *** ### MULTIPLE\_SELECT :::::tip Version Availability This element is available starting from v3.8.0 ::::: Multiple select element attributes. This UI element allows users to manage an array of tags in a single input. ```typescript MULTIPLE_SELECT: { name: 'name', disabled: 'disabled', placeholder: 'placeholder' } ``` | Attribute | Type | Description | |-----------|------|-------------| | name | `string` | Element identifier | | disabled | `boolean` | Disable interaction | | placeholder | `string` | Placeholder text shown when nothing is selected | --- --- url: https://plugin.stripo.email/extensions/reference/constants/UIElementType.md --- # UIElementType Enum defining all available UI element types in the editor. ```typescript enum UIElementType ``` ## Description `UIElementType` enumerates all the standard UI elements available in the Stripo Email Editor. These elements are the building blocks for creating controls and user interfaces within extensions. Each element type has specific attributes and behaviors that make them suitable for different interaction patterns. ## Import ```typescript import { UIElementType } from '@stripoinc/ui-editor-extensions'; ``` ## Input Elements ### BUTTON Clickable button element. ```typescript BUTTON = 'UE-BUTTON' ``` **Attributes**: caption, icon, disabled, name **Use Cases**: Actions, triggers, confirmations *** ### CHECKBOX Single checkbox input. ```typescript CHECKBOX = 'UE-CHECKBOX' ``` **Attributes**: caption, disabled, name **Use Cases**: Boolean options, feature toggles *** ### CHECK\_BUTTONS Group of checkbox buttons. ```typescript CHECK_BUTTONS = 'UE-CHECK-BUTTONS' ``` **Attributes**: buttons, disabled, name **Use Cases**: Multiple selection from options *** ### RADIO\_BUTTONS Group of radio buttons. ```typescript RADIO_BUTTONS = 'UE-RADIO-BUTTONS' ``` **Attributes**: buttons, disabled, name **Use Cases**: Single selection from options *** ### SWITCHER Toggle switch element. ```typescript SWITCHER = 'UE-SWITCHER' ``` **Attributes**: disabled, name **Use Cases**: On/off states, feature enablement ## Text Input Elements ### TEXT Single-line text input. ```typescript TEXT = 'UE-TEXT' ``` **Attributes**: placeholder, disabled, name **Use Cases**: Short text, names, titles *** ### TEXTAREA Multi-line text input. ```typescript TEXTAREA = 'UE-TEXTAREA' ``` **Attributes**: placeholder, resizable, disabled, name **Use Cases**: Long text, descriptions, content ## Selection Elements ### SELECTPICKER Dropdown select element. ```typescript SELECTPICKER = 'UE-SELECT' ``` **Attributes**: searchable, multiSelect, placeholder, items, disabled, name **Use Cases**: Choosing from many options *** ### FONT\_FAMILY\_SELECT Specialized font family selector. ```typescript FONT_FAMILY_SELECT = 'UE-FONT-FAMILY-SELECT' ``` **Attributes**: All select attributes plus addCustomFontOption **Use Cases**: Font selection with custom font support *** ### AMP\_FORM\_SERVICE\_PICKER :::::tip Version Availability This element is available starting from v3.8.0 ::::: AMP form service picker element. For more details about configuring AMP services, see [Initialization Settings: AMP Form Services](https://plugin.stripo.email/editor-configuration/initialization-settings#amp-form-services). ```typescript AMP_FORM_SERVICE_PICKER = 'UE-AMP-FORM-SERVICE-PICKER' ``` **Attributes**: name, disabled **Use Cases**: AMP form service selection *** ### MULTIPLE\_SELECT :::::tip Version Availability This element is available starting from v3.8.0 ::::: Multiple select input element. This UI element allows users to manage an array of tags in a single input. ```typescript MULTIPLE_SELECT = 'UE-MULTIPLE_SELECT' ``` **Attributes**: placeholder, disabled, name **Use Cases**: Selecting multiple values with a dedicated UI element ## Specialized Input Elements ### COLOR Color picker element. ```typescript COLOR = 'UE-COLOR' ``` **Attributes**: disabled, name **Use Cases**: Color selection *** ### COUNTER Numeric counter input. ```typescript COUNTER = 'UE-COUNTER' ``` **Attributes**: minValue, maxValue, step, disabled, name **Use Cases**: Numeric values, quantities *** ### DATEPICKER Date selection element. ```typescript DATEPICKER = 'UE-DATEPICKER' ``` **Attributes**: placeholder, minDate, disabled, name **Use Cases**: Date selection, scheduling *** ### MERGETAGS Merge tag selector. ```typescript MERGETAGS = 'UE-MERGETAGS' ``` **Attributes**: disabled, name **Use Cases**: Dynamic content insertion ## Display Elements ### LABEL Text label element. ```typescript LABEL = 'UE-LABEL' ``` **Attributes**: text, hint, name **Use Cases**: Field labels, descriptions *** ### MESSAGE Message display element. ```typescript MESSAGE = 'UE-MESSAGE' ``` **Attributes**: type, icon, disabled, name **Use Cases**: Info, warning, error messages *** ### ICON Icon display element. ```typescript ICON = 'UE-ICON' ``` **Attributes**: img, src, title, width, height, hint, disabled, visibility **Use Cases**: Visual indicators, buttons with icons ## Component Elements ### CHECK\_ITEM Individual checkbox item. ```typescript CHECK_ITEM = 'UE-CHECK-ITEM' ``` **Attributes**: text, hint, icon, value, disabled, name **Use Cases**: Item in checkbox group *** ### RADIO\_ITEM Individual radio button item. ```typescript RADIO_ITEM = 'UE-RADIO-ITEM' ``` **Attributes**: text, hint, icon, value, disabled, name **Use Cases**: Item in radio group *** ### SELECT\_ITEM Individual select option. ```typescript SELECT_ITEM = 'UE-SELECT-ITEM' ``` **Attributes**: text, value, disabled, name **Use Cases**: Option in select dropdown ## Container Elements ### EXPANDABLE Expandable/collapsible container. ```typescript EXPANDABLE = 'UE-EXPANDABLE' ``` **Attributes**: expanded, name **Use Cases**: Collapsible sections, grouped controls *** ### EXPANDABLE\_HEADER Header section of an expandable container. ```typescript EXPANDABLE_HEADER = 'UE-EXPANDABLE_HEADER' ``` **Attributes**: N/A **Use Cases**: Title/header area for expandable sections *** ### EXPANDABLE\_CONTENT Content section of an expandable container. ```typescript EXPANDABLE_CONTENT = 'UE-EXPANDABLE_CONTENT' ``` **Attributes**: N/A **Use Cases**: Collapsible content area *** ### ORDERABLE Reorderable list container. ```typescript ORDERABLE = 'UE-ORDERABLE' ``` **Attributes**: icon, position, name **Use Cases**: Drag-and-drop reordering of items *** ### ORDERABLE\_ITEM Individual item within an orderable list. ```typescript ORDERABLE_ITEM = 'UE-ORDERABLE-ITEM' ``` **Attributes**: name **Use Cases**: Items that can be reordered within an orderable container *** ### ORDERABLE\_ICON Custom drag handle icon for an orderable item. ```typescript ORDERABLE_ICON = 'UE-ORDERABLE-ICON' ``` **Attributes**: icon, name **Use Cases**: Override default drag handle for specific items *** ### NESTED\_CONTROL Container for nested control elements. ```typescript NESTED_CONTROL = 'UE-NESTED-CONTROL' ``` **Attributes**: controlId **Use Cases**: Embedding controls within other controls *** ### REPEATABLE :::tip Version Availability This element is available starting from v3.5.0 ::: Container for repeatable list of items. ```typescript REPEATABLE = 'UE-REPEATABLE' ``` **Attributes**: name **Use Cases**: Dynamic lists of items (e.g., gallery items, menu links, social icons) *** ### DRAGGABLE\_BLOCK ::::tip Version Availability This element is available starting from v3.7.0 :::: Draggable block reference element. ```typescript DRAGGABLE_BLOCK = 'UE-DRAGGABLE-BLOCK' ``` **Attributes**: block-id, name, disabled **Use Cases**: Drag-and-drop block selectors, module pickers *** ### SCROLLABLE :::::tip Version Availability This element is available starting from v3.8.0 ::::: Scrollable container element. ```typescript SCROLLABLE = 'UE-SCROLLABLE-CONTAINER' ``` **Attributes**: No dedicated `UEAttr` mapping is currently documented **Use Cases**: Scrollable side panels, constrained lists, overflow-heavy layouts --- --- url: >- https://plugin.stripo.email/extensions/reference/integrations/ExternalAiAssistant.md --- # ExternalAiAssistant Core class for integrating external AI assistants with the Stripo Email Editor. ```typescript class ExternalAiAssistant ``` ## Description `ExternalAiAssistant` enables integration of third-party AI services (like OpenAI, Claude, or custom AI solutions) into the Stripo Email Editor. This interface allows users to leverage AI for generating and improving email content including subject lines, preheaders, and body text. The AI assistant can provide contextual suggestions based on the type of content being edited. ## Import ```typescript import { ExternalAiAssistant } from '@stripoinc/ui-editor-extensions'; ``` ## Methods ### openAiAssistant() Opens the AI assistant interface for content generation or improvement. ```typescript openAiAssistant(params: { value: string; onDataSelectCallback: ExternalAiAssistantCallback; onCancelCallback: ExternalAiAssistantCancelCallback; type: AiAssistantValueType; }): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | value | `string` | Current content to be improved or context for generation | | onDataSelectCallback | `ExternalAiAssistantCallback` | Called when AI generates content | | onCancelCallback | `ExternalAiAssistantCancelCallback` | Called when user cancels | | type | `AiAssistantValueType` | Type of content (SUBJECT, HIDDEN\_PREHEADER, TEXT\_BLOCK) | ## Callback Types ### ExternalAiAssistantCallback Called when AI assistant generates content. ```typescript type ExternalAiAssistantCallback = (html: string) => void ``` **Parameters**: * `html` - Generated HTML content from the AI *** ### ExternalAiAssistantCancelCallback Called when the user cancels the AI assistant. ```typescript type ExternalAiAssistantCancelCallback = () => void ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/integrations/ExternalDisplayConditionsLibrary.md --- # ExternalDisplayConditionsLibrary Core class for integrating external display condition systems with the Stripo Email Editor. ```typescript class ExternalDisplayConditionsLibrary ``` ## Description `ExternalDisplayConditionsLibrary` enables integration of third-party conditional display systems that control when and to whom email content is shown. This interface allows users to set sophisticated display rules based on recipient attributes, behaviors, segments, or any custom logic. Display conditions enable personalized email experiences by showing or hiding content blocks dynamically based on defined criteria. ## Import ```typescript import { ExternalDisplayConditionsLibrary } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### api ```typescript api: ExternalDisplayConditionsApi ``` #### Type [ExternalDisplayConditionsApi](../api/ExternalDisplayConditionsApi.md) The API instance providing access to editor configuration, translations, and other core functionality for display conditions integrations. ## Methods ### getCategoryName() Returns the name of the display condition category. ```typescript getCategoryName(): string ``` #### Returns `string` - The name of the category *** ### openExternalDisplayConditionsDialog() Opens the display conditions configuration dialog. ```typescript openExternalDisplayConditionsDialog( currentCondition: DisplayCondition, successCallback: ExternalDisplayConditionSelectedCB, cancelCallback: () => void ): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | currentCondition | `DisplayCondition` | Current display condition to edit | | successCallback | `ExternalDisplayConditionSelectedCB` | Called with updated condition | | cancelCallback | `() => void` | Called when dialog is cancelled | *** ### getIsContextActionEnabled() Determines if the context action for this library is enabled. ```typescript getIsContextActionEnabled(): boolean ``` #### Returns `boolean` - True if the context action should be shown *** ### getContextActionIndex() Gets the display position for the context action. ```typescript getContextActionIndex(): number ``` #### Returns `number` - Index position for the context action in the menu ## Type Definitions ### DisplayCondition Represents a display condition configuration. ```typescript interface DisplayCondition { id: number; // ID of the condition name: string; // name of the condition description: string; // description of the condition beforeScript: string; // script to run before the condition afterScript: string; // script to run after the condition extraData?: string; // extra custom data, can be set by user conditionsCount?: number; // number of individual conditions represented } ``` ### ExternalDisplayConditionSelectedCB Callback for when a condition is selected. ```typescript type ExternalDisplayConditionSelectedCB = (condition: DisplayCondition) => void ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/integrations/ExternalImageLibrary.md --- # ExternalImageLibrary Core class for integrating external image libraries with the Stripo Email Editor. ```typescript class ExternalImageLibrary ``` ## Description `ExternalImageLibrary` enables integration of third-party image storage and management services (like Cloudinary, Unsplash, or custom DAM systems) into the Stripo Email Editor. This interface allows users to browse, search, and select images from external sources directly within the editor, streamlining the workflow for adding images to email templates. ## Import ```typescript import { ExternalImageLibrary } from '@stripoinc/ui-editor-extensions'; ``` ## Methods ### openImageLibrary() Opens the external image library interface for image selection. ```typescript openImageLibrary( currentImageUrl: string, onImageSelectCallback: ExternalGalleryImageSelectCallback, onCancelCallback: ExternalGalleryImageCancelCallback ): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | currentImageUrl | `string` | URL of the currently selected image (if any) | | onImageSelectCallback | `ExternalGalleryImageSelectCallback` | Called when user selects an image | | onCancelCallback | `ExternalGalleryImageCancelCallback` | Called when user cancels | ## Type Definitions ### Callback Types ```typescript type ExternalGalleryImageSelectCallback = (image: ExternalGalleryImage) => void; type ExternalGalleryImageCancelCallback = () => void; ``` ### ExternalGalleryImage Represents an image from the external gallery. ```typescript interface ExternalGalleryImage { originalName: string; // Original filename (e.g., 'product-photo.png') width: number; // Width in pixels height: number; // Height in pixels sizeBytes: number; // File size in bytes url: string; // Full URL to the image altText: string; // Alt text for accessibility labels?: Record; // Optional metadata (v3.2.0+) } ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/integrations/ExternalImageLibraryTab.md --- # ExternalImageLibraryTab :::tip Version Availability This class is available starting from v3.2.0 ::: Create custom tab within the external image library interface to organize images from different sources. ```typescript class ExternalImageLibraryTab ``` ## Description The `ExternalImageLibraryTab` class allows you to add a custom tab to the image library interface. ## Import ```typescript import { ExternalImageLibraryTab } from '@stripoinc/ui-editor-extensions'; ``` ## Methods ### getName() Returns the translated name/label for the tab. ```typescript public getName(): string ``` #### Returns `string` - The tab label to display in the image library interface *** ### openImageLibraryTab() Opens the custom tab and provides a container for rendering your image library UI. ```typescript public openImageLibraryTab( container: HTMLElement, onImageSelectCallback: ExternalGalleryImageSelectCallback, selectedNode?: ImmutableHtmlNode ): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | container | `HTMLElement` | DOM element where your custom UI should be rendered | | onImageSelectCallback | `ExternalGalleryImageSelectCallback` | Callback to invoke when a user selects an image | | selectedNode | `ImmutableHtmlNode` | (Optional) Selected node for which the gallery is being opened. Available from v3.4.0 | --- --- url: >- https://plugin.stripo.email/extensions/reference/integrations/ExternalSmartElementsLibrary.md --- # ExternalSmartElementsLibrary Core class for integrating external smart elements libraries with the Stripo Email Editor. ```typescript class ExternalSmartElementsLibrary ``` ## Description `ExternalSmartElementsLibrary` enables integration of dynamic content libraries that provide smart, data-driven elements for email templates. Smart elements are pre-configured, reusable components that can include dynamic content, personalization, product recommendations, or any complex HTML structures that adapt based on data. This interface allows users to browse and insert these intelligent components directly into their emails. ## Import ```typescript import { ExternalSmartElementsLibrary } from '@stripoinc/ui-editor-extensions'; ``` ## Methods ### openSmartElementsLibrary() Opens the smart elements library interface for element selection. ```typescript openSmartElementsLibrary( onDataSelectCallback: ExternalSmartElementSelectCallback, onCancelCallback: ExternalSmartElementCancelCallback ): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | onDataSelectCallback | `ExternalSmartElementSelectCallback` | Called when user selects a smart element | | onCancelCallback | `ExternalSmartElementCancelCallback` | Called when user cancels | ## Type Definitions ### ExternalSmartElement Represents a smart element as a key-value map of properties. ```typescript type ExternalSmartElement = Record ``` **Common Properties**: * `p_name` - Product name * `p_image` - Product image link * `p_price` - Product price * Custom properties based on element type ### Callback Types ```typescript type ExternalSmartElementSelectCallback = (smartElement: ExternalSmartElement) => void; type ExternalSmartElementCancelCallback = () => void; ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/integrations/ExternalVideosLibrary.md --- # ExternalVideosLibrary Core class for integrating external video libraries with the Stripo Email Editor. ```typescript class ExternalVideosLibrary ``` ## Description `ExternalVideosLibrary` enables integration of third-party video hosting and management services (like YouTube, Vimeo, Wistia, or custom video platforms) into the Stripo Email Editor. This interface allows users to browse, search, and select videos from external sources, automatically generating video thumbnails with play buttons for email-safe video representation. ## Import ```typescript import { ExternalVideosLibrary } from '@stripoinc/ui-editor-extensions'; ``` ## Methods ### openExternalVideosLibraryDialog() Opens the external video library interface for video selection. ```typescript openExternalVideosLibraryDialog( currentValue: string, successCallback: ExternalVideosLibrarySelectedCallback, cancelCallback: ExternalVideosLibraryCancelCallback ): void ``` #### Parameters | Name | Type | Description | |-----------------|-----------------------------------------|----------------------------------| | currentValue | `string` | Currently selected video URL | | successCallback | `ExternalVideosLibrarySelectedCallback` | Called when user selects a video | | cancelCallback | `ExternalVideosLibraryCancelCallback` | Called when user cancels | ## Type Definitions ### ExternalGalleryVideo Represents a video from the external gallery. ```typescript type ExternalGalleryVideo = { originalVideoName: string; // Video name/title originalImageName: string; // Thumbnail image name urlImage: string; // Thumbnail image URL urlVideo: string; // Video URL or embed link hasCustomButton: boolean; // Whether to use custom play button altText: string; // Alt text for video thumbnail } ``` ### Callback Types ```typescript type ExternalVideosLibrarySelectedCallback = (value: ExternalGalleryVideo) => void; type ExternalVideosLibraryCancelCallback = () => void; ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/modification/TemplateModifier.md --- # TemplateModifier Interface for managing template modifications in the Stripo Email Editor Extensions SDK. ```typescript interface TemplateModifier ``` ## Description The `TemplateModifier` interface provides a unified API for modifying both HTML and CSS nodes in email templates. It follows a fluent interface pattern, allowing method chaining for multiple modifications before applying them as a single transaction. ## Import ```typescript import { TemplateModifier } from '@stripoinc/ui-editor-extensions'; ``` ## Properties None ## Methods ### modifyHtml() Sets the HTML node for modification and returns an HtmlNodeModifier instance. ```typescript modifyHtml(node: ImmutableHtmlNode): HtmlNodeModifier ``` #### Parameters | Parameter | Type | Description | |-----------|-----------------------------------------------------------------------|-------------| | node | [ImmutableHtmlNode](/extensions/reference/nodes/ImmutableHtmlNode) | The HTML node to modify | #### Returns [HtmlNodeModifier](./HtmlNodeModifier) - Interface for chaining HTML modifications #### Usage Notes * Call this method to begin modifying an HTML element * Multiple HTML nodes can be modified in a single transaction * Changes are not applied until `apply()` is called #### Example ```typescript const modifier = this.api.getDocumentModifier(); modifier.modifyHtml(buttonNode) .setStyle('background', 'blue') .apply(description); ``` *** ### modifyCss() Sets the CSS node for modification and returns a CssNodeModifier instance. ```typescript modifyCss(node: ImmutableCssNode): CssNodeModifier ``` #### Parameters | Parameter | Type | Description | |-----------|---------------------------------------------------------------------|------------------------| | node | [ImmutableCssNode](/extensions/reference/nodes/ImmutableCssNode) | The CSS node to modify | #### Returns [CssNodeModifier](./CssNodeModifier) - Interface for chaining CSS modifications #### Usage Notes * Use this method to modify CSS rules and properties * Can be combined with HTML modifications in the same transaction * Supports media queries and nested rules #### Example ```typescript modifier.modifyCss(cssNode) .setProperty('color', 'red') .setProperty('font-size', '16px') .apply(description); ``` *** ### apply() Applies all accumulated modifications as a single transaction. ```typescript apply(description: ModificationDescription): void ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | description | [ModificationDescription](./ModificationDescription) | Description object providing context about the modifications | #### Returns `void` #### Usage Notes * This method executes all queued modifications * Modifications are applied atomically * The description is used for version history and undo/redo #### Example ```typescript modifier .modifyHtml(htmlNode) .setInnerHtml('New content') .modifyCss(cssNode) .setProperty('margin', '10px') .apply(new ModificationDescription('Updated content and styling')); ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/modification/HtmlNodeModifier.md --- # HtmlNodeModifier Interface for modifying HTML nodes within email templates. ```typescript interface HtmlNodeModifier extends TemplateModifier ``` ## Description `HtmlNodeModifier` provides methods for manipulating HTML elements, including content modification, attribute management, styling, and advanced features such as display conditions and structure management. ## Import ```typescript import { HtmlNodeModifier } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Extends: [`TemplateModifier`](./TemplateModifier) ## Methods ### Content Modification Methods #### setInnerHtml() Replaces the inner HTML content of the current node. ```typescript setInnerHtml(html: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `html` | `string` | The HTML string to set as inner content | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(element) .setInnerHtml('

New content

'); ``` *** #### setText() Updates the text content of an HTML text node. ```typescript setText(newValue: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `newValue` | `string` | The new text value | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(textNode) .setText('Updated text content'); ``` *** #### append() Appends HTML content as the last child of the current node. ```typescript append(html: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `html` | `string` | The HTML string to append | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(container) .append('
Additional content
'); ``` *** #### prepend() Prepends HTML content as the first child of the current node. ```typescript prepend(html: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `html` | `string` | The HTML string to prepend | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(container) .prepend('
Header content
'); ``` *** #### replaceWith() Replaces the current node with new HTML content. ```typescript replaceWith(html: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `html` | `string` | The HTML string to replace the node with | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(oldElement) .replaceWith('
New element
'); ``` *** ### Attribute Methods #### setAttribute() Sets or updates an attribute on the current HTML node. ```typescript setAttribute(name: string, value: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `name` | `string` | The attribute name | | `value` | `string` | The attribute value | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(link) .setAttribute('href', 'https://example.com') .setAttribute('target', '_blank'); ``` *** #### removeAttribute() Removes an attribute from the current HTML node. ```typescript removeAttribute(name: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `name` | `string` | The attribute name to remove | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(element) .removeAttribute('data-old-value'); ``` *** #### setValue() Sets the value attribute for form elements. ```typescript setValue(value: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `value` | `string` | The value to set | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(inputElement) .setValue('default text'); ``` *** ### Class Methods #### setClass() Adds a CSS class to the current node. ```typescript setClass(name: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `name` | `string` | The CSS class name to add | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(element) .setClass('highlighted') .setClass('active'); ``` *** #### removeClass() Removes a CSS class from the current node. ```typescript removeClass(name: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `name` | `string` | The CSS class name to remove | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(element) .removeClass('disabled'); ``` *** #### setHiddenElementState() Sets the device-specific hidden state for the current HTML node. ```typescript setHiddenElementState(state: HideElementState): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `state` | [`HideElementState`](/extensions/reference/types/HideElementState) | The hidden state to apply | **Returns:** `HtmlNodeModifier` for method chaining **Usage Notes:** * The editor resolves the canonical target node for the current HTML node (BLOCK, CONTAINER, STRUCTURE, STRIPE) * Use `'desktop'` to hide the target on desktop * Use `'mobile'` to hide the target on mobile * Use `undefined` to clear the device-specific hidden state **Example:** ```typescript modifier.modifyHtml(element) .setHiddenElementState('mobile'); ``` *** ### Style Methods #### setStyle() Sets a CSS style property on the current node. ```typescript setStyle(property: string, value: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `property` | `string` | The CSS property name | | `value` | `string` | The CSS property value | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(element) .setStyle('background-color', '#f0f0f0') .setStyle('padding', '10px'); ``` *** #### removeStyle() Removes a CSS style property from the current node. ```typescript removeStyle(property: string): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `property` | `string` | The CSS property name to remove | **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(element) .removeStyle('margin'); ``` *** ### Advanced Methods #### delete() Removes the current node from the DOM tree. ```typescript delete(): HtmlNodeModifier ``` **Returns:** `HtmlNodeModifier` for method chaining **Example:** ```typescript modifier.modifyHtml(elementToRemove) .delete(); ``` *** #### setDisplayCondition() Sets a display condition for conditional visibility. ```typescript setDisplayCondition(condition: DisplayCondition): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|-----------------------------------------------------------------------|-------------| | `condition` | [`DisplayCondition`](/extensions/reference/types/DisplayCondition) | The display condition configuration | **Returns:** `HtmlNodeModifier` for method chaining **Usage Notes:** * Display conditions control element visibility based on runtime evaluation * Useful for personalization, A/B testing, and dynamic content * Conditions are evaluated when the email is rendered **Example:** ```typescript modifier.modifyHtml(element) .setDisplayCondition({ id: 1, name: 'Female', description: 'Only female customers will see this part of the email.', beforeScript: '{% if contact.gender == \"Female\" %}', afterScript: '{% endif %}' }); ``` *** #### setNodeConfig() Attaches custom configuration data to the node. ```typescript setNodeConfig(config: Record): HtmlNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `config` | `Record` | Configuration object to attach | **Returns:** `HtmlNodeModifier` for method chaining **Usage Notes:** * Store extension-specific data without using HTML attributes * Configuration is preserved during template operations * Synchronized across all users in collaborative editing **Example:** ```typescript modifier.modifyHtml(widgetNode) .setNodeConfig({ widgetType: 'countdown', endDate: '2024-12-31T23:59:59Z', format: 'DD:HH:MM:SS', onExpiry: 'hide' }); ``` *** #### multiRowStructureModifier() Returns a modifier for creating and modifying email structures. ```typescript multiRowStructureModifier(): MultiRowStructureModifier ``` **Returns:** [`MultiRowStructureModifier`](./MultiRowStructureModifier) - Interface for structure modifications **Example:** ```typescript modifier.modifyHtml(structureNode) .multiRowStructureModifier() .updateLayout(['33%', '34%', '33%']); ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/modification/CssNodeModifier.md --- # CssNodeModifier Interface for modifying CSS nodes in email templates. ```typescript interface CssNodeModifier extends TemplateModifier ``` ## Description `CssNodeModifier` provides methods for manipulating CSS rules, properties, and media queries in email templates. It enables adding, modifying, and removing CSS rules while maintaining email client compatibility. ## Import ```typescript import { CssNodeModifier } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Extends: [`TemplateModifier`](./TemplateModifier) ## Methods ### Rule Management Methods #### insertRuleBefore() Inserts a CSS rule before a specified node. ```typescript insertRuleBefore(css: string, beforeNode: ImmutableCssNode): CssNodeModifier ``` | Parameter | Type | Description | |-----------|-----------------------------------------------------------------------|-------------| | `css` | `string` | The CSS rule string to insert | | `beforeNode` | [`ImmutableCssNode`](/extensions/reference/nodes/ImmutableCssNode) | The node to insert before | **Returns:** `CssNodeModifier` for method chaining **Throws:** Error if the node is not a rule or media type **Example:** ```typescript modifier.modifyCss(documentNode) .insertRuleBefore('.new-class { color: red; }', existingRule); ``` *** #### insertRuleAfter() Inserts a CSS rule after a specified node. ```typescript insertRuleAfter(css: string, afterNode: ImmutableCssNode): CssNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `css` | `string` | The CSS rule string to insert | | `afterNode` | [`ImmutableCssNode`](/extensions/reference/nodes/ImmutableCssNode) | The node to insert after | **Returns:** `CssNodeModifier` for method chaining **Throws:** Error if the node is not a rule or media type **Example:** ```typescript modifier.modifyCss(documentNode) .insertRuleAfter('@media (max-width: 600px) { .mobile { display: block; } }', mediaQuery); ``` *** #### appendRule() Appends a CSS rule as the last child of the current node. ```typescript appendRule(css: string): CssNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `css` | `string` | The CSS rule string to append | **Returns:** `CssNodeModifier` for method chaining **Throws:** Error if the node is not a rule or media type **Example:** ```typescript modifier.modifyCss(styleNode) .appendRule('.footer { background: #f0f0f0; padding: 20px; }'); ``` *** #### prependRule() Prepends a CSS rule as the first child of the current node. ```typescript prependRule(css: string): CssNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `css` | `string` | The CSS rule string to prepend | **Returns:** `CssNodeModifier` for method chaining **Throws:** Error if the node is not a rule or media type **Example:** ```typescript modifier.modifyCss(styleNode) .prependRule('* { box-sizing: border-box; }'); ``` *** #### removeRule() Removes the current CSS rule or media node. ```typescript removeRule(): CssNodeModifier ``` **Returns:** `CssNodeModifier` for method chaining **Throws:** Error if the node is not a rule or media type **Example:** ```typescript modifier.modifyCss(outdatedRule) .removeRule(); ``` *** ### Property Methods #### setProperty() Sets or updates a CSS property in the current rule. ```typescript setProperty(name: string, value: string): CssNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `name` | `string` | The CSS property name | | `value` | `string` | The CSS property value | **Returns:** `CssNodeModifier` for method chaining **Throws:** Error if multiple properties with the same name are found **Example:** ```typescript modifier.modifyCss(ruleNode) .setProperty('color', '#333333') .setProperty('font-size', '16px') .setProperty('line-height', '1.5'); ``` *** #### removeProperty() Removes a CSS property from the current rule. ```typescript removeProperty(name: string): CssNodeModifier ``` | Parameter | Type | Description | |-----------|------|-------------| | `name` | `string` | The CSS property name to remove | **Returns:** `CssNodeModifier` for method chaining **Throws:** Error if multiple properties with the same name are found **Example:** ```typescript modifier.modifyCss(ruleNode) .removeProperty('text-decoration') .removeProperty('text-transform'); ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/modification/MultiRowStructureModifier.md --- # MultiRowStructureModifier Interface for creating and modifying email structure layouts. ```typescript interface MultiRowStructureModifier ``` ## Description `MultiRowStructureModifier` provides high-level methods to manage email structure containers, handling the intricate details of email-compatible HTML generation. ## Import ```typescript import { MultiRowStructureModifier } from '@stripoinc/ui-editor-extensions'; ``` ## Access Access the `MultiRowStructureModifier` through the `HtmlNodeModifier`: ```typescript const structureModifier = modifier .modifyHtml(structureNode) .multiRowStructureModifier(); ``` ## Methods ### updateLayoutWithContent() Creates a new structure with specified containers and content. ```typescript updateLayoutWithContent( layout: StructureLayout[], containerContent: string[] ): HtmlNodeModifier ``` #### Parameters | Parameter | Type | Description | |--------------------|-----------------------------------------------------------------------|-----------------------------------------------------------------| | `layout` | [`StructureLayout[]`](/extensions/reference/types/StructureLayout.md) | Array of container configurations defining the structure layout | | `containerContent` | `string[]` | Array of HTML content strings for containers content | #### Returns [`HtmlNodeModifier`](./HtmlNodeModifier.md) for method chaining #### Description This method completely replaces the existing structure with a new layout defined by the container configuration and content distribution. It handles: * Responsive design implementation * MSO/Outlook compatibility * Automatic image resizing * Proper DOM positioning * Content distribution to content containers only #### Usage Notes * Content is only placed in content containers * Empty and spacer containers do not receive content * Number of content strings can be less than content containers (remaining will be empty) * Excess content is wrapped to the next line as a new structure #### Example ```typescript // Create a three-column layout with mixed container types structureModifier.updateLayoutWithContent( [ {width: '15%', contentType: 'SPACER'}, // Left margin '35%', // Content column 1 {width: '15%', contentType: 'EMPTY'}, // Placeholder for d&d content '35%' // Content column 2 ], [ `<${BlockType.BLOCK_TEXT}>

Content 1

`, `<${BlockType.BLOCK_TEXT}>

Content 2

` ] ); ``` *** ### updateLayout() Modifies the container layout while preserving existing content. ```typescript updateLayout(layout: StructureLayout[]): HtmlNodeModifier ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `layout` | [`StructureLayout[]`](/extensions/reference/types/StructureLayout.md) | New container layout configuration | #### Returns [`HtmlNodeModifier`](./HtmlNodeModifier.md) for method chaining #### Description This method changes the container arrangement of the current structure without losing existing content. Content is redistributed among the new container layout with intelligent handling of mismatches. #### Content Redistribution Rules 1. **More containers than content**: Empty containers are added 2. **Fewer containers than content**: Excess content is wrapped to the next line as a new structure 3. **Same number**: Content is mapped one-to-one 4. **Empty/Spacer containers**: Do not receive redistributed content #### Example ```typescript // Convert two-column to three-column layout structureModifier.updateLayout(['33%', '34%', '33%']); // Add margins with spacers structureModifier.updateLayout([ {width: '30%', contentType: 'SPACER'}, '40%', {width: '30%', contentType: 'SPACER'} ]); ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/modification/ModificationDescription.md --- # ModificationDescription Class for providing context about template modifications. ```typescript class ModificationDescription ``` ## Description `ModificationDescription` provides metadata about modifications for version history, undo/redo functionality, and internationalization support. Every modification applied through the Template Modifier API requires a description to document the change and provide context for collaboration and history tracking. ## Import ```typescript import { ModificationDescription } from '@stripoinc/ui-editor-extensions'; ``` ## Constructor ```typescript constructor(key: string) ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `key` | `string` | Description text or internationalization key | ### Example ```typescript // Simple text description const description = new ModificationDescription('Changed button color'); // Internationalization key const i18nDescription = new ModificationDescription('actions.button_color_changed'); ``` ## Methods ### withParams() Adds parameters for template string interpolation. ```typescript withParams(params: Record): ModificationDescription ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `params` | `Record` | Key-value pairs for template interpolation | #### Returns `ModificationDescription` - Returns this instance for method chaining #### Description This method allows you to provide dynamic values that will be interpolated into the description text. Parameters are replaced in the description string using `{paramName}` syntax. #### Example ```typescript const description = new ModificationDescription('Changed color from {oldColor} to {newColor}') .withParams({ oldColor: '#000000', newColor: '#FF0000' }); // Results in: "Changed color from #000000 to #FF0000" ``` *** ### getValue() Returns the description data structure. ```typescript getValue(): {key: string; params: Record} ``` #### Returns An object containing: * `key`: The description text or i18n key * `params`: The parameters object (may be undefined if not set) #### Example ```typescript const description = new ModificationDescription('Button updated') .withParams({ type: 'primary' }); console.log(description.getValue()); // Output: { key: 'Button updated', params: { type: 'primary' } } ``` --- --- url: https://plugin.stripo.email/extensions/reference/nodes/BaseImmutableNode.md --- # BaseImmutableNode Foundational interface for all immutable nodes in the Stripo Email Editor Extensions SDK. ```typescript interface BaseImmutableNode ``` ## Description `BaseImmutableNode` is the foundational interface that provides common querying and traversal methods for both HTML and CSS nodes. It enables navigation through the document tree, searching for elements, and accessing node relationships. ## Import ```typescript import { BaseImmutableNode } from '@stripoinc/ui-editor-extensions'; ``` ## Type Parameters | Parameter | Constraint | Description | |-----------|------------|-------------| | `T` | `ImmutableHtmlNode \| ImmutableCssNode` | The specific node type (HTML or CSS) | ## Methods ### Query Methods #### querySelector() Finds a single matching child element within the node's subtree. ```typescript querySelector(selector: string): T | undefined ``` | Parameter | Type | Description | |-----------|------|-------------| | `selector` | `string` | CSS selector for HTML nodes, special syntax for CSS nodes | **Returns:** `T | undefined` - First matching node, or `undefined` if no match **HTML Selector Syntax:** * Standard CSS selectors: `.class`, `#id`, `tag`, `[attribute]` **CSS Node Selector Syntax:** * `@{max-width:100px}` - Media query, exact match * `@*{max-width}` - Media query, partial match * `#test.class-2` - Selector, exact match * `*#test` - Selector, partial match * `{display}` - Attribute, exact match * `&{comment text}` - Comment, exact match * `&*{comment}` - Comment, partial match **Example:** ```typescript // HTML node const button = htmlNode.querySelector('.btn-primary'); // CSS node const mediaQuery = cssNode.querySelector('@{media only screen and (max-width: 600px)}'); ``` *** #### querySelectorAll() Returns all matching elements within the current node's subtree. ```typescript querySelectorAll(selector: string): T[] ``` | Parameter | Type | Description | |-----------|------|-------------| | `selector` | `string` | CSS selector for HTML nodes, special syntax for CSS nodes | **Returns:** `T[]` - Array of matching nodes, empty array if no matches **Example:** ```typescript // Find all buttons const buttons = htmlNode.querySelectorAll('button'); // Find all CSS rules with display property const displayRules = cssNode.querySelectorAll('{display}'); ``` *** #### closest() Finds the closest ancestor matching the selector. ```typescript closest(selector: string): T | undefined ``` | Parameter | Type | Description | |-----------|------|-------------| | `selector` | `string` | Selector to match parent nodes | **Returns:** `T | undefined` - Closest matching parent, or `undefined` if not found **Example:** ```typescript // Find closest container const container = element.closest('.container'); // Find parent media query const mediaParent = cssRule.closest('@*{max-width}'); ``` *** ### Navigation Methods #### parent() Retrieves the parent node. ```typescript parent(): T | undefined ``` **Returns:** `T | undefined` - Parent node, or `undefined` if no parent **Example:** ```typescript const parentElement = node.parent(); if (parentElement) { console.log('Parent found'); } ``` *** #### children() Retrieves direct child element nodes (excludes text nodes). ```typescript children(): T[] ``` **Returns:** `T[]` - Array of child element nodes **Example:** ```typescript const childElements = container.children(); console.log(`Container has ${childElements.length} child elements`); ``` *** #### childNodes() Retrieves all child nodes including text nodes. ```typescript childNodes(): T[] ``` **Returns:** `T[]` - Array of all child nodes **Example:** ```typescript const allChildren = element.childNodes(); // Includes both element and text nodes ``` *** #### siblings() Retrieves sibling nodes of the current node. ```typescript siblings(): T[] ``` **Returns:** `T[]` - Array of sibling nodes (excludes current node) **Example:** ```typescript const siblings = node.siblings(); siblings.forEach(sibling => { // Process each sibling }); ``` *** #### nextSibling() Retrieves the next sibling node. ```typescript nextSibling(): T | undefined ``` **Returns:** `T | undefined` - Next sibling, or `undefined` if none **Example:** ```typescript let current = firstNode; while (current) { // Process node current = current.nextSibling(); } ``` *** #### nextElementSibling() Retrieves the next sibling that is an element node. ```typescript nextElementSibling(): T | undefined ``` **Returns:** `T | undefined` - Next element sibling, or `undefined` if none **Example:** ```typescript const nextElement = node.nextElementSibling(); // Skips text nodes ``` *** #### previousSibling() Retrieves the previous sibling node. ```typescript previousSibling(): T | undefined ``` **Returns:** `T | undefined` - Previous sibling, or `undefined` if none *** #### previousElementSibling() Retrieves the previous sibling that is an element node. ```typescript previousElementSibling(): T | undefined ``` **Returns:** `T | undefined` - Previous element sibling, or `undefined` if none *** ### Node Information Methods #### getType() Retrieves the type of the node. ```typescript getType(): string | undefined ``` **Returns:** `string | undefined` - Node type **HTML Node Types:** * `'element'` - HTML element * `'text'` - Text node * `'comment'` - Comment node * `'document'` - Document node * `'doctype'` - DOCTYPE declaration * `'documentFragment'` - Document fragment **CSS Node Types:** * `'attr'` - CSS attribute/property * `'comment'` - CSS comment * `'rule'` - CSS rule * `'media'` - Media query * `'document'` - CSS document **Example:** ```typescript if (node.getType() === 'element') { // Handle element node } ``` *** #### getNodeConfig() Retrieves the configuration object of the node. ```typescript getNodeConfig(): Record ``` **Returns:** `Record` - Node configuration object **Example:** ```typescript const config = node.getNodeConfig(); if (config.customProperty) { // Use custom configuration } ``` *** #### getClosestModuleId() Retrieves the ID of the closest module associated with the current node. ```typescript getClosestModuleId(): number | undefined ``` **Returns:** `number | undefined` - Module ID, or `undefined` if not within a module **Example:** ```typescript const moduleId = node.getClosestModuleId(); if (moduleId) { console.log(`Node is within module ${moduleId}`); } ``` *** #### getClosestModuleElement() Retrieves the closest module element associated with the current node. ```typescript getClosestModuleElement(): T | undefined ``` **Returns:** `T | undefined` - The immutable node representing the module element, or `undefined` if not within a module **Example:** ```typescript const moduleElement = node.getClosestModuleElement(); if (moduleElement) { console.log('Module element:', moduleElement); } ``` *** #### getModuleElementsById() Returns module elements by module ID. ```typescript getModuleElementsById(id: number): T[] ``` **Returns:** `T[]` - Array of module elements matching the ID **Example:** ```typescript const modules = node.getModuleElementsById(123); ``` *** #### getModuleElements() Returns all module elements within the document. ```typescript getModuleElements(): T[] ``` **Returns:** `T[]` - Array of all module elements **Example:** ```typescript const allModules = node.getModuleElements(); ``` ## Best Practices 1. **Check for undefined**: Many methods return `undefined` when no match is found 2. **Use appropriate selectors**: HTML and CSS nodes have different selector syntaxes 3. **Type checking**: Use `getType()` to determine node type before operations 4. **Efficient traversal**: Use specific methods like `children()` vs `childNodes()` based on needs 5. **Cache results**: Store frequently accessed nodes to avoid repeated queries --- --- url: >- https://plugin.stripo.email/extensions/reference/nodes/BaseImmutableHtmlNode.md --- # BaseImmutableHtmlNode Base interface for HTML-specific node operations. ```typescript interface BaseImmutableHtmlNode extends BaseImmutableNode ``` ## Description `BaseImmutableHtmlNode` extends the base immutable node interface with HTML-specific type casting methods. It provides the foundation for all HTML nodes (both element and text nodes) and enables safe type conversion between different HTML node types. ## Import ```typescript import { BaseImmutableHtmlNode } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Extends: [`BaseImmutableNode`](./BaseImmutableNode.md) ## Methods ### asText() Casts the current node to an `ImmutableHtmlTextNode`. ```typescript asText(): ImmutableHtmlTextNode ``` **Returns:** [`ImmutableHtmlTextNode`](./ImmutableHtmlTextNode.md) - The current node as a text node **Description:** This method performs a type cast to interpret the current node as a text node. Use this when you know the node is a text node and need to access text-specific methods. **Example:** ```typescript const node: BaseImmutableHtmlNode = getNode(); if (node.getType() === 'text') { const textNode = node.asText(); const content = textNode.getTextContent(); } ``` *** ### asElement() Casts the current node to an `ImmutableHtmlElementNode`. ```typescript asElement(): ImmutableHtmlElementNode ``` **Returns:** [`ImmutableHtmlElementNode`](./ImmutableHtmlElementNode.md) - The current node as an element node **Description:** This method performs a type cast to interpret the current node as an element node. Use this when you know the node is an element and need to access element-specific methods. **Example:** ```typescript const node: BaseImmutableHtmlNode = getNode(); if (node.getType() === 'element') { const element = node.asElement(); const tagName = element.getTagName(); const classes = element.getClassList(); } ``` ## Type Casting Pattern ### Safe Type Casting Always check the node type before casting: ```typescript function processHtmlNode(node: BaseImmutableHtmlNode) { const nodeType = node.getType(); switch(nodeType) { case 'element': const element = node.asElement(); // Access element-specific methods console.log(`Tag: ${element.getTagName()}`); break; case 'text': const textNode = node.asText(); // Access text-specific methods console.log(`Text: ${textNode.getTextContent()}`); break; default: console.log(`Unknown node type: ${nodeType}`); } } ``` --- --- url: https://plugin.stripo.email/extensions/reference/nodes/BaseImmutableCssNode.md --- # BaseImmutableCssNode Base interface for CSS-specific node operations. ```typescript interface BaseImmutableCssNode extends BaseImmutableNode ``` ## Description `BaseImmutableCssNode` extends the base immutable node interface with CSS-specific type casting methods and properties. It provides the foundation for all CSS nodes (rules, attributes, comments, and documents) and enables safe type conversion between different CSS node types. ## Import ```typescript import { BaseImmutableCssNode } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Extends: [`BaseImmutableNode`](./BaseImmutableNode.md) ## Methods ### isCommented() Checks if the CSS node is commented out. ```typescript isCommented(): boolean ``` **Returns:** `boolean` - True if the node is commented, false otherwise **Throws:** `Error` if the node cannot be found **Description:** Determines if the current CSS node has been commented out. This is useful for identifying disabled styles or temporarily excluded rules. **Example:** ```typescript if (cssNode.isCommented()) { console.log('This CSS node is commented out'); } ``` *** ### asDocument() Casts the current node to an `ImmutableCssDocumentNode`. ```typescript asDocument(): ImmutableCssDocumentNode ``` **Returns:** [`ImmutableCssDocumentNode`](./ImmutableCssDocumentNode.md) - The current node as a document node **Description:** Type casts the node as a CSS document node. Use this for root stylesheets or media query blocks. **Example:** ```typescript if (node.getType() === 'document' || node.getType() === 'media') { const doc = node.asDocument(); // Process document or media query } ``` *** ### asRule() Casts the current node to an `ImmutableCssRuleNode`. ```typescript asRule(): ImmutableCssRuleNode ``` **Returns:** [`ImmutableCssRuleNode`](./ImmutableCssRuleNode.md) - The current node as a rule node **Description:** Type casts the node as a CSS rule node. Use this when working with CSS rules that have selectors. **Example:** ```typescript if (node.getType() === 'rule') { const rule = node.asRule(); const selector = rule.getSelector(); } ``` *** ### asAttribute() Casts the current node to an `ImmutableCssAttributeNode`. ```typescript asAttribute(): ImmutableCssAttributeNode ``` **Returns:** [`ImmutableCssAttributeNode`](./ImmutableCssAttributeNode.md) - The current node as an attribute node **Description:** Type casts the node as a CSS attribute (property) node. Use this for accessing CSS property names and values. **Example:** ```typescript if (node.getType() === 'attr') { const attr = node.asAttribute(); const name = attr.getAttributeName(); const value = attr.getAttributeValue(); } ``` *** ### asComment() Casts the current node to an `ImmutableCssCommentNode`. ```typescript asComment(): ImmutableCssCommentNode ``` **Returns:** [`ImmutableCssCommentNode`](./ImmutableCssCommentNode.md) - The current node as a comment node **Description:** Type casts the node as a CSS comment node. Use this for accessing comment text content. **Example:** ```typescript if (node.getType() === 'comment') { const comment = node.asComment(); const text = comment.getTextContent(); } ``` ## Type Casting Pattern ### Safe Type Casting Always check the node type before casting: ```typescript function processCssNode(node: BaseImmutableCssNode) { const nodeType = node.getType(); switch(nodeType) { case 'rule': const rule = node.asRule(); console.log(`Rule: ${rule.getSelector()}`); break; case 'attr': const attr = node.asAttribute(); console.log(`Property: ${attr.getAttributeName()}: ${attr.getAttributeValue()}`); break; case 'comment': const comment = node.asComment(); console.log(`Comment: ${comment.getTextContent()}`); break; case 'document': case 'media': const doc = node.asDocument(); console.log('Document or media query node'); break; default: console.log(`Unknown CSS node type: ${nodeType}`); } } ``` --- --- url: https://plugin.stripo.email/extensions/reference/nodes/ImmutableHtmlNode.md --- # ImmutableHtmlNode Union type representing immutable HTML nodes in email templates. ```typescript type ImmutableHtmlNode = ImmutableHtmlElementNode | ImmutableHtmlTextNode; ``` ## Description `ImmutableHtmlNode` is a union type that represents any HTML node in the template structure. It can be either an element node (tags like `
`, `

`, etc.) or a text node (text content within elements). This type is used throughout the API for HTML manipulation and querying. ## Import ```typescript import { ImmutableHtmlNode } from '@stripoinc/ui-editor-extensions'; ``` ## Type Members | Type | Description | |------|-------------| | [`ImmutableHtmlElementNode`](./ImmutableHtmlElementNode.md) | Represents HTML element nodes with tags, attributes, and styles | | [`ImmutableHtmlTextNode`](./ImmutableHtmlTextNode.md) | Represents text content within HTML elements | ## Base Inheritance All HTML nodes extend from: * [`BaseImmutableNode`](./BaseImmutableNode.md) - Common node operations * [`BaseImmutableHtmlNode`](./BaseImmutableHtmlNode.md) - HTML-specific base operations ## Type Guards and Casting ### Determining Node Type ```typescript function processNode(node: ImmutableHtmlNode) { const type = node.getType(); if (type === 'element') { // Node is ImmutableHtmlElementNode const element = node.asElement(); const tagName = element.getTagName(); console.log(`Element: ${tagName}`); } else if (type === 'text') { // Node is ImmutableHtmlTextNode const textNode = node.asText(); const content = textNode.getTextContent(); console.log(`Text: ${content}`); } } ``` ### Type Casting Methods ```typescript // Cast to element node const element: ImmutableHtmlElementNode = node.asElement(); // Cast to text node const textNode: ImmutableHtmlTextNode = node.asText(); ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/nodes/ImmutableHtmlElementNode.md --- # ImmutableHtmlElementNode Interface representing immutable HTML element nodes in email templates. ```typescript interface ImmutableHtmlElementNode extends ImmutableHtmlTextNode ``` ## Description `ImmutableHtmlElementNode` represents HTML elements (tags) in the template structure. It provides methods to access element properties, attributes, styles, classes, and content. This interface is essential for inspecting and analyzing HTML elements before making modifications. ## Import ```typescript import { ImmutableHtmlElementNode } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Extends: [`ImmutableHtmlTextNode`](./ImmutableHtmlTextNode.md) ## Methods ### Attribute Methods #### getAttribute() Retrieves the value of a specified attribute. ```typescript getAttribute(name: string): string | null ``` | Parameter | Type | Description | |-----------|------|-------------| | `name` | `string` | The attribute name (case-sensitive) | **Returns:** `string | null` - Attribute value or null if not found **Example:** ```typescript const href = element.getAttribute('href'); const target = element.getAttribute('target'); const dataId = element.getAttribute('data-id'); ``` *** ### Class Methods #### hasClass() Checks if the element has a specific CSS class. ```typescript hasClass(className: string): boolean ``` | Parameter | Type | Description | |-----------|------|-------------| | `className` | `string` | The CSS class name to check | **Returns:** `boolean` - True if class exists, false otherwise **Example:** ```typescript if (element.hasClass('active')) { console.log('Element is active'); } ``` *** #### getClassList() Retrieves all CSS classes applied to the element. ```typescript getClassList(): string[] ``` **Returns:** `string[]` - Array of class names **Example:** ```typescript const classes = element.getClassList(); // ['btn', 'btn-primary', 'large'] ``` *** ### Style Methods #### getStyle() Retrieves the value of a specific inline style property. ```typescript getStyle(name: string): string | undefined ``` | Parameter | Type | Description | |-----------|------|-------------| | `name` | `string` | The style property name (case-sensitive) | **Returns:** `string | undefined` - Style value or undefined if not set **Example:** ```typescript const color = element.getStyle('color'); const backgroundColor = element.getStyle('background-color'); ``` *** #### getComputedStyle() Retrieves the computed style value of a CSS property. ```typescript getComputedStyle(name: string): string | undefined ``` | Parameter | Type | Description | |-----------|------|-------------| | `name` | `string` | The CSS property name (case-sensitive) | **Returns:** `string | undefined` - Computed style value or undefined **Note:** Computed styles include inherited and stylesheet-defined styles. **Example:** ```typescript const computedColor = element.getComputedStyle('color'); const computedFontSize = element.getComputedStyle('font-size'); ``` *** ### Element Information Methods #### getTagName() Retrieves the tag name of the element. ```typescript getTagName(): string ``` **Returns:** `string` - Lowercase tag name **Example:** ```typescript const tagName = element.getTagName(); // 'div', 'p', 'table', etc. ``` *** #### getValue() Retrieves the value attribute of the element. ```typescript getValue(): string | undefined ``` **Returns:** `string | undefined` - Value attribute content, or `undefined` if the node or the value attribute is not found **Example:** ```typescript const inputValue = inputElement.getValue(); ``` *** ### Content Methods #### getInnerHTML() Retrieves the inner HTML content. ```typescript getInnerHTML(): string ``` **Returns:** `string` - Inner HTML as string **Example:** ```typescript const innerHTML = element.getInnerHTML(); // '

Hello World

' ``` *** #### getOuterHTML() Retrieves the outer HTML including the element itself. ```typescript getOuterHTML(): string ``` **Returns:** `string` - Outer HTML as string **Example:** ```typescript const outerHTML = element.getOuterHTML(); // '

Content

' ``` *** #### getInnerText() Retrieves the text content of the element and its descendants. ```typescript getInnerText(): string ``` **Returns:** `string` - Concatenated text content **Example:** ```typescript const text = element.getInnerText(); // All text content without HTML tags ``` *** ### Layout Methods #### getBoundingClientRect() Retrieves the bounding rectangle of the element. ```typescript getBoundingClientRect(): DOMRect | undefined ``` **Returns:** `DOMRect | undefined` - Element's bounding rectangle, or `undefined` if the node or its DOM element is not found **DOMRect Properties:** * `x`, `y`: Top-left coordinates * `width`, `height`: Dimensions * `top`, `right`, `bottom`, `left`: Edge positions **Example:** ```typescript const rect = element.getBoundingClientRect(); console.log(`Width: ${rect.width}, Height: ${rect.height}`); ``` *** ### Advanced Methods #### getDisplayCondition() Retrieves the display condition configuration. ```typescript getDisplayCondition(): DisplayCondition ``` **Returns:** [`DisplayCondition`](../types/DisplayCondition.md) - Display condition object **Example:** ```typescript const condition = element.getDisplayCondition(); if (condition) { console.log(`Condition: ${condition.name}`); } ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/nodes/ImmutableHtmlTextNode.md --- # ImmutableHtmlTextNode Interface for immutable HTML text nodes in email templates. ```typescript interface ImmutableHtmlTextNode extends BaseImmutableHtmlNode ``` ## Description `ImmutableHtmlTextNode` represents text content within HTML elements. It provides methods to access and work with text nodes, which are the actual textual content displayed in email templates. Text nodes contain no markup, only plain text. ## Import ```typescript import { ImmutableHtmlTextNode } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Extends: [`BaseImmutableHtmlNode`](./BaseImmutableHtmlNode.md) ## Methods ### getTextContent() Retrieves the text content of the node. ```typescript getTextContent(): string | undefined ``` **Returns:** `string | undefined` - The text content, or `undefined` if the node has no content **Example:** ```typescript const text = textNode.getTextContent(); console.log(`Text: "${text}"`); ``` --- --- url: https://plugin.stripo.email/extensions/reference/nodes/ImmutableCssNode.md --- # ImmutableCssNode Union type representing immutable CSS nodes in email templates. ```typescript type ImmutableCssNode = ImmutableCssCommentNode | ImmutableCssAttributeNode | ImmutableCssRuleNode | ImmutableCssDocumentNode; ``` ## Description `ImmutableCssNode` is a union type that represents any CSS node in the stylesheet structure. It can be a comment, attribute (property), rule, or document node. This type is used throughout the API for CSS manipulation and querying. ## Import ```typescript import { ImmutableCssNode } from '@stripoinc/ui-editor-extensions'; ``` ## Type Members | Type | Description | |------|-------------| | [`ImmutableCssCommentNode`](./ImmutableCssCommentNode.md) | CSS comment nodes | | [`ImmutableCssAttributeNode`](./ImmutableCssAttributeNode.md) | CSS properties within rules | | [`ImmutableCssRuleNode`](./ImmutableCssRuleNode.md) | CSS rules with selectors | | [`ImmutableCssDocumentNode`](./ImmutableCssDocumentNode.md) | CSS document root or @media blocks | ## Base Inheritance All CSS nodes extend from: * [`BaseImmutableNode`](./BaseImmutableNode.md) - Common node operations * [`BaseImmutableCssNode`](./BaseImmutableCssNode.md) - CSS-specific base operations ## Type Guards and Casting ### Determining Node Type ```typescript function processCssNode(node: ImmutableCssNode) { const type = node.getType(); switch(type) { case 'rule': const rule = node.asRule(); console.log(`Rule: ${rule.getSelector()}`); break; case 'attr': const attr = node.asAttribute(); console.log(`Property: ${attr.getAttributeName()}: ${attr.getAttributeValue()}`); break; case 'comment': const comment = node.asComment(); console.log(`Comment: ${comment.getTextContent()}`); break; case 'document': case 'media': const doc = node.asDocument(); console.log('Document or media query node'); break; } } ``` --- --- url: https://plugin.stripo.email/extensions/reference/nodes/ImmutableCssRuleNode.md --- # ImmutableCssRuleNode Interface for immutable CSS rule nodes in stylesheets. ```typescript interface ImmutableCssRuleNode extends BaseImmutableCssNode ``` ## Description `ImmutableCssRuleNode` represents CSS rules with selectors in the stylesheet. It provides methods to access rule properties, selectors, and child nodes (properties and comments). This interface is essential for inspecting and analyzing CSS rules before modification. ## Import ```typescript import { ImmutableCssRuleNode } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Extends: [`BaseImmutableCssNode`](./BaseImmutableCssNode.md) ## Methods ### getSelector() Retrieves the selector string of the CSS rule. ```typescript getSelector(): string ``` **Returns:** `string` - The CSS selector **Throws:** `Error` if the node cannot be found **Example:** ```typescript const selector = rule.getSelector(); // ".button", "#header", "table.email-container", etc. ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/nodes/ImmutableCssAttributeNode.md --- # ImmutableCssAttributeNode Interface for immutable CSS attribute (property) nodes. ```typescript interface ImmutableCssAttributeNode extends BaseImmutableCssNode ``` ## Description `ImmutableCssAttributeNode` represents CSS properties within rules. Each attribute node contains a property name and value pair (e.g., `color: blue`). This interface provides methods to access both the property name and its value. ## Import ```typescript import { ImmutableCssAttributeNode } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Extends: [`BaseImmutableCssNode`](./BaseImmutableCssNode.md) ## Methods ### getAttributeName() Retrieves the name of the CSS property. ```typescript getAttributeName(): string ``` **Returns:** `string` - The CSS property name **Throws:** `Error` if the node cannot be found **Example:** ```typescript const propertyName = attribute.getAttributeName(); // "color", "font-size", "background-color", etc. ``` *** ### getAttributeValue() Retrieves the value of the CSS property. ```typescript getAttributeValue(): string ``` **Returns:** `string` - The CSS property value **Throws:** `Error` if the node cannot be found **Example:** ```typescript const propertyValue = attribute.getAttributeValue(); // "blue", "16px", "#ffffff", "1.5em", etc. ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/nodes/ImmutableCssCommentNode.md --- # ImmutableCssCommentNode Interface for immutable CSS comment nodes. ```typescript interface ImmutableCssCommentNode extends BaseImmutableCssNode ``` ## Description `ImmutableCssCommentNode` represents CSS comments in stylesheets. Comments can contain documentation, TODOs, temporary code, or metadata. This interface provides methods to access the comment text content. ## Import ```typescript import { ImmutableCssCommentNode } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Extends: [`BaseImmutableCssNode`](./BaseImmutableCssNode.md) ## Methods ### getTextContent() Retrieves the text content of the CSS comment. ```typescript getTextContent(): string | undefined ``` **Returns:** `string | undefined` - The comment text (without /\* \*/ delimiters), or `undefined` if the node has no content **Example:** ```typescript const commentText = comment.getTextContent(); // "TODO: Update these styles", "Legacy code - do not remove", etc. ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/nodes/ImmutableCssDocumentNode.md --- # ImmutableCssDocumentNode Interface for immutable CSS document and media query nodes. ```typescript interface ImmutableCssDocumentNode extends BaseImmutableCssNode ``` ## Description `ImmutableCssDocumentNode` represents CSS document nodes, which include the root stylesheet node and media query blocks. These nodes serve as containers for CSS rules and other nested structures. The interface extends the base CSS node without adding additional methods, as document nodes primarily act as structural containers. ## Import ```typescript import { ImmutableCssDocumentNode } from '@stripoinc/ui-editor-extensions'; ``` ## Inheritance Extends: [`BaseImmutableCssNode`](./BaseImmutableCssNode.md) ## Node Types Document nodes can represent: 1. **Root Document**: The top-level stylesheet container 2. **Media Queries**: `@media` rule containers 3. **Other At-Rules**: `@supports`, `@document` containers (limited email support) --- --- url: >- https://plugin.stripo.email/extensions/reference/settings-panel/SettingsPanelRegistry.md --- # SettingsPanelRegistry Registry for customizing block settings panels with controls and tabs. ```typescript class SettingsPanelRegistry ``` ## Description `SettingsPanelRegistry` allows you to customize the settings panels that appear when users select blocks in the email editor. You can organize controls into tabs, add custom controls to existing blocks, or completely restructure the settings interface for specific block types. This is essential for providing intuitive configuration options for your custom blocks and enhancing the editing experience for built-in blocks. The registry works with [SettingsPanelTab](./SettingsPanelTab.md) instances to define the structure and content of settings panels. ## Import ```typescript import { SettingsPanelRegistry } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### api Provides access to settings panel-specific editor functionalities. ```typescript public api!: SettingsPanelApi ``` #### Type [SettingsPanelApi](../api/SettingsPanelApi.md) #### Usage Notes * Automatically injected by the editor * Provides methods for panel manipulation * Available after registry initialization ## Abstract Methods ### registerBlockControls() Registers custom control configurations for specific block types. ```typescript public registerBlockControls( blockControlsMap: Record ): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | blockControlsMap | `Record` | Map of block IDs to their settings panel tabs | #### Usage Notes * Block IDs should match those from `Block.getId()` or built-in block types * Each block can have multiple tabs * Tabs are displayed in the order they appear in the array --- --- url: >- https://plugin.stripo.email/extensions/reference/settings-panel/SettingsPanelTab.md --- # SettingsPanelTab Configuration class for organizing controls within settings panel tabs. ```typescript class SettingsPanelTab ``` ## Description `SettingsPanelTab` represents a single tab within a block's settings panel. It defines which controls appear in the tab, their order, and the tab's label. Tabs help organize related controls together, making the settings interface more intuitive and manageable for users. This class provides a fluent API for configuring tabs, allowing you to dynamically add, remove, and reorder controls as needed. ## Import ```typescript import { SettingsPanelTab } from '@stripoinc/ui-editor-extensions'; ``` ## Constructor ```typescript constructor(tabId: string, controlsIds: string[]) ``` ### Parameters | Name | Type | Description | |------|------|-------------| | tabId | `string` | Identifier for the tab (e.g., 'settings', 'styles', 'data') | | controlsIds | `string[]` | Array of control IDs to display in this tab | ### Example ```typescript const settingsTab = new SettingsPanelTab(SettingsTab.SETTINGS, [ GeneralControls.TEXT_COLOR, 'my-padding-control' ]); ``` ## Methods ### getTabId() Returns the identifier of this tab. ```typescript public getTabId(): string ``` #### Returns `string` - The tab identifier *** ### getLabel() Returns the display label for this tab. ```typescript public getLabel(): string | undefined ``` #### Returns `string | undefined` - The tab label, or undefined if not set *** ### getControlsIds() Returns the array of control IDs in this tab. ```typescript public getControlsIds(): string[] ``` #### Returns `string[]` - Array of control identifiers *** ### getControls() :::::tip Version Availability This method is available starting from v3.8.0 ::::: Returns the normalized control configuration objects in this tab. ```typescript public getControls(): SettingsPanelTabControl[] ``` #### Returns [`SettingsPanelTabControl[]`](#settingspaneltabcontrolconfig) - Array of control configuration objects *** ### withLabel() Sets a custom display label for the tab. ```typescript public withLabel(label: string): SettingsPanelTab ``` #### Parameters | Name | Type | Description | |------|------|-------------| | label | `string` | The label to display on the tab | #### Returns `SettingsPanelTab` - The tab instance for method chaining #### Example ```typescript const tab = new SettingsPanelTab('settings', ['color', 'size']) .withLabel('Appearance'); ``` *** ### addControl() Adds a control to the tab at the specified position. ```typescript public addControl( control: string | SettingsPanelTabControlConfig, position: number ): SettingsPanelTab ``` #### Parameters | Name | Type | Description | |------|------|-------------| | control | `string \| SettingsPanelTabControlConfig` | Control ID or control configuration object | | position | `number` | Position index | #### Returns `SettingsPanelTab` - The tab instance for method chaining #### Position Behavior * `position < 0`: Adds control at the beginning * `position > array.length`: Adds control at the end * `0 <= position <= array.length`: Inserts at specified index #### Example ```typescript const tab = new SettingsPanelTab('settings', ['color']) .addControl('size', 0) // Add at beginning: ['size', 'color'] .addControl( { id: 'padding', class: 'custom-grid-cell', withFullHeight: true }, 1 ); ``` *** ### deleteControl() Removes a control from the tab. ```typescript public deleteControl(controlId: string): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | controlId | `string` | The ID of the control to remove | #### Usage Notes * Does nothing if the control ID is not found * Maintains the order of remaining controls #### Example ```typescript const tab = new SettingsPanelTab('settings', ['color', 'size', 'padding']); tab.deleteControl('size'); // Result: ['color', 'padding'] ``` ## SettingsPanelTabControlConfig :::::tip Version Availability This interface is available starting from v3.8.0 ::::: ```typescript interface SettingsPanelTabControlConfig { id: string; class?: string; withFullHeight?: boolean; } ``` ### Properties | Property | Type | Description | |----------|------|-------------| | `id` | `string` | Control identifier | | `class` | `string` | Optional CSS class applied by the editor layout | | `withFullHeight` | `boolean` | Expands the control to full available tab height when supported | --- --- url: https://plugin.stripo.email/extensions/reference/types/AIPopoverOptions.md --- # AIPopoverOptions Interface defining configuration options for AI-assisted popovers in the Stripo Email Editor Extensions SDK. ```typescript interface AIPopoverOptions ``` ## Description The `AIPopoverOptions` interface configures AI-powered content generation popovers that can be anchored to specific elements in the editor. These popovers provide context-aware AI assistance for generating email content, subject lines, preheaders, and other text elements. ## Import ```typescript import { AIPopoverOptions } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### targetElement HTML element that the popover anchors to. ```typescript targetElement: HTMLElement ``` #### Type `HTMLElement` #### Usage Notes * Required for computing popover placement * Should be a visible element in the DOM * Typically the element being edited or a related button * Popover positioning is calculated relative to this element #### Example ```typescript { targetElement: container.querySelector('#ai-button') } ``` *** ### preferredSides Ordered array of preferred placement sides for the popover. ```typescript preferredSides: PopoverSide[] ``` #### Type `PopoverSide[]` - Array of placement preferences #### PopoverSide Enum ```typescript enum PopoverSide { TOP = 'top', RIGHT = 'right', BOTTOM = 'bottom', LEFT = 'left' } ``` #### Usage Notes * Defines fallback positioning when space is constrained * First available position in the array is used * If no preferred sides fit, the popover auto-positions * Order matters - earlier positions have higher priority #### Example ```typescript { preferredSides: [PopoverSide.BOTTOM, PopoverSide.TOP, PopoverSide.RIGHT] } ``` *** ### type Specifies the AI intent or content type to generate. ```typescript type: ExtensionPopoverType ``` #### Type `ExtensionPopoverType` #### ExtensionPopoverType Enum ```typescript enum ExtensionPopoverType { AI_HIDDEN_PREHEADER = 'aiHiddenPreheader', AI_SUBJECT = 'aiSubject', AI_TEXT = 'aiText' } ``` #### Usage Notes * Determines the AI model's behavior and prompting * `AI_TEXT` - General text content generation * `AI_SUBJECT` - Email subject line generation * `AI_HIDDEN_PREHEADER` - Preheader text generation * Each type optimizes for different content characteristics #### Example ```typescript { type: ExtensionPopoverType.AI_TEXT } ``` *** ### value Initial text context that seeds the AI generation. ```typescript value: string ``` #### Type `string` #### Usage Notes * Provides context for more relevant AI suggestions * Can be existing content to improve or refine * Empty string for completely new content generation * AI uses this as a starting point or reference #### Example ```typescript { value: 'Welcome to our newsletter' } ``` *** ### onResult Callback function invoked when AI generation completes. ```typescript onResult: (response: string) => void ``` #### Type `(response: string) => void` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | response | `string` | The AI-generated text content | #### Usage Notes * Not called if user cancels the popover * Use this to update your block's content * Should handle the response asynchronously #### Example ```typescript { onResult: (generatedText: string) => { console.log('AI generated:', generatedText); // Update block content with generated text } } ``` --- --- url: https://plugin.stripo.email/extensions/reference/types/BlockHint.md --- # BlockHint Interface defining the structure of tooltip content displayed when hovering over block items in the Stripo Email Editor. ```typescript interface BlockHint { title: string; description: string; } ``` ## Description The `BlockHint` interface represents the tooltip or hint information displayed when users hover over block items in the blocks panel. It provides contextual information to help users understand what a block does before dragging it into their email template. ## Import ```typescript import { BlockHint } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### title The main heading text displayed in the hint tooltip. ```typescript title: string ``` #### Type `string` *** ### description Detailed explanation of the block's functionality and usage. ```typescript description: string ``` #### Type `string` --- --- url: https://plugin.stripo.email/extensions/reference/types/BlockItem.md --- # BlockItem Interface defining the structure of a draggable block element in the Stripo Email Editor blocks panel. ```typescript interface BlockItem { name: string; title: string; iconSrc: string; description: string; disabled?: boolean; } ``` ## Description The `BlockItem` interface represents a single draggable block element that appears in the Stripo Email Editor blocks panel. Each block item contains metadata about the block, including its visual representation, textual information, and interaction state. ## Import ```typescript import { BlockItem } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### name Unique internal identifier for the block type. ```typescript name: string ``` #### Type `string` *** ### title User-friendly display title shown in the blocks panel. ```typescript title: string ``` #### Type `string` *** ### iconSrc URL to the block's icon image or image content. ```typescript iconSrc: string ``` #### Type `string` ### description Detailed description of the block's purpose and functionality. ```typescript description: string ``` #### Type `string` *** ### disabled Controls whether the block can be dragged and used. ```typescript disabled: boolean ``` #### Type `boolean` --- --- url: https://plugin.stripo.email/extensions/reference/types/CustomFontFamily.md --- # CustomFontFamily Interface defining custom font family configuration for the Stripo Email Editor Extensions SDK. ```typescript interface CustomFontFamily ``` ## Description The `CustomFontFamily` interface specifies the structure for adding custom fonts to the Stripo Email Editor. It allows extensions to register brand-specific or custom fonts that become available throughout the editor, including in font selection dropdowns and for use in email templates. ## Import ```typescript import { CustomFontFamily } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### name Display name of the font family shown in the editor's font selector. ```typescript name: string ``` #### Type `string` #### Usage Notes * This is the human-readable name displayed to users * Should be descriptive and recognizable * Appears in all font family selection dropdowns * Can include spaces and special characters #### Example ```typescript { name: 'Proxima Nova' } ``` *** ### fontFamily CSS font-family declaration used in the document's HTML/CSS. ```typescript fontFamily: string ``` #### Type `string` #### Usage Notes * Must be a valid CSS font-family value * Can include fallback fonts * This value is directly inserted into CSS rules * Should follow standard CSS font-family syntax #### Example ```typescript { fontFamily: '"Proxima Nova", "Helvetica Neue", Helvetica, Arial, sans-serif' } ``` *** ### url URL pointing to the font file or CSS file containing @font-face declarations. ```typescript url: string ``` #### Type `string` #### Usage Notes * Can be an absolute URL to external font services * Can be a relative URL to self-hosted fonts * Typically points to a CSS file with @font-face rules * The CSS is automatically loaded when the font is registered * Supports Google Fonts, Adobe Fonts, or custom font hosting #### Example ```typescript { url: 'https://fonts.googleapis.com/css2?family=Proxima+Nova:wght@400;700' } ``` --- --- url: https://plugin.stripo.email/extensions/reference/types/DisplayCondition.md --- # DisplayCondition Configuration interface for conditional element visibility. ```typescript interface DisplayCondition ``` ## Description `DisplayCondition` defines the configuration for controlling when an HTML element should be displayed based on dynamic conditions. This is used for personalization, A/B testing, and responsive content delivery in email templates. ## Import ```typescript import { DisplayCondition } from '@stripoinc/ui-editor-extensions'; ``` ## Properties | Property | Type | Required | Description | |----------|------|----------|-------------| | `id` | `number` | Yes | Unique identifier for the condition | | `name` | `string` | Yes | Human-readable name for the condition | | `description` | `string` | Yes | Detailed description of what the condition does | | `beforeScript` | `string` | Yes | JavaScript code executed before condition evaluation | | `afterScript` | `string` | Yes | JavaScript code executed after condition evaluation | | `extraData` | `string` | No | Optional additional data in string format | | `conditionsCount` | `number` | No | Optional number of individual conditions represented by this display condition | --- --- url: https://plugin.stripo.email/extensions/reference/types/EmojiPopoverOptions.md --- # EmojiPopoverOptions Interface defining configuration options for emoji picker popovers in the Stripo Email Editor Extensions SDK. ```typescript interface EmojiPopoverOptions ``` ## Description The `EmojiPopoverOptions` interface configures emoji picker popovers that can be anchored to specific elements in the editor. It provides a user-friendly way to insert emojis into email content, supporting search functionality and emoji categories. ## Import ```typescript import { EmojiPopoverOptions } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### targetElement HTML element that the popover anchors to. ```typescript targetElement: HTMLElement ``` #### Type `HTMLElement` #### Usage Notes * Required for computing popover placement * Should be a visible element in the DOM * Typically a button or the text input where emoji will be inserted * Popover positioning is calculated relative to this element #### Example ```typescript { targetElement: container.querySelector('.emoji-button') } ``` *** ### preferredSides Ordered array of preferred placement sides for the popover. ```typescript preferredSides: PopoverSide[] ``` #### Type `PopoverSide[]` - Array of placement preferences #### PopoverSide Enum ```typescript enum PopoverSide { TOP = 'top', RIGHT = 'right', BOTTOM = 'bottom', LEFT = 'left' } ``` #### Usage Notes * Defines fallback positioning when space is constrained * First available position in the array is used * If no preferred sides fit, the popover auto-positions * Order matters - earlier positions have higher priority #### Example ```typescript { preferredSides: [PopoverSide.BOTTOM, PopoverSide.TOP] } ``` *** ### onResult Callback function invoked when an emoji is selected. ```typescript onResult: (response: string) => void ``` #### Type `(response: string) => void` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | response | `string` | The selected emoji character | #### Usage Notes * Called when user selects an emoji from the picker * Not called if user closes the popover without selection * The response is the actual emoji character (e.g., "😀") * Use this to insert the emoji into your content #### Example ```typescript { onResult: (emoji: string) => { console.log('Selected emoji:', emoji); // Insert emoji into content } } ``` --- --- url: https://plugin.stripo.email/extensions/reference/types/HideElementState.md --- # HideElementState Type alias for the editor's device-specific hidden-element state. ```typescript type HideElementState = 'desktop' | 'mobile' | undefined ``` ## Description `HideElementState` represents whether the editor hides a node on a specific device mode. It is used by [BlockApi](../api/BlockApi) to read the current state and by [HtmlNodeModifier](../modification/HtmlNodeModifier) to update it through template modifications. ## Import ```typescript import type { HideElementState } from '@stripoinc/ui-editor-extensions'; ``` ## Values | Value | Description | |-------|-------------| | `'desktop'` | The target node is hidden on desktop | | `'mobile'` | The target node is hidden on mobile | | `undefined` | No device-specific hidden state is applied | ## Example ```typescript const state: HideElementState = this.api.getHiddenElementState(node); this.api.getDocumentModifier() .modifyHtml(node) .setHiddenElementState(state === 'mobile' ? undefined : 'mobile') .apply(new ModificationDescription('Toggle mobile visibility')); ``` --- --- url: https://plugin.stripo.email/extensions/reference/types/StructureLayout.md --- # StructureLayout Type definition for container layouts in email structures. ```typescript type StructureLayout = CreateStructureEmptyContainer | CreateStructureContentContainer; ``` ## Description `StructureLayout` is a union type that represents different types of containers that can be used in email structure layouts. It allows for flexible container definitions including content containers, empty placeholders, and spacers. ## Import ```typescript import { StructureLayout } from '@stripoinc/ui-editor-extensions'; ``` ## Type Definition ### CreateStructureContentContainer A string value representing the width percentage of a content container: ```typescript type CreateStructureContentContainer = string; ``` **Examples:** `'50%'`, `'33%'`, `'100%'` ### CreateStructureEmptyContainer An object configuration for non-content containers: ```typescript interface CreateStructureEmptyContainer { width: string; contentType: 'EMPTY' | 'SPACER'; } ``` ## Container Types ### 1. Content Container **Type:** `string` Content containers hold actual email content such as text, images, buttons, and other elements. ```typescript const contentContainer: StructureLayout = '50%'; ``` **Characteristics:** * Defined as a simple string with percentage width * Receives HTML content during layout operations * Can be modified and updated with new content * Typically used for main content areas ### 2. Empty Container **Type:** `{ width: string, contentType: 'EMPTY' }` Empty containers are placeholders that reserve space but don't initially contain content. ```typescript const emptyContainer: StructureLayout = { width: '25%', contentType: 'EMPTY' }; ``` **Characteristics:** * Can be converted to content containers later * Useful for flexible layouts * Does not receive content during `updateLayoutWithContent()` ### 3. Spacer Container **Type:** `{ width: string, contentType: 'SPACER' }` Spacer containers create spacing and margins in layouts. ```typescript const spacerContainer: StructureLayout = { width: '10%', contentType: 'SPACER' }; ``` **Characteristics:** * Never receives content * Used for margins and gutters * Helps with responsive spacing * Maintains layout alignment --- --- url: https://plugin.stripo.email/extensions/reference/ui-elements/UIElement.md --- # UIElement Core class for creating custom UI elements in the Stripo Email Editor. ```typescript class UIElement ``` ## Description `UIElement` is the foundation for building custom user interface components that can be embedded within the Stripo Email Editor's controls and panels. These elements provide interactive functionality beyond standard HTML elements, such as custom inputs, color pickers, dropdowns, or any specialized UI component your extension requires. UI elements are rendered dynamically when needed and can maintain state, respond to attribute changes, and integrate seamlessly with the editor's control system. They're particularly useful when building complex controls that require custom interaction patterns not available in standard HTML form elements. ## Import ```typescript import { UIElement } from '@stripoinc/ui-editor-extensions'; ``` ## Properties ### api Provides access to editor functionalities specific to this UI element instance. ```typescript public api!: UIElementApi ``` #### Type [UIElementApi](../api/UIElementApi.md) #### Usage Notes * Automatically injected by the editor when the element is instantiated * Provides methods for dispatching events and accessing editor state * Available after the element is registered ## Methods ### getId() Returns the unique identifier for this UI element type. ```typescript getId(): string ``` #### Returns `string` - A unique identifier for the UI element #### Usage Notes * Must be unique across all registered UI elements * Used for registration and referencing within controls #### Example ```typescript public getId(): string { return 'brand-color-picker'; } ``` *** ### getTemplate() Returns the HTML template string that defines the structure of this UI element. ```typescript getTemplate(): string ``` #### Returns `string` - HTML template for the element #### Usage Notes * Defines the initial HTML structure * Can use custom attributes that will be managed by the element * Should be a valid HTML fragment #### Example ```typescript public getTemplate(): string { return `
`; } ``` *** ### onRender() Called when the UI element should render its content into the provided container. ```typescript abstract onRender(container: HTMLElement): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | container | `HTMLElement` | The DOM element where the UI element should be rendered | #### Usage Notes * This is where you set up event listeners and initialize the element * The container already contains the HTML from `getTemplate()` * Store references to DOM elements for later use #### Example ```typescript public onRender(container: HTMLElement): void { const button = container.querySelector('.picker-button'); const preview = container.querySelector('.color-preview'); button?.addEventListener('click', () => { this.openColorPicker(); }); // Initialize with current value if (this.currentColor) { preview.style.backgroundColor = this.currentColor; } } ``` ### onDestroy() Cleanup hook called when the UI element is being destroyed. ```typescript onDestroy(): void ``` #### Usage Notes * Remove event listeners added in `onRender()` * Clear timers or intervals * Clean up any external resources * Prevent memory leaks #### Example ```typescript public onDestroy(): void { // Remove event listeners if (this.clickHandler) { this.button?.removeEventListener('click', this.clickHandler); } // Clear any timers if (this.updateTimer) { clearInterval(this.updateTimer); } } ``` *** ### getValue() Returns the current value of the UI element. ```typescript getValue(): any ``` #### Returns `any` - The current value of the element #### Usage Notes * Implement if your element manages state or value * Called by the editor to retrieve the element's current value * Return type depends on your element's purpose #### Example ```typescript public getValue(): string { return this.selectedColor || '#000000'; } ``` *** ### setValue() Sets the value of the UI element. ```typescript setValue(value: any): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | value | `any` | The new value to set | #### Usage Notes * Implement if your element needs to be updated externally * Update the UI to reflect the new value * Validate the value if necessary #### Example ```typescript public setValue(value: string): void { if (this.isValidColor(value)) { this.selectedColor = value; this.updatePreview(value); } } ``` *** ### onAttributeUpdated() Called when one of the element's supported attributes gets updated externally. ```typescript onAttributeUpdated(name: string, value: unknown): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | name | `string` | The name of the attribute that was updated | | value | `unknown` | The new value of the attribute | #### Usage Notes * React to attribute changes like visibility or disabled state * Supported attributes are defined in [UEAttr](../constants/UEAttr.md) * Update the UI accordingly #### Example ```typescript export class CustomSlider extends UIElement { getId() { return 'custom-slider'; } onAttributeUpdated(name, value) { if (name === 'disabled' && value === true) { console.log('Slider is disabled'); } super.onAttributeUpdated(_name, _value); } //Additional configuration } class CustomControl extends UIElement { getTemplate() { return `
`; } onRender(container) { this.api.setUIEAttribute('custom-slider-name', 'disabled', true); } //Additional configuration } ``` --- --- url: >- https://plugin.stripo.email/extensions/reference/ui-elements/UIElementTagRegistry.md --- # UIElementTagRegistry Registry for mapping custom UI element tags to their implementations. ```typescript class UIElementTagRegistry ``` ## Description `UIElementTagRegistry` provides a mechanism to register custom HTML tags that will be replaced with your UI elements when controls are rendered. This allows you to use simple, declarative tags in your control templates that automatically get replaced with complex UI elements at runtime. For example, you can define a tag like `` in your control templates, and the registry will ensure it gets replaced with your custom color picker UI element when the control is rendered. ## Import ```typescript import { UIElementTagRegistry } from '@stripoinc/ui-editor-extensions'; ``` ## Methods ### registerUiElements() Registers mappings between custom HTML tags and UI element IDs. ```typescript public registerUiElements(uiElementsTagsMap: Record): void ``` #### Parameters | Name | Type | Description | |------|------|-------------| | uiElementsTagsMap | `Record` | Map of tag names to UI element IDs | #### Usage Notes * Tag names should be valid HTML custom element names (lowercase with hyphens) * UI element IDs must match those returned by `UIElement.getId()` * The map is provided by the editor based on available UI elements * Register only the tags you want to use in your templates --- --- url: https://plugin.stripo.email/extensions/changelog.md --- # Stripo Extensions SDK Change Log ## v3.10.0 ### Overview Version 3.10.0 is a minor release of the Stripo Extensions SDK that introduces programmatic access to user permissions, configurable custom font connection methods, new rich text and popup panel UI elements, and explicitly nullable node query return types. **Release Date:** 27 July 2026\ **Release Type:** Minor Release\ **Version:** 3.10.0\ **Previous Version:** 3.9.0 :::::tip Editor Compatibility The npm package version 3.10.0 is fully compatible with the Stripo Editor starting from version **2.71.0 and higher**. ::::: *** ### What's New #### New: User Permissions API [BaseApi](./reference/api/BaseApi) now exposes the editor's user permissions through `getUserPermissions()` and the `onUserPermissionsUpdated()` subscription. Use the new [EditorPermissions](./reference/types/EditorPermissions) type to adapt extension UI to the current user's access level. See [Permissions and Access Management](/editor-configuration/permissions-and-access-management) for how permissions are configured on your backend. ```typescript const permissions = this.api.getUserPermissions(); if (!permissions.content?.write) { this.disableEditingControls(); } this.api.onUserPermissionsUpdated((newPermissions, oldPermissions) => { this.refreshControlsState(newPermissions); }); ``` #### Changed: Nullable Node Query Return Types Several node query methods now explicitly return `undefined` instead of throwing an error when the underlying node or value is missing: * `ImmutableHtmlTextNode.getTextContent(): string | undefined` * `ImmutableHtmlElementNode.getValue(): string | undefined` * `ImmutableHtmlElementNode.getBoundingClientRect(): DOMRect | undefined` * `ImmutableHtmlElementNode.getDisplayCondition(): DisplayCondition | undefined` * `ImmutableCssCommentNode.getTextContent(): string | undefined` *** ### Migration Guide #### From v3.9.0 to v3.10.0 No breaking changes are required for existing extensions at runtime. **Nullable Return Types**: If your extension is compiled with strict TypeScript settings, add `undefined` handling for the results of `getTextContent()`, `getValue()`, `getBoundingClientRect()`, and `getDisplayCondition()`. *** ## v3.9.0 ### Overview Version 3.9.0 is a minor release of the Stripo Extensions SDK that introduces programmatic access to the editor’s hidden-element state and template theme mode state, along with condition counts for display conditions. **Release Date:** 29 April 2026\ **Release Type:** Minor Release\ **Version:** 3.9.0\ **Previous Version:** 3.8.0 :::::tip Editor Compatibility The npm package version 3.9.0 is fully compatible with the Stripo Editor starting from version **2.63.0 and higher**. ::::: *** ### What's New #### New: Hidden Element State API [BlockApi](./reference/api/BlockApi) now includes `getHiddenElementState(target)` for reading the editor's canonical hide-on-device state for a node. ```typescript public onTemplateNodeUpdated(node: ImmutableHtmlNode): void { const hiddenState = this.api.getHiddenElementState(node); if (hiddenState === 'mobile') { this.api.sendEvent('extensions.block.hidden-on-mobile', { blockId: this.getId(), }); } } ``` #### New: `setHiddenElementState()` for HTML Node Modifications [HtmlNodeModifier](./reference/modification/HtmlNodeModifier) now supports `setHiddenElementState(state)`. Use this to mark the canonical target node (BLOCK, CONTAINER, STRUCTURE, STRIPE) as hidden on desktop or mobile, or pass `undefined` to clear the hidden state. ```typescript this.api.getDocumentModifier() .modifyHtml(this.currentNode) .setHiddenElementState('mobile') .apply(new ModificationDescription('Hide block on mobile')); ``` #### New: Exported `HideElementState` Type The SDK now exports [HideElementState](./reference/types/HideElementState), a union type for hidden-element state values: ```typescript type HideElementState = 'desktop' | 'mobile' | undefined; ``` #### New: Template Theme Mode State The SDK now exposes the template's active theme through [ThemeMode](./reference/constants/ThemeMode). Use [BaseApi](./reference/api/BaseApi) to read `getEditorState().themeMode`, or subscribe to `EditorStatePropertyType.themeMode` with `onEditorStatePropUpdated()` to react to `ThemeMode.LIGHT` and `ThemeMode.DARK`. #### Enhanced: Display Condition Metadata [DisplayCondition](./reference/types/DisplayCondition) now includes optional `conditionsCount` metadata. External display condition integrations can use this value to preserve or display how many individual conditions are represented by a saved condition object. *** ### Migration Guide #### From v3.8.0 to v3.9.0 No breaking changes are required for existing extensions. Use `getHiddenElementState()` and `setHiddenElementState()` only when you need to synchronize custom controls with the editor's device-specific visibility state. For theme-aware extension UI, read `themeMode` from `getEditorState()` or subscribe to `EditorStatePropertyType.themeMode`. *** ## v3.8.0 ### Overview Version 3.8.0 is a minor release of the Stripo Extensions SDK that adds editor event dispatching and theme-aware editor state, introduces richer settings panel tab configuration, expands block support for AMP interaction, and adds new UI building blocks for image alignment and AMP-related controls. **Release Date:** 8 April 2026\ **Release Type:** Minor Release\ **Version:** 3.8.0\ **Previous Version:** 3.7.0 :::::tip Editor Compatibility The npm package version 3.8.0 is fully compatible with the Stripo Editor starting from version **2.61.0 and higher**. ::::: *** ### What's New #### New: BaseApi `sendEvent()` for Editor Integrations All components inheriting from [BaseApi](./reference/api/BaseApi) can now emit fire-and-forget editor events using `sendEvent(type, params)`. Calling `sendEvent()` triggers the editor initialization callback `onEvent(type, params)`, so your host application can react to extension-originated events. See [Initialization Settings](https://plugin.stripo.email/editor-configuration/initialization-settings) for details. ```typescript this.api.sendEvent('extensions.coupon.opened', { blockId: this.getId(), source: 'settings-panel', }); ``` #### Enhanced: SettingsPanelTab Control Configuration [SettingsPanelTab](./reference/settings-panel/SettingsPanelTab) now supports richer control metadata via `getControls()` and config-based `addControl()` calls. This lets you attach layout-related options such as custom CSS classes and `withFullHeight`. ```typescript const tab = new SettingsPanelTab('settings', ['title-control']) .addControl( { id: 'advanced-control', class: 'custom-grid-cell', withFullHeight: true, }, 1, ); ``` #### New: AMP-Aware Block Interaction Hook The [Block](./reference/blocks/Block) class adds `allowInteractWithAMPWhenSelected()` so a block can explicitly control whether AMP content remains interactive while the block is selected. #### New: Additional UI Elements and Attributes [UIElementType](./reference/constants/UIElementType) now includes: * `AMP_FORM_SERVICE_PICKER` * `MULTIPLE_SELECT` * `SCROLLABLE` [UEAttr](./reference/constants/UEAttr) also adds attribute mappings for `AMP_FORM_SERVICE_PICKER` and `MULTIPLE_SELECT`, including `placeholder` support for multiple select elements. #### New: Image Alignment Built-In Control `ImageAlignmentBuiltInControl` is now exported for image block integrations that need to reuse the built-in alignment control flow. *** ### Migration Guide #### From v3.7.0 to v3.8.0 **Settings Panel Tabs**: If you need layout metadata for tab controls, switch from string-only `addControl()` calls to the new object form with `id`, `class`, and `withFullHeight`. *** ## v3.7.0 ### Overview Version 3.7.0 is a minor release of the Stripo Extensions SDK that adds Modules Panel Tabs, introduces draggable block UI elements, expands block composition and block panel hooks, and refines lifecycle cleanup support. **Release Date:** 9 February 2026\ **Release Type:** Minor Release\ **Version:** 3.7.0\ **Previous Version:** 3.6.0 ::::tip Editor Compatibility The npm package version 3.7.0 is fully compatible with the Stripo Editor starting from version **2.56.0 and higher**. :::: *** ### What's New #### New: Modules Panel Tabs A new [ModulesPanelTab](./reference/controls/ModulesPanelTab.md) class and [ModulesPanelTabApi](./reference/api/ModulesPanelTabApi.md) allow you to add custom tabs to the Modules panel, with full access to UI element APIs and lifecycle hooks. ```typescript import { ExtensionBuilder, ModulesPanelTab } from '@stripoinc/ui-editor-extensions'; class MyModulesTab extends ModulesPanelTab { getId(): string { return 'my-modules-tab'; } getIcon(): string { return 'my-icon'; } getName(): string { return 'My Modules'; } getTabIndex(): number { return 2; } getTemplate(): string { return '
My modules tab
'; } } const extension = new ExtensionBuilder() .addModulesPanelTab(MyModulesTab) .build(); ``` #### New: Block Hooks for Panel Visibility and Template Styles The `Block` class now supports: * `shouldDisplayInBlocksPanel()` to hide a block from the blocks panel while keeping it available elsewhere. * `getTemplateStyles()` to provide block-specific CSS that is injected when the block is used. #### New: Draggable Block UI Elements [UIElementType.DRAGGABLE\_BLOCK](./components/ui-element#draggable-block-ui-element) has been added with `block-id` support in `UIElementsAttributes`, enabling UI elements that bind to a specific block ID for drag-and-drop scenarios. #### New: Stripe Block Composition Type `BlockCompositionType.STRIPE` has been added to support stripe-level composition patterns. #### Enhanced: Lifecycle Cleanup Hook `BaseValidatedClass` introduces a `destroy()` method so extensions can clean up resources when the editor is reinitialized or the extension is uninstalled. *** ### Breaking Changes #### 1. Text Link Color Built-In Control Removed The `TextLinkColorBuiltInControl` and `BuiltInControlTypes.TextControls.LINKS_COLOR` have been removed, along with the `text-link-color` control implementation. *** ### Migration Guide #### From v3.6.0 to v3.7.0 **Use Text Color Built-In Control**: Replace `TextLinkColorBuiltInControl` with `TextColorBuiltInControl` and update references from `BuiltInControlTypes.TextControls.LINKS_COLOR` to `BuiltInControlTypes.TextControls.TEXT_COLOR`. *** ## v3.6.0 ### Overview Version 3.6.0 is a minor release of the Stripo Extensions SDK that expands editor APIs for display conditions and tab titles, adds module lookup helpers, and refines node query return types. **Release Date:** 15 January 2026\ **Release Type:** Minor Release\ **Version:** 3.6.0\ **Previous Version:** 3.5.0 ::::tip Editor Compatibility The npm package version 3.6.0 is fully compatible with the Stripo Editor starting from version **2.53.0 and higher**. :::: *** ### What's New #### New: External Display Conditions API Access The `ExternalDisplayConditionsLibrary` now exposes an `api` property of type `ExternalDisplayConditionsApi` for editor integrations. #### New: Settings Panel Tab Title HTML The `ControlApi` now includes `setSettingsPanelTabTitleHtml()` to update a settings panel tab title with custom HTML. ```typescript this.api.setSettingsPanelTabTitleHtml('my-tab', ' My Tab'); ``` #### New: Blocks Panel Placement Toggle The `BlocksPanel` class adds `isPanelPlacementChangeEnabled()` to control whether the modules panel shows the drag handle for reordering. ```typescript class MyBlocksPanel extends BlocksPanel { isPanelPlacementChangeEnabled(): boolean { return false; } } ``` #### New: Module Lookup Helpers `ImmutableHtmlNode` now supports module discovery helpers for finding module elements in the document tree: ```typescript const modules = node.getModuleElements(); const moduleById = node.getModuleElementsById(123); ``` *** ### Breaking Changes #### 1. Typo Fix: `getClosetModuleElement()` Renamed The method `getClosetModuleElement()` has been renamed to `getClosestModuleElement()`. *** ### Migration Guide #### From v3.5.0 to v3.6.0 **Rename Method**: Replace `getClosetModuleElement()` with `getClosestModuleElement()`. *** ## v3.5.0 ### Overview Version 3.5.0 is a minor release of the Stripo Extensions SDK that introduces the General Panel Tab for custom editor extensions, support for repeatable UI elements, and architectural improvements. **Release Date:** 19 December 2025\ **Release Type:** Minor Release\ **Version:** 3.5.0\ **Previous Version:** 3.4.0 :::tip Editor Compatibility The npm package version 3.5.0 is fully compatible with the Stripo Editor starting from version **2.49.1 and higher**. ::: *** ### What's New #### New: General Panel Tab A new `GeneralPanelTab` class has been introduced to allow developers to add custom tabs to the "General" panel of the editor. This is ideal for global extension settings or tools that are not specific to a single block. ```typescript import { GeneralPanelTab, ExtensionBuilder } from '@stripoinc/ui-editor-extensions'; class MyGlobalTab extends GeneralPanelTab { getId(): string { return 'my-global-tab'; } getIcon(): string { return 'my-icon'; } getName(): string { return 'My Tool'; } getTabIndex(): number { return 1; } getTemplate(): string { return '
My custom tool content
'; } } // Register in ExtensionBuilder const extension = new ExtensionBuilder() .addGeneralPanelTab(MyGlobalTab) .build(); ``` #### New: Repeatable UI Elements Introduced `UIElementType.REPEATABLE` for creating lists of items within controls. This allows for dynamic lists of sub-elements that can be managed within a single control. #### New: Block Settings Panel Title with HTML Support The `Block` class now includes a `getSettingsPanelTitleHtml()` method that allows you to customize the settings panel title with HTML content. ```typescript import { Block } from '@stripoinc/ui-editor-extensions'; class MyBlock extends Block { // ... other methods getSettingsPanelTitleHtml(): string { return ' My Custom Block'; } } ``` #### Enhanced: Control API with Repeatable Support The `ControlApi` has been enhanced with `updateUIElementValue()` to support granular updates, including items within repeatable elements using path notation. ```typescript // Update a specific field in a repeatable list this.api.updateUIElementValue('items[0].title', 'New Title'); ``` The `onValueChanged` callback now also receives an optional `index` parameter when the change occurs within a repeatable element. ```typescript this.api.onValueChanged('items.title', (newValue, oldValue, index) => { console.log(`Item at index ${index} changed from ${oldValue} to ${newValue}`); }); ``` *** ### Breaking Changes #### 1. ContextActionType: Removed Items The following constants have been removed from `ContextActionType`: * `UPDATE_MODULE` * `REMOVE_CONTAINER` *** ### Migration Guide #### From v3.4.0 to v3.5.0 **Context Actions**: If you were using `ContextActionType.UPDATE_MODULE` or `ContextActionType.REMOVE_CONTAINER`, please update your implementation as these are no longer supported. *** ## v3.4.0 ### Overview Version 3.4.0 is a minor release of the Stripo Extensions SDK that enhances the External Image Library Tab with contextual node information. This release maintains full backward compatibility with v3.3.0. **Release Date:** 12 December 2025\ **Release Type:** Minor Release\ **Version:** 3.4.0\ **Previous Version:** 3.3.0 :::tip Editor Compatibility The npm package version 3.4.0 is fully compatible with the Stripo Editor starting from version **2.48.0 and higher**. ::: *** ### What's New #### Enhanced: ExternalImageLibraryTab with Node Context The `ExternalImageLibraryTab` class has been enhanced to provide contextual information about the selected node when opening the image library. The `openImageLibraryTab()` method now receives an optional `selectedNode` parameter. ```typescript import { ExternalImageLibraryTab, ExternalGalleryImageSelectCallback, ImmutableHtmlNode } from '@stripoinc/ui-editor-extensions'; class MyCustomImageTab extends ExternalImageLibraryTab { getName(): string { return 'My Images'; } // Old (v3.3.0) // openImageLibraryTab( // container: HTMLElement, // onImageSelectCallback: ExternalGalleryImageSelectCallback // ): void { ... } // New (v3.4.0) - with optional selectedNode parameter openImageLibraryTab( container: HTMLElement, onImageSelectCallback: ExternalGalleryImageSelectCallback, selectedNode?: ImmutableHtmlNode // NEW: Optional node context ): void { // Access information about the selected node if (selectedNode) { const nodeId = selectedNode.getAttribute('id'); console.log('Image library opened for node:', nodeId); // Use node context to customize image library behavior } // Render your custom image library UI in the container container.innerHTML = ` `; } } ``` **Use Cases:** * Filter or customize image suggestions based on the selected node type * Apply node-specific image constraints (size, format, etc.) * Provide contextual image recommendations based on node attributes **Backward Compatibility:** * The `selectedNode` parameter is optional, so existing implementations continue to work without modifications * You can gradually adopt this feature when needed *** ### Migration Guide #### From v3.3.0 to v3.4.0 **No Breaking Changes** - Version 3.4.0 is fully backward compatible with v3.3.0. All existing code will continue to work without modifications. **Optional Enhancement** - If you want to take advantage of node context in your custom image library tabs, you can add the optional `selectedNode` parameter to your `openImageLibraryTab()` method implementation. *** ## v3.3.0 ### Overview Version 3.3.0 is a minor release of the Stripo Extensions SDK that introduces enhanced node query methods. This release maintains full backward compatibility with v3.2.0. **Release Date:** 28 November 2025\ **Release Type:** Minor Release\ **Version:** 3.3.0\ **Previous Version:** 3.2.0 :::tip Editor Compatibility The npm package version 3.3.0 is fully compatible with the Stripo Editor starting from version **2.46.0 and higher**. ::: *** ### What's New #### New: Module Element Query Method The `BaseImmutableNode` interface now includes a method `getClosetModuleElement()` to retrieve the closest module element directly. ```typescript import { ImmutableHtmlNode } from '@stripoinc/ui-editor-extensions'; // In your block or control const moduleId = node.getClosestModuleId(); console.log('Closest module ID:', moduleId); // NEW in v3.3.0 - Get the module element itself const moduleElement = node.getClosetModuleElement(); if (moduleElement) { console.log('Module element found:', moduleElement.getTagName()); // Perform operations on the module element } ``` **Use Cases:** * Direct access to module element for modification * Inspecting module structure and attributes **Difference from `getClosestModuleId()`:** * `getClosestModuleId()`: Returns the numeric ID of the closest module * `getClosetModuleElement()`: Returns the actual ImmutableNode element of the module, or `undefined` if not found *** ### Migration Guide #### From v3.2.0 to v3.3.0 **No Breaking Changes** - Version 3.3.0 is fully backward compatible with v3.2.0. All existing code will continue to work without modifications. *** ## v3.2.0 ### Overview Version 3.2.0 is a minor release of the Stripo Extensions SDK that introduces support for a custom image library tab and enhanced image metadata capabilities. This release maintains full backward compatibility with v3.1.0. **Release Date:** 09 November 2025\ **Release Type:** Minor Release\ **Version:** 3.2.0\ **Previous Version:** 3.1.0 :::tip Editor Compatibility The npm package version 3.2.0 is fully compatible with the Stripo Editor starting from version **2.43.0 and higher**. ::: *** ### What's New #### New: ExternalImageLibraryTab Class A new class has been introduced to enable developers to create a custom tab within the build-in Stripo image library. This allows you to organize images from different sources into a separate tab for better user experience. ::: image-wrap ![](/img/extensions/externalImagesLibraryTab1.png) ::: ```typescript import { ExternalImageLibraryTab, ExternalGalleryImageSelectCallback } from '@stripoinc/ui-editor-extensions'; class MyCustomImageTab extends ExternalImageLibraryTab { /** * Returns the translated name/label for the tab */ getName(): string { return 'My Images'; } /** * Opens the custom tab and renders your image library UI * @param container - DOM element where you should render your UI * @param onImageSelectCallback - Callback to invoke when user selects an image */ openImageLibraryTab( container: HTMLElement, onImageSelectCallback: ExternalGalleryImageSelectCallback ): void { // Render your custom image library UI in the container container.innerHTML = ` `; // When user selects an image, call the callback // onImageSelectCallback(imageData); } } // Register in ExtensionBuilder const extension = new ExtensionBuilder() .withExternalImageLibraryTab(MyCustomImageTab) .build(); ``` **Full Example Implementation:** [How to Integrate an External Image Library Tab](./tutorials/examples/integrations/external-image-library-tab.md) *** #### Enhanced: ExternalGalleryImage Interface The `ExternalGalleryImage` interface now supports optional metadata labels for additional image information: ::: image-wrap ![](/img/extensions/externalImagesLibraryTab2.png) ::: ```typescript import { ExternalGalleryImage } from '@stripoinc/ui-editor-extensions'; // Old (v3.1.0) const image: ExternalGalleryImage = { originalName: 'product.png', width: 800, height: 600, sizeBytes: 102400, url: 'https://example.com/product.png', altText: 'Product image' }; // New (v3.2.0) - with optional labels const image: ExternalGalleryImage = { originalName: 'product.png', width: 800, height: 600, sizeBytes: 102400, url: 'https://example.com/product.png', altText: 'Product image', labels: { // NEW: Optional metadata category: 'Products', photographer: 'John Doe', license: 'Commercial', tags: 'red, clothing, winter' } }; ``` *** ### Migration Guide #### From v3.1.0 to v3.2.0 **No Breaking Changes** - Version 3.2.0 is fully backward compatible with v3.1.0. All existing code will continue to work without modifications. ## v3.1.0 ### Overview Version 3.1.0 is a minor release of the Stripo Extensions SDK that introduces new UI elements for creating orderable/sortable lists, enhanced control lifecycle hooks, and additional node query capabilities. This release maintains full backward compatibility with v3.0.0. **Release Date:** 03 November 2025\ **Release Type:** Minor Release\ **Version:** 3.1.0\ **Previous Version:** 3.0.0 :::tip Editor Compatibility The npm package version 3.1.0 is fully compatible with the Stripo Editor starting from version **2.42.0 and higher**. ::: *** ### What's New #### New: Orderable UI Element A new UI element has been introduced to create orderable/sortable lists within your custom controls. These elements enable users to reorder items through drag-and-drop or positioning controls. ```typescript import { UIElementType } from '@stripoinc/ui-editor-extensions'; // Available orderable element types UIElementType.ORDERABLE // 'UE-ORDERABLE' - Container for orderable items UIElementType.ORDERABLE_ITEM // 'UE-ORDERABLE-ITEM' - Individual orderable item UIElementType.ORDERABLE_ICON // 'UE-ORDERABLE-ICON' - Icon for ordering controls ``` *** #### New: Control `onDocumentChanged()` Lifecycle Hook Controls now have access to a new lifecycle hook that is called whenever any part of the document template changes. ```typescript import { Control, ImmutableHtmlNode } from '@stripoinc/ui-editor-extensions'; class MyControl extends Control { getId(): string { return 'my-control'; } getTemplate(): string { return '
...
'; } /** * NEW in v3.1.0 * Called when any part of the document template has changed * @param node - The immutable HTML node representing current node instance */ onDocumentChanged(node: ImmutableHtmlNode): void { // React to document-wide changes // This can be frequent; use cautiously for performance-sensitive operations const customValue = node.getAttribute('data-custom'); if (customValue) { // Update control state based on document changes } } } ``` **Important Notes:** * This hook can be called frequently during editing * Use cautiously for performance-sensitive operations * Receives the current node instance as a parameter * Useful for controls that need to react to global template changes **Difference from `onTemplateNodeUpdated()`:** * `onTemplateNodeUpdated()`: Called when the node associated with this control's context is updated * `onDocumentChanged()`: Called when any part of the document template changes *** #### New: Node Module Identification The `BaseImmutableNode` interface now includes a method to retrieve the closest module ID associated with a node. ```typescript import { ImmutableHtmlNode } from '@stripoinc/ui-editor-extensions'; // In your block or control const moduleId = node.getClosestModuleId(); console.log('Closest module ID:', moduleId); ``` **Use Cases:** * Identifying which module a node belongs to * Module-specific operations and logic **Example Implementation:** The [External Merge Tags Selector](/extensions/tutorials/examples/integrations/external-merge-tags-selector) tutorial demonstrates practical usage of `getClosestModuleId()` for detecting module context and displaying contextual UI indicators. *** ### Migration Guide #### From v3.0.0 to v3.1.0 **No Breaking Changes** - Version 3.1.0 is fully backward compatible with v3.0.0. All existing code will continue to work without modifications. ## v3.0.0 ### Overview Version 3.0.0 is a major release of the Stripo Extensions SDK that introduces significant architectural improvements, enhanced TypeScript support, and new features for extending the Stripo email editor. This release includes breaking changes that require migration from v2.x. **Release Date:** 20 October 2025\ **Release Type:** Major Release\ **Version:** 3.0.0\ **Previous Version:** 2.0.2 :::tip Editor Compatibility The npm package version 3.0.0 is fully compatible with the Stripo Editor starting from version **2.40.0 and higher**. ::: *** ### What's New #### Enhanced Package Distribution The SDK now supports multiple module formats for better compatibility across different JavaScript environments: * **ESM (ECMAScript Modules)**: Modern ES module format * **CJS (CommonJS)**: Node.js compatible format * **Browser Bundle**: Optimized browser-ready bundle ```json { "exports": { ".": { "types": "./dist/types/index.d.ts", "import": "./dist/esm/index.js", "require": "./dist/cjs/index.cjs", "default": "./dist/esm/index.js" }, "./browser": { "browser": "./dist/browser/index.js", "default": "./dist/browser/index.js" } } } ``` **Migration:** No code changes required. The package will automatically use the appropriate format for your environment. *** #### New: Icons Registry A new `IconsRegistry` class enables [registration](/extensions/features/icons-management) of custom SVG icons for use throughout your extensions. ```typescript import { IconsRegistry } from '@stripoinc/ui-editor-extensions'; class MyIconsRegistry extends IconsRegistry { registerIconsSvg(iconsMap: Record): void { iconsMap['custom-icon'] = '...'; iconsMap['another-icon'] = '...'; } } // Register in ExtensionBuilder const extension = new ExtensionBuilder() .setIconsRegistry(MyIconsRegistry) .build(); ``` **Benefits:** * Centralized icon management * SVG support for crisp, scalable icons * Easy to share icons across multiple components *** #### New: Control Visibility Management Controls can now be dynamically shown or hidden based on the selected node's state. ```typescript import { Control } from '@stripoinc/ui-editor-extensions'; class MyControl extends Control { // New method - implement to control visibility isVisible(node: ImmutableHtmlNode): boolean { // Show control only for specific block types return node.hasClass('my-custom-block'); } getId(): string { return 'my-control'; } getTemplate(): string { return '
...
'; } } ``` **Use Cases:** * Show controls only for specific block types * Hide controls based on user permissions * Conditional UI based on template state *** #### New: Additional Built-in Controls Available Pre-built visibility controls are now available for major block types: * `StructureVisibilityBuiltInControl` * `ContainerVisibilityBuiltInControl` * `ImageVisibilityBuiltInControl` * `ButtonVisibilityBuiltInControl` * `TextVisibilityBuiltInControl` New typography and styling controls for button blocks: * `ButtonFontFamilyBuiltInControl` - Font family selection for buttons * `ButtonTextSizeBuiltInControl` - Button text size control * `ButtonTextStyleAndFontColorBuiltInControl` - Combined text style and color control New background and border controls: * `ContainerBackgroundImageBuiltInControl` - Set background images on containers * `ContainerBorderBuiltInControl` - Container border styling * `StructureBackgroundImageBuiltInControl` - Structure background images *** #### New BlocksPanel Customization [Customize](/extensions/reference/blocks/BlocksPanel#getmodulestabiconname) the modules tab icon in the blocks panel: ```typescript import { BlocksPanel } from '@stripoinc/ui-editor-extensions'; class MyBlocksPanel extends BlocksPanel { getModulesTabIconName(modulesTab: { key: string; label: Record }): string | undefined { return 'custom-modules-icon'; // Use registered icon } } ``` *** ### Breaking Changes #### 1. Class Architecture: Abstract Classes Replaced with Validated Classes **BREAKING CHANGE:** All extension base classes have been refactored from TypeScript abstract classes to regular classes with runtime validation. ##### Affected Classes * `Block` * `Control` * `UIElement` * `ContextAction` ##### Old Implementation (v2.x) ```typescript import { Block } from '@stripoinc/ui-editor-extensions'; export class MyBlock extends Block { // TypeScript enforced abstract method implementation abstract getId(): string; abstract getTemplate(): string; abstract getIcon(): string; abstract getName(): string; abstract getDescription(): string; } ``` ##### New Implementation (v3.0) ```typescript import { Block } from '@stripoinc/ui-editor-extensions'; export class MyBlock extends Block { constructor() { super(); // Now calls BaseValidatedClass constructor } // Methods now throw errors if not implemented getId(): string { return 'my-block'; } getTemplate(): string { return '
...
'; } getIcon(): string { return 'icon.svg'; } getName(): string { return 'My Block'; } getDescription(): string { return 'Description'; } } ``` **Why This Change?** * Improved runtime validation with clearer error messages * Better developer experience with validation logging **Migration Steps:** 1. If your class defines a `constructor()`, ensure you call `super()` inside it 2. Ensure all required methods are implemented (validation will catch missing methods at runtime) 3. Test your extension - you'll see helpful error messages if methods are missing *** #### 2. Block Lifecycle Hooks: Signature Changes **BREAKING CHANGE:** Several `Block` lifecycle hooks have changed their signatures. ##### `onDocumentInit()` - Return Type Removed **Old (v2.x):** ```typescript onDocumentInit(): HtmlNodeModifier | undefined { const modifier = this.api.getDocumentModifier(); // Make modifications return modifier; } ``` **New (v3.0):** ```typescript onDocumentInit(): void { const modifier = this.api.getDocumentModifier(); // Make modifications and apply them modifier.apply(new ModificationDescription(`Some modifications applied`)) // No return value needed } ``` **Migration:** Remove the return statement and apply migrations with the appropriate description manually. *** ##### `onSelect()` - Return Type Removed **Old (v2.x):** ```typescript onSelect(node: ImmutableHtmlNode): HtmlNodeModifier | undefined { const modifier = this.api.getDocumentModifier(); // Make modifications return modifier; } ``` **New (v3.0):** ```typescript onSelect(node: ImmutableHtmlNode): void { const modifier = this.api.getDocumentModifier(); // Make modifications and apply them modifier.apply(new ModificationDescription(`Some modifications applied`)) // No return value needed } ``` **Migration:** Remove the return statement and apply migrations with the appropriate description manually. *** ##### `onCopy()` - Parameters and Return Type Changed **Old (v2.x):** ```typescript onCopy( targetNode: ImmutableHtmlNode, sourceNode: ImmutableHtmlNode ): HtmlNodeModifier | undefined { const modifier = this.api.getDocumentModifier(); // Modify the copied node return modifier; } ``` **New (v3.0):** ```typescript onCopy(modifier: HtmlNodeModifier): void { // Use the provided modifier directly // Modifier is already set up for the copy operation modifier.setAttribute('data-copied', 'true'); // No return value } ``` **Migration:** 1. Change method signature to accept `modifier: HtmlNodeModifier` 2. Use the provided modifier parameter directly 3. Remove return statement 4. Your modifications will now be applied directly within the copy operation. *** ##### `onDelete()` - Return Type Removed **Old (v2.x):** ```typescript onDelete(node: ImmutableHtmlNode): HtmlNodeModifier | undefined { const modifier = this.api.getDocumentModifier(); // Make modifications return modifier; } ``` **New (v3.0):** ```typescript onDelete(node: ImmutableHtmlNode): void { const modifier = this.api.getDocumentModifier(); // Make modifications and apply them modifier.apply(new ModificationDescription(`Some modifications applied`)) // No return value needed } ``` **Migration:** Remove the return statement and apply migrations with the appropriate description manually. *** ##### `onDocumentChanged()` - Now Receives Node Parameter **Old (v2.x):** ```typescript onDocumentChanged(): void { // React to any document changes // No context about which node changed } ``` **New (v3.0):** ```typescript onDocumentChanged(node: ImmutableHtmlNode): void { // React to document changes for this specific node // The node parameter indicates which extension instance triggered the event // For example, if you have multiple instances of a custom block (such as Coupon block), this hook will be called separately for each node, allowing you to distinguish between them (e.g., Coupon block 1 and Coupon block 2) if (node.getAttribute('data-custom') === 'value') { // Handle specific node changes } } ``` **Migration:** Add the `node: ImmutableHtmlNode` parameter to your method signature. *** #### 3. Control Class: `onTemplateNodeUpdated()` Now Optional **BREAKING CHANGE:** The `onTemplateNodeUpdated()` method is now optional with a default no-op implementation. **Old (v2.x):** ```typescript import { Control } from '@stripoinc/ui-editor-extensions'; class MyControl extends Control { abstract onTemplateNodeUpdated(node: ImmutableHtmlNode): void; // HAD to implement this method onTemplateNodeUpdated(node: ImmutableHtmlNode): void { // Update UI based on node changes } } ``` **New (v3.0):** ```typescript import { Control } from '@stripoinc/ui-editor-extensions'; class MyControl extends Control { // Now optional - only implement if needed onTemplateNodeUpdated(node: ImmutableHtmlNode): void { // Update UI based on node changes } // Or omit entirely if you don't need it } ``` **Migration:** No changes required if you're already implementing this method. If you had empty implementations, you can remove them. *** #### 4. ContextAction: Method Renamed **BREAKING CHANGE:** The `getIconClass()` method has been renamed to `getIcon()`. **Old (v2.x):** ```typescript import { ContextAction } from '@stripoinc/ui-editor-extensions'; class MyAction extends ContextAction { getIconClass(): string { return 'custom-icon'; } } ``` **New (v3.0):** ```typescript import { ContextAction } from '@stripoinc/ui-editor-extensions'; class MyAction extends ContextAction { getIcon(): string { return 'custom-icon'; // Icon name from IconsRegistry } } ``` **Migration:** 1. Rename `getIconClass()` to `getIcon()` *** #### 5. Built-in Control Renames **BREAKING CHANGE:** Several built-in controls have been renamed for clarity and consistency. #### Font Family Controls **Old (v2.x):** ```typescript import { FontFamilyBuiltInControl, // Text font control LinkColorBuiltInControl, // Text Link color ButtonFontColorBuiltInControl, // Button font color BackgroundImageBuiltInControl // Generic background } from '@stripoinc/ui-editor-extensions'; ``` **New (v3.0):** ```typescript import { TextFontFamilyBuiltInControl, // Renamed from FontFamilyBuiltInControl TextLinkColorBuiltInControl, // Renamed from LinkColorBuiltInControl ButtonFontFamilyBuiltInControl, // New - button specific // BackgroundImageBuiltInControl - REMOVED, use specific controls below ContainerBackgroundImageBuiltInControl, // Container specific StructureBackgroundImageBuiltInControl // Structure specific } from '@stripoinc/ui-editor-extensions'; ``` **Migration Table:** | Old Name (v2.x) | New Name (v3.0) | Notes | |----------------|----------------|-------| | `FontFamilyBuiltInControl` | `TextFontFamilyBuiltInControl` | Renamed for clarity | | `LinkColorBuiltInControl` | `TextLinkColorBuiltInControl` | Renamed for clarity | | `ButtonFontColorBuiltInControl` | `ButtonTextStyleAndFontColorBuiltInControl` | Enhanced functionality | | `BackgroundImageBuiltInControl` | Use `ContainerBackgroundImageBuiltInControl` or `StructureBackgroundImageBuiltInControl` | Removed - use specific controls | *** ### Extension Class Changes #### New: `iconsRegistry` Support The `Extension` class now supports icon registry configuration. **Old (v2.x):** ```typescript const extension = new ExtensionBuilder() .setI18n(translations) .setStyles(styles) .addBlock(MyBlock) .build(); ``` **New (v3.0):** ```typescript const extension = new ExtensionBuilder() .setI18n(translations) .setStyles(styles) .setIconsRegistry(MyIconsRegistry) // NEW .addBlock(MyBlock) .build(); ``` The `Extension` class now has: * `getIconsRegistry(): ConstructorOfType | undefined` - NEW method **Migration:** No changes required unless you want to use the new IconsRegistry feature. --- --- url: https://plugin.stripo.email/hosting-stripo-editor-files-on-your-own-cdn.md --- # Hosting Stripo Editor Files on Your Own CDN Stripo's static files are hosted on Stripo servers and can be accessed via the following URL:\ `https://plugins.stripo.email/resources/uieditor/latest/UIEditor.js` To enhance the loading speed of the source files, you have the option to set up your own Content Delivery Network (CDN) and host the editor's static files there. The static files required for the UI Editor can be found in [Stripo's GitHub repository](https://github.com/stripoinc/stripo-plugin-releases). In the repository, navigate to the `main` branch and locate the `static` folder. This folder contains the latest release of static files. You may copy these files and save them on your server. It is required to maintain the same structure (on the same level as they are in the repository), meaning the encapsulation of the files should remain unchanged. Once the files are on your server, you need to change the URL to the `UIEditor.js` script from ours to your server. For example: ```html ``` Please note that while you can cache all these files on your server, the `UIEditor.js` script should not be cached to ensure you are always using the latest version. --- --- url: https://plugin.stripo.email/extensions/reference/types/EditorPermissions.md --- # EditorPermissions Interface describing the current user's permissions in the Stripo Email Editor. ```typescript interface EditorPermissions ``` ## Description `EditorPermissions` represents the set of feature permissions granted to the current editor user. Each permission group is an [`EditorPermissionAccess`](#editorpermissionaccess) object with optional `read` and `write` flags. Permissions are provided by your backend through the User Permissions API and can be read in extensions via [BaseApi](../api/BaseApi.md) using `getUserPermissions()` or observed with `onUserPermissionsUpdated()`. See [Permissions and Access Management](/editor-configuration/permissions-and-access-management) for how permissions are configured on your backend. ## Import ```typescript import type { EditorPermissions } from '@stripoinc/ui-editor-extensions'; ``` ## Properties All properties are optional. A missing permission group means no restriction information is available for it. | Property | Type | Description | |----------|------|-------------| | appearance | `EditorPermissionAccess` | Access to appearance settings (fonts, colors, styles) | | codeEditor | `EditorPermissionAccess` | Access to the HTML code editor | | content | `EditorContentPermissionAccess` | Access to template content, with optional text-only editing | | modules | `EditorPermissionAccess` | Access to browsing and managing custom/saved modules | | entityImages | `EditorPermissionAccess` | Access to the image gallery scoped to the current entity | | projectImages | `EditorPermissionAccess` | Access to the image gallery scoped to the project | | versionHistory | `EditorPermissionAccess` | Access to viewing and restoring template version history | | manageOwnComments | `EditorPermissionAccess` | Access to viewing and managing the user's own comments | | manageAllComments | `EditorPermissionAccess` | Access to viewing and managing comments of all users | | replyAllComments | `EditorPermissionAccess` | Access to replying to comments of all users | | accessibilityTesting | `EditorPermissionAccess` | Access to the Accessibility Testing Mode | | elementsLock | `EditorPermissionAccess` | Access to locking and unlocking template elements | ## Related Types ### EditorPermissionAccess Base access descriptor for a permission group. ```typescript interface EditorPermissionAccess { read?: boolean; write?: boolean; } ``` | Property | Type | Description | |----------|------|-------------| | read | `boolean` | Allows viewing the feature | | write | `boolean` | Allows modifying data through the feature | ### EditorContentPermissionAccess Access descriptor for the `content` permission group with an additional text-only flag. ```typescript interface EditorContentPermissionAccess extends EditorPermissionAccess { textOnly?: boolean; } ``` | Property | Type | Description | |----------|------|-------------| | textOnly | `boolean` | Allows editing only text values without changing layout or design | ## Example ```typescript const permissions = this.api.getUserPermissions(); // Adapt extension UI to the user's access level if (!permissions.content?.write) { this.disableEditingControls(); } if (permissions.content?.textOnly) { this.enableTextOnlyMode(); } // React to permission changes this.api.onUserPermissionsUpdated((newPermissions) => { this.setEditingEnabled(!!newPermissions.content?.write); }); ```
<${BLOCK_IMAGE} ${BlockAttr.BLOCK_IMAGE.src}="https://hpy.stripocdn.email/content/guids/CABINET_e5244175dd1729a1d6ee1f8bd0d5490f/images/50421523966142571.jpg" ${BlockAttr.BLOCK_IMAGE.alt}="Lorem ipsum"> <${BLOCK_TEXT}>

Lorem ipsum dolor sit amet

<${BLOCK_BUTTON} ${BlockAttr.BLOCK_BUTTON.href}="https://stripo.email"> Click me
<${BlockType.BLOCK_TEXT} align="center">

{{COUPON_CODE}}

` and `` elements, rather than as a root node. Because of this, users cannot directly select or edit this text region using `BlockCompositionType.BLOCK`, which also restricts free-form editing of the coupon code area. ::: tip Merge Tags Merge tags like {{COUPON\_CODE}} can be replaced with actual values when generating the final email. ::: ## Step 3: Register Custom Icons Create the icon registry to provide custom icons for your blocks. ### Create Icon Registry Create `src/icons/ExtensionIconsRegistry.js`: ```javascript import {IconsRegistry} from '@stripoinc/ui-editor-extensions'; import couponBlockIcon from './coupon.svg?raw'; /** * Extension Icons Registry * Registers custom SVG icons for use in the extension */ export class ExtensionIconsRegistry extends IconsRegistry { /** * Registers SVG icons by adding them to the icons map * @param {Object} iconsMap - Map of icon identifiers to SVG strings */ registerIconsSvg(iconsMap) { iconsMap['couponBlockIcon'] = couponBlockIcon; } } ``` ### Add SVG Icon Create `src/icons/coupon.svg`: ```svg ``` ::: tip Custom Icons You can use any SVG icon you prefer. The `?raw` import suffix in Vite tells the bundler to import the SVG file as a string rather than a URL, which is required for the IconsRegistry. ::: ## Step 4: Create Custom Controls Stripo provides a wide variety of built-in control classes for common styling needs, which you can use directly without implementing them from scratch. In this section, we'll utilize three such built-in controls to easily add styling to our coupon block: ### Font Color Control Create `src/settings/controls/CouponFontColorControl.js`: ```javascript import {TextColorBuiltInControl} from '@stripoinc/ui-editor-extensions'; export const COUPON_FONT_COLOR_CONTROL_ID = 'coupon-font-color-control'; /** * Coupon Font Color Control * Extends the built-in text color control for changing coupon text color */ export class CouponFontColorControl extends TextColorBuiltInControl { /** * Returns the unique identifier for this control * @returns {string} Control identifier */ getId() { return COUPON_FONT_COLOR_CONTROL_ID; } } ``` ### Font Size Control Create `src/settings/controls/CouponFontSizeControl.js`: ```javascript import {TextSizeBuiltInControl} from '@stripoinc/ui-editor-extensions'; export const COUPON_FONT_SIZE_CONTROL_ID = 'coupon-font-size-control'; /** * Coupon Font Size Control * Extends the built-in text size control for changing coupon text size */ export class CouponFontSizeControl extends TextSizeBuiltInControl { /** * Returns the unique identifier for this control * @returns {string} Control identifier */ getId() { return COUPON_FONT_SIZE_CONTROL_ID; } } ``` ### Font Family Control Create `src/settings/controls/CouponFontFamilyControl.js`: ```javascript import {TextFontFamilyBuiltInControl} from '@stripoinc/ui-editor-extensions'; export const COUPON_FONT_FAMILY_CONTROL_ID = 'coupon-font-family-control'; /** * Coupon Font Family Control * Extends the built-in font family control for changing coupon text font */ export class CouponFontFamilyControl extends TextFontFamilyBuiltInControl { /** * Returns the unique identifier for this control * @returns {string} Control identifier */ getId() { return COUPON_FONT_FAMILY_CONTROL_ID; } } ``` ## Step 5: Configure Settings Panel The settings panel registry determines which controls appear when users select your custom block. Create `src/settings/ExtensionSettingsPanelRegistry.js`: ```javascript import {SettingsPanelRegistry, SettingsPanelTab, SettingsTab} from '@stripoinc/ui-editor-extensions'; import {COUPON_FONT_COLOR_CONTROL_ID} from './controls/CouponFontColorControl'; import {COUPON_FONT_SIZE_CONTROL_ID} from './controls/CouponFontSizeControl'; import {COUPON_FONT_FAMILY_CONTROL_ID} from './controls/CouponFontFamilyControl'; /** * Extension Settings Panel Registry * Configures which controls appear in the settings panel for each custom block */ export class ExtensionSettingsPanelRegistry extends SettingsPanelRegistry { /** * Registers controls for custom blocks * @param {Object} controls - Map of block IDs to control configurations */ registerBlockControls(controls) { // Register controls for the coupon block controls['coupon-block'] = [ // Create a tab in the Styles section new SettingsPanelTab( SettingsTab.STYLES, // Built-in tab identifier [ // List of control IDs to display in this tab COUPON_FONT_FAMILY_CONTROL_ID, COUPON_FONT_SIZE_CONTROL_ID, COUPON_FONT_COLOR_CONTROL_ID ] ) ] } } ``` ## Step 6: Add Internationalization Create translation files to support multiple languages in the editor interface. Create `src/i18n/en.js`: ```javascript /** * English translations for the Coupon Block extension * Keys are used in this.api.translate() calls throughout the extension */ export default { "Coupon": "Coupon", "Add a discount code to your email template": "Add a discount code to your email template", } ``` ### Adding More Languages To support additional languages, create more translation files: ```javascript // src/i18n/es.js - Spanish translations export default { "Coupon": "Cupón", "Add a discount code to your email template": "Añade un código de descuento a tu plantilla de correo", } // src/i18n/fr.js - French translations export default { "Coupon": "Coupon", "Add a discount code to your email template": "Ajoutez un code de réduction à votre modèle d'e-mail", } ``` ## Step 7: Register the Extension Create `src/extension.js` to assemble all components and register the extension: ```javascript import { ExtensionBuilder } from '@stripoinc/ui-editor-extensions'; import {ExtensionIconsRegistry} from './icons/ExtensionIconsRegistry'; import {CouponBlock} from './CouponBlock'; import en from './i18n/en'; import es from './i18n/es'; import fr from './i18n/fr'; import {ExtensionSettingsPanelRegistry} from './settings/ExtensionSettingsPanelRegistry'; import {CouponFontColorControl} from './settings/controls/CouponFontColorControl'; import {CouponFontSizeControl} from './settings/controls/CouponFontSizeControl'; import {CouponFontFamilyControl} from './settings/controls/CouponFontFamilyControl'; /** * Coupon Block Extension * Combines all extension components into a single extension package */ const extension = new ExtensionBuilder() // Register custom icons .withIconsRegistry(ExtensionIconsRegistry) // Register localization files .withLocalization({ 'en': en, 'es': es, 'fr': fr, }) // Register settings panel configuration .withSettingsPanelRegistry(ExtensionSettingsPanelRegistry) // Register custom controls .addControl(CouponFontColorControl) .addControl(CouponFontSizeControl) .addControl(CouponFontFamilyControl) // Register custom blocks .addBlock(CouponBlock) // Build the final extension object .build(); export default extension; ``` ## Step 8: Run the Development Server Your coupon block extension is now ready for testing. ### Start the Development Server ```bash npm run dev ``` This command will: 1. Start the Vite development server on `http://localhost:3000` 2. Automatically open your default browser 3. Load the Stripo editor with your coupon block extension ## Complete Example For a full working example with all source files, check out the complete implementation in our [GitHub repository](https://github.com/stripoinc/stripo-plugin-samples/tree/main/extensions/javascript/coupon-block). --- --- url: >- https://plugin.stripo.email/extensions/tutorials/examples/integrations/external-image-library.md --- # Integrate an External Image Library ## Overview The External Image Library integration allows users to browse and select images from your image hosting service or third-party platforms directly within the Stripo Email Editor. This integration provides a seamless experience for embedding images into email templates while maintaining full control over your image assets. ### What You'll Build In this tutorial, you'll create a fully functional image library modal that: * Displays a responsive grid of image thumbnails with hover effects * Supports category-based filtering (All, Nature, Abstract, Digital and Photography) * Handles image selection with proper callback integration * Returns image data in the format expected by the Stripo Editor ::: image-wrap ![](/img/plugin/new/image207.webp){width=1423 height=562} ::: ::: image-wrap ![](/img/plugin/new/image208.webp){width=1999 height=1045} ::: ### Use Cases * **Custom Image Platform**: Connect to your proprietary image hosting service * **Curated Content**: Provide pre-approved images for brand consistency * **Third-Party Integration**: Connect to platforms such as Unsplash, Pexels, or Shutterstock * **Dynamic Content**: Fetch images from your API or CMS in real time * **Brand Asset Management**: Integrate with your Digital Asset Management (DAM) system ## Prerequisites Before starting this tutorial, ensure you have: * Node.js version 22.x or higher installed * A basic understanding of JavaScript ES6+ syntax * Familiarity with the Stripo Extensions SDK ## Understanding the Interface The [ExternalImageLibrary](/extensions/reference/integrations/ExternalImageLibrary) class must implement a single method: ```typescript openImageLibrary( currentImageUrl: string, onImageSelectCallback: (image: ExternalGalleryImage) => void, onCancelCallback: () => void ): void ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `currentImageUrl` | `string` | Currently selected image URL (if any) | | `onImageSelectCallback` | `Function` | Callback function invoked when a user selects an image | | `onCancelCallback` | `Function` | Callback function invoked when a user cancels the selection | ### ExternalGalleryImage Object When a user selects an image, your library implementation must return an object with the following structure: ```typescript { originalName: string; // Image file name width: number; // Image width in pixels height: number; // Image height in pixels sizeBytes: number; // File size in bytes url: string; // Image URL altText: string; // Alt text for accessibility labels?: Record; // Optional metadata (v3.2.0+) } ``` :::tip Starting from v3.2.0, you can include optional metadata using the `labels` property to store additional image information such as category, source or custom tags. ::: ::: image-wrap ![](/img/extensions/externalImagesLibraryTab2.png) ::: ## Step 1: Project Setup Create your project structure and install dependencies according to the [Getting Started](/extensions/getting-started) guide. Your project directory structure should look like this: ```bash external-image-library/ ├── index.html ├── src/ │ ├── creds.js │ ├── index.js │ └── extension.js ├── package.json └── vite.config.js ``` ## Step 2: Create the Image Library Class Create a new file `src/MyExternalImagesLibrary.js` with the following basic class structure: ```javascript import {ExternalImageLibrary} from '@stripoinc/ui-editor-extensions'; /** * External Image Library Implementation * This class implements a modal image gallery with filtering capabilities * for the Stripo Email Editor extension system. */ export default class MyExternalImagesLibrary extends ExternalImageLibrary { // Instance properties externalLibrary; imageSelectCallback = () => {}; cancelCallback = () => {}; activeCategory = 'all'; constructor() { super(); this.createModal(); this.attachEventListeners(); this.initializeFilters(); } /** * Required method called by the Stripo editor * Opens the image library modal dialog * @param {string} currentImageUrl - Currently selected image URL (if any) * @param {Function} onImageSelectCallback - Callback invoked when an image is selected * @param {Function} onCancelCallback - Callback invoked when the modal is cancelled */ openImageLibrary(currentImageUrl, onImageSelectCallback, onCancelCallback) { // Store callbacks this.imageSelectCallback = onImageSelectCallback; this.cancelCallback = onCancelCallback; // Show modal this.externalLibrary.style.display = 'flex'; // Reset filters to show all images this.filterImages('all'); const allButton = this.externalLibrary.querySelector('[data-category="all"]'); if (allButton) { this.updateActiveButton(allButton); } } } ``` ### Key Components Explained * **Instance Properties**: * `externalLibrary`: Reference to the modal DOM element * `imageSelectCallback`: Stores the success callback from the Stripo editor * `cancelCallback`: Stores the cancel callback from the Stripo editor * `activeCategory`: Tracks the currently active filter category * **Constructor**: Initializes the complete modal UI when the class is instantiated * **openImageLibrary**: Required method that: * Stores the callbacks for later invocation * Displays the modal dialog * Resets filters to display all images * Updates the active button state ## Step 3: Define Image Data and Styles Add static properties for image data and UI configuration. Continue editing `src/MyExternalImagesLibrary.js`: ```javascript export default class MyExternalImagesLibrary extends ExternalImageLibrary { // ... existing properties ... // UI Style configurations static STYLES = { // Modal overlay styles overlay: { backgroundColor: 'rgba(0,0,0,.7)', position: 'fixed', top: '0', right: '0', bottom: '0', left: '0', zIndex: '1050', display: 'flex', alignItems: 'center', justifyContent: 'center', backdropFilter: 'blur(4px)', fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" }, // Modal container styles modal: { backgroundColor: '#ffffff', borderRadius: '12px', boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', maxWidth: '1000px', width: '90%', display: 'flex', flexDirection: 'column', position: 'relative' }, // Header styles header: { padding: '24px 32px', borderBottom: '1px solid #e5e7eb', display: 'flex', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#f9fafb', borderRadius: '12px 12px 0 0' }, // Content container styles content: { padding: '32px', height: '340px', overflowY: 'auto', overflowX: 'hidden', boxSizing: 'border-box' }, // Grid styles grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '20px', gridAutoRows: '125px' }, // Button styles buttonActive: { padding: '6px 14px', borderRadius: '6px', border: 'none', backgroundColor: '#34c759', color: 'white', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'background-color 0.2s' }, buttonInactive: { padding: '6px 14px', borderRadius: '6px', border: '1px solid #e5e7eb', backgroundColor: 'white', color: '#6b7280', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'all 0.2s' }, // Footer styles footer: { padding: '16px 32px', borderTop: '1px solid #e5e7eb', backgroundColor: '#fef3c7', borderRadius: '0 0 12px 12px', textAlign: 'center' } }; // Sample images data static IMAGES = [ { category: 'nature', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/g05fb9c707080df68b2b7a48884ecfb678ea72bfba6c4b30a3622d77b4e1fc4d686c2fa0ca69ecb08cb93ebe999a2cffd_640.jpeg', title: 'Nature Scene' }, { category: 'abstract', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/g039e37b4b08aab63892fa5bcb069ef5a4c3903ffdfe6a266b65d59c96669e69786dd8db7d6d686af5a93c55b5f59cc21_640.jpeg', title: 'Abstract Art' }, { category: 'digital', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/g865b29f858626ae67bf436c38ea27d23ce34ce2c083b7829929c1ce0f1c7a6f72f2f7654bfe16d837d53df0713b157da_640.jpeg', title: 'Digital Design' }, { category: 'photography', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/gf0fe89ba1310967859765c4634e5fdb5abc15576715a0e8793c2bccdd12fabb435f1f62ebf39c017d6c520c6a0cd0e83_640.jpeg', title: 'Mountain Vista' }, { category: 'nature', src: 'https://rf.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/ge472fe8d47d49d6b4f022364cf352302eff394855af35507d5b92fe30d08e0c8a992246d3d813ff21769fd771fab7c90_640.jpeg', title: 'Ocean' }, { category: 'nature', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/g82fa2ffeb30d400bfa57c908a9716e883b41baf34f6ca09cb0650124ef4f0d10322137d9cfab9ae35aedb1a3d8adfb4e_640.jpeg', title: 'Waterfall' }, { category: 'digital', src: 'https://rf.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/g37c2a939530fa27db0025bc0031514973909782c635b82080f91f5c7c15d473e0b3b7c6d41ef5a20d6370c6f58ae8ff0_640.jpeg', title: 'Lunar Rover' }, { category: 'photography', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/gfd3e800e20a1305512a60feb3cdf5fbc07ac64b8397398a9c12e3eb73ae28805d1ed5cbc941f302ab7186feedd38e4ff_640.jpeg', title: 'Ocean Sunset' } ]; // ... rest of the class ... } ``` ### Image Object Properties | Property | Type | Description | |----------|------|-------------| | `category` | `string` | Filter category (nature, abstract, digital, photography) | | `src` | `string` | Image URL | | `title` | `string` | Image display name and alt text | ::: tip Production Implementation In production environments, replace the static `IMAGES` array with API calls to dynamically fetch images from your backend service or third-party image providers. ::: ## Step 4: Build the Modal UI Structure Implement the core modal creation methods. These methods generate the modal HTML structure and inject it into the page. ### Create Modal Method ```javascript /** * Creates the modal HTML structure and appends it to the document body */ createModal() { const modalHtml = this.generateModalHTML(); const container = document.createElement('div'); container.innerHTML = modalHtml; document.body.appendChild(container); // Store reference to the modal element this.externalLibrary = document.getElementById('externalImagesLibrary'); // Initially hide the modal this.externalLibrary.style.display = 'none'; } /** * Generates the complete modal HTML structure * @returns {string} Complete HTML string for the modal */ generateModalHTML() { return `
${this.generateHeaderHTML()} ${this.generateContentHTML()} ${this.generateFooterHTML()}
`; } ``` ### Style Conversion Helper Add the following utility method that converts JavaScript style objects to inline CSS strings: ```javascript /** * Converts a style object to an inline style string * @param {Object} styleObj - Style object with camelCase properties * @returns {string} Inline CSS style string with kebab-case properties */ styleObjToString(styleObj) { return Object.entries(styleObj) .map(([key, value]) => { // Convert camelCase to kebab-case const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase(); return `${kebabKey}: ${value}`; }) .join('; '); } ``` ::: warning Important The `styleObjToString()` method is essential for converting the `STYLES` object into inline CSS strings. This method is required for proper modal rendering. ::: ### Modal Structure Overview The modal consists of three main sections: 1. **Header**: Title, category filter buttons, and close button 2. **Content**: Scrollable grid of image thumbnails 3. **Footer**: Informational disclaimer ## Step 5: Generate Header with Filters Create the header section with title, filter buttons, and close button. ### Header HTML Generator ```javascript /** * Generates the modal header section HTML * @returns {string} HTML string for the header section */ generateHeaderHTML() { return `

Image Library

${this.generateFilterButtons()}
${this.generateCloseButton()}
`; } ``` ### Filter Buttons Generator ```javascript /** * Generates category filter buttons HTML * @returns {string} HTML string for all filter buttons */ generateFilterButtons() { const categories = [ { id: 'all', label: 'All', active: true }, { id: 'nature', label: 'Nature', active: false }, { id: 'abstract', label: 'Abstract', active: false }, { id: 'digital', label: 'Digital', active: false }, { id: 'photography', label: 'Photography', active: false } ]; return categories.map(cat => ` `).join(''); } ``` ::: tip Category Customization Categories can be added or modified by updating the `categories` array. Ensure your image data contains matching `category` values for proper filtering. ::: ### Close Button Generator ```javascript /** * Generates close button HTML with hover effects * @returns {string} HTML string for the close button */ generateCloseButton() { return ` `; } ``` ## Step 6: Generate Content with Image Grid Create the content section that displays the image thumbnails in a responsive grid. ### Content Container Generator ```javascript /** * Generates the modal content section HTML with image grid * @returns {string} HTML string for the content section */ generateContentHTML() { return `
${this.generateImageThumbnails()}
`; } ``` ### Image Thumbnails Generator ```javascript /** * Generates image thumbnail cards HTML * @returns {string} HTML string for all image thumbnail cards */ generateImageThumbnails() { return MyExternalImagesLibrary.IMAGES.map(image => `
${image.title}

${image.title}

`).join(''); } ``` ### Key Features 1. **Responsive Grid**: Utilizes CSS Grid with `auto-fill` to create a responsive layout that adapts to different screen sizes 2. **Fixed Row Height**: The `gridAutoRows: '125px'` property ensures consistent thumbnail heights across the grid 3. **Hover Effects**: Inline event handlers provide smooth transition animations during user interaction 4. **Data Attributes**: Image metadata (`data-category`) is stored in data attributes for efficient filtering 5. **Overlay Effect**: The title appears on hover with a gradient background overlay for enhanced visual presentation 6. **Object Fit**: The `object-fit: cover` property ensures images fill their containers while maintaining their aspect ratio ## Step 7: Generate Footer with Disclaimer Add a footer section to display important notices or disclaimers. ```javascript /** * Generates the modal footer section HTML with disclaimer notice * @returns {string} HTML string for the footer section */ generateFooterHTML() { return `

⚠️ Please be advised: This popup window is not part of the plugin. It is intended solely for demonstration purposes and can be implemented independently in any desired way.

`; } ``` ## Step 8: Implement Event Handlers Add event listeners to handle user interactions with the modal. ### Attach Event Listeners ```javascript /** * Attaches event listeners to modal elements after creation */ attachEventListeners() { // Close button click handler this.externalLibrary.querySelector('.close') .addEventListener('click', this.cancelAndClose.bind(this)); // Image click handler (using event delegation) this.externalLibrary.addEventListener('click', this.onImageClick.bind(this)); } ``` ### Handle Image Selection ```javascript /** * Handles click events on image thumbnail cards * @param {Event} e - Click event object */ onImageClick(e) { // Check if clicked on thumbnail or any of its children const thumbnail = e.target.closest('.thumbnail'); if (!thumbnail) return; // Get the image element within the thumbnail const img = thumbnail.querySelector('img'); if (!img) return; // Create callback object with image data const imageData = { originalName: img.src.split('/').pop(), width: 600, height: 410, size: 169000, url: img.getAttribute('src'), altText: img.getAttribute('alt') }; // Close modal and execute callback this.close(); this.imageSelectCallback(imageData); } ``` ::: tip Dynamic Image Metadata In production environments, you should fetch actual image dimensions and file sizes from your backend or use the Image API to retrieve real metadata. The example above uses placeholder values for demonstration purposes. ::: ### Handle Modal Closure ```javascript /** * Closes the modal and invokes the cancel callback */ cancelAndClose() { this.close(); this.cancelCallback(); } /** * Closes the modal dialog by hiding it from view */ close() { this.externalLibrary.style.display = 'none'; } ``` ### Event Delegation Benefits Using event delegation by listening to the parent container provides several advantages: * **Improved Performance**: A single event listener replaces multiple individual listeners for each thumbnail * **Simplified Maintenance**: Eliminates the need to dynamically attach and detach listeners * **Future-Proof Implementation**: Automatically handles dynamically added image elements * **Memory Efficiency**: Reduces the memory footprint when managing numerous elements ## Step 9: Implement Category Filtering Add filtering functionality to help users find images by category. ### Initialize Filter Buttons ```javascript /** * Initializes category filter button functionality */ initializeFilters() { const filterButtons = this.externalLibrary.querySelectorAll('.filter-buttons button'); filterButtons.forEach(button => { button.addEventListener('click', (e) => { const category = e.target.getAttribute('data-category'); this.filterImages(category); this.updateActiveButton(e.target); }); }); } ``` ### Filter Images by Category ```javascript /** * Filters displayed images based on the selected category * @param {string} category - Category identifier to filter by (or 'all' for all images) */ filterImages(category) { this.activeCategory = category; const thumbnails = this.externalLibrary.querySelectorAll('.thumbnail'); thumbnails.forEach(thumbnail => { const shouldShow = category === 'all' || thumbnail.getAttribute('data-category') === category; thumbnail.style.display = shouldShow ? 'block' : 'none'; }); } ``` ### Update Button Visual States ```javascript /** * Updates the visual state of category filter buttons * @param {HTMLElement} activeButton - The button element that was clicked and should be marked active */ updateActiveButton(activeButton) { const buttons = this.externalLibrary.querySelectorAll('.filter-buttons button'); buttons.forEach(button => { const isActive = button === activeButton; const styles = isActive ? MyExternalImagesLibrary.STYLES.buttonActive : MyExternalImagesLibrary.STYLES.buttonInactive; // Apply styles Object.assign(button.style, styles); }); } ``` ## Step 10: Register the Extension Create `src/extension.js` to register your image library with the Stripo extension system: ```javascript import {ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; import MyExternalImagesLibrary from './MyExternalImagesLibrary'; export default new ExtensionBuilder() .withExternalImageLibrary(MyExternalImagesLibrary) .build(); ``` ### Extension Registration Explained The `ExtensionBuilder` class provides a fluent API for registering integrations: * **`withExternalImageLibrary()`**: Registers your custom image library implementation * **`build()`**: Constructs and returns the final extension object for the editor ::: tip Multiple Integrations Multiple `.with*()` methods can be chained to register different integrations within a single extension: ```javascript new ExtensionBuilder() .withExternalImageLibrary(MyExternalImagesLibrary) .withExternalVideosLibrary(MyExternalVideosLibrary) .withExternalMergeTagsSelector(MyMergeTagsSelector) .build(); ``` ::: ## Step 11: Run the Development Server Your implementation is now ready for testing. ### Start the Development Server ```bash npm run dev ``` This command will: 1. Start the Vite development server on `http://localhost:3000` 2. Automatically open your default browser 3. Load the Stripo Editor with your image library extension integrated ## Complete Example For a full working example, check out the complete implementation in our [GitHub repository](https://github.com/stripoinc/stripo-plugin-samples/tree/main/extensions/javascript/external-image-library). --- --- url: >- https://plugin.stripo.email/extensions/tutorials/examples/integrations/external-image-library-tab.md --- # Integrate a Custom Tab in the Native Image Gallery ## Overview The External Image Library Tab integration allows you to add a custom tab directly within Stripo's native image gallery interface. Unlike creating a standalone modal dialog, this integration embeds your custom image collection as a seamless tab alongside Stripo's built-in image sources. ### What You'll Build In this tutorial, you'll create a fully functional custom image library tab that: * Integrates directly into Stripo's native gallery as a custom tab * Displays a responsive grid of image thumbnails with hover effects * Supports category-based filtering (All, Nature, Abstract, Digital, and Photography) * Handles image selection with proper callback integration * Provides localization support for multiple languages * Returns enhanced image metadata using the `labels` property ::: tip Available from v3.2.0 The `ExternalImageLibraryTab` interface and enhanced metadata support through the `labels` property are available starting from v3.2.0 of the Stripo Extensions SDK. ::: ::: image-wrap ![](/img/extensions/externalImagesLibraryTab1.png) ::: ::: image-wrap ![](/img/extensions/externalImagesLibraryTab2.png) ::: ### Use Cases * **Seamless User Experience**: Integrate your image library without disrupting the native editor workflow * **Enhanced Metadata**: Store additional image information such as categories or custom tags ## Prerequisites Before starting this tutorial, ensure you have: * Node.js version 22.x or higher installed * A basic understanding of JavaScript ES6+ syntax * Familiarity with the Stripo Extensions SDK * Completed the [Getting Started](/extensions/getting-started) guide ## Understanding the Interface The [ExternalImageLibraryTab](/extensions/reference/integrations/ExternalImageLibrary#externalimagelibrarytab) class must implement two key methods: ### Required Methods ```typescript /** * Returns the localized name of the tab * This name will be displayed as the tab title in the gallery */ getName(): string /** * Called when the tab is opened * Renders content into the provided container */ openImageLibraryTab( container: HTMLElement, onImageSelect: (image: ExternalGalleryImage) => void, selectedNode?: ImmutableHtmlNode ): void ``` ### Method Parameters | Method | Parameter | Type | Description | |--------|-----------|------|-------------| | `openImageLibraryTab` | `container` | `HTMLElement` | DOM container where your tab content should be rendered | | `openImageLibraryTab` | `onImageSelect` | `Function` | Callback to invoke when a user selects an image | | `openImageLibraryTab` | `selectedNode` | `ImmutableHtmlNode` | (Optional) Selected node for which the gallery is being opened | ### ExternalGalleryImage Object When a user selects an image, your implementation must return an object with the following structure: ```typescript { originalName: string; // Image file name width: number; // Image width in pixels height: number; // Image height in pixels sizeBytes: number; // File size in bytes url: string; // Image URL altText: string; // Alt text for accessibility labels?: Record; // Optional metadata } ``` :::tip Enhanced Metadata The optional `labels` property lets you attach extra metadata to an image—such as its category, source, or custom tags. These details will be shown beneath the default image properties like dimensions and file size in the gallery UI. ::: ## Step 1: Project Setup Create your project structure and install dependencies according to the [Getting Started](/extensions/getting-started) guide. Your project directory structure should look like this: ```bash external-image-library-tab/ ├── index.html ├── src/ │ ├── creds.js │ ├── index.js │ ├── extension.js ├── package.json └── vite.config.js ``` ### Install Dependencies Ensure you have the required dependencies in your `package.json`: ```json { "name": "external-image-library-tab", "version": "1.0.0", "description": "External Image Library Tab Extension for Stripo Editor", "scripts": { "dev": "vite", "build": "vite build" }, "dependencies": { "@stripoinc/ui-editor-extensions": "^3.2.0" }, "devDependencies": { "vite": "^7.1.9" } } ``` Run the installation: ```bash npm install ``` ## Step 2: Create the Tab Implementation Class Create a new file `src/MyExternalImageLibraryTab.js` that implements the `ExternalImageLibraryTab` interface: ```javascript import {ExternalImageLibraryTab} from '@stripoinc/ui-editor-extensions'; import {ImageLibraryTabUI} from './ImageLibraryTabUI.js'; /** * External Image Library Tab Implementation * This class implements the ExternalImageLibraryTab interface * and delegates all UI logic to ImageLibraryTabUI */ export default class MyExternalImageLibraryTab extends ExternalImageLibraryTab { constructor() { super(); // Create UI handler instance this.ui = new ImageLibraryTabUI(); } /** * Required method: Returns the localized name of the tab * This name will be displayed as the tab title in the gallery * @returns {string} Localized tab name */ getName() { return this.api.translate('Custom Images'); } /** * Required method: Called when the tab is opened * Delegates rendering to the UI class * @param {HTMLElement} container - DOM container where content should be rendered * @param {Function} onImageSelect - Callback to invoke when an image is selected * @param {ImmutableHtmlNode} selectedNode - (Optional) Selected node for which the gallery is being opened */ openImageLibraryTab(container, onImageSelect, selectedNode) { this.ui.initialize(container, onImageSelect); } } ``` ### Key Components Explained * **Constructor**: Initializes the UI handler class that manages all presentation logic * **getName()**: Returns the tab name. Uses `this.api.translate()` to support localization * **openImageLibraryTab()**: Entry point called by Stripo when the tab is activated. Delegates rendering to the UI class :::tip Separation of Concerns This example follows a clean architecture pattern by separating the interface implementation (Tab class) from the UI logic (UI class). This makes the code more maintainable and testable. ::: ## Step 3: Create the UI Handler Class Create a new file `src/ImageLibraryTabUI.js` that handles all UI rendering and interactions: ```javascript /** * UI Logic and Rendering for Image Library Tab * This class handles all the presentation logic, DOM manipulation, * and user interactions for the custom image library tab. */ export class ImageLibraryTabUI { constructor() { this.container = null; this.imageSelectCallback = () => {}; this.activeCategory = 'all'; } /** * Initializes the UI and renders content in the provided container * @param {HTMLElement} container - DOM container where content should be rendered * @param {Function} onImageSelect - Callback to invoke when an image is selected */ initialize(container, onImageSelect) { this.container = container; this.imageSelectCallback = onImageSelect; this.renderUI(); this.initializeFilters(); this.filterImages('all'); } /** * Renders the complete UI structure inside the container */ renderUI() { const html = this.generateHTML(); this.container.innerHTML = html; this.attachEventListeners(); } } ``` ### Instance Properties | Property | Type | Description | |----------|------|-------------| | `container` | `HTMLElement` | Reference to the DOM container provided by Stripo | | `imageSelectCallback` | `Function` | Callback to invoke when an image is selected | | `activeCategory` | `string` | Currently active filter category | ## Step 4: Define Image Data and Styles Add static properties for image data and UI configuration to `src/ImageLibraryTabUI.js`: ```javascript export class ImageLibraryTabUI { // UI Style configurations static STYLES = { // Container styles container: { padding: '20px', boxSizing: 'border-box', backgroundColor: '#ffffff', fontFamily: '-apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, \'Helvetica Neue\', Arial, sans-serif', }, // Header styles header: { marginBottom: '24px', paddingBottom: '16px', borderBottom: '2px solid #e5e7eb', }, // Filter buttons container filterContainer: { display: 'flex', gap: '8px', marginBottom: '20px', flexWrap: 'wrap', }, // Grid styles grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: '16px', gridAutoRows: '120px', }, // Button styles buttonActive: { padding: '8px 16px', borderRadius: '6px', border: 'none', backgroundColor: '#3b82f6', color: 'white', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'background-color 0.2s', }, buttonInactive: { padding: '8px 16px', borderRadius: '6px', border: '1px solid #d1d5db', backgroundColor: 'white', color: '#6b7280', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'all 0.2s', }, // Notice banner styles notice: { padding: '12px 16px', marginBottom: '20px', backgroundColor: '#fef3c7', borderRadius: '8px', border: '1px solid #fbbf24', }, }; // Sample images data static IMAGES = [ { category: 'nature', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/g05fb9c707080df68b2b7a48884ecfb678ea72bfba6c4b30a3622d77b4e1fc4d686c2fa0ca69ecb08cb93ebe999a2cffd_640.jpeg', title: 'Nature Scene', }, { category: 'abstract', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/g039e37b4b08aab63892fa5bcb069ef5a4c3903ffdfe6a266b65d59c96669e69786dd8db7d6d686af5a93c55b5f59cc21_640.jpeg', title: 'Abstract Art', }, { category: 'digital', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/g865b29f858626ae67bf436c38ea27d23ce34ce2c083b7829929c1ce0f1c7a6f72f2f7654bfe16d837d53df0713b157da_640.jpeg', title: 'Digital Design', }, { category: 'photography', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/gf0fe89ba1310967859765c4634e5fdb5abc15576715a0e8793c2bccdd12fabb435f1f62ebf39c017d6c520c6a0cd0e83_640.jpeg', title: 'Mountain Vista', }, { category: 'nature', src: 'https://rf.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/ge472fe8d47d49d6b4f022364cf352302eff394855af35507d5b92fe30d08e0c8a992246d3d813ff21769fd771fab7c90_640.jpeg', title: 'Ocean', }, { category: 'nature', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/g82fa2ffeb30d400bfa57c908a9716e883b41baf34f6ca09cb0650124ef4f0d10322137d9cfab9ae35aedb1a3d8adfb4e_640.jpeg', title: 'Waterfall', }, { category: 'digital', src: 'https://rf.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/g37c2a939530fa27db0025bc0031514973909782c635b82080f91f5c7c15d473e0b3b7c6d41ef5a20d6370c6f58ae8ff0_640.jpeg', title: 'Lunar Rover', }, { category: 'photography', src: 'https://demo.stripocdn.email/content/guids/CABINET_ec6ac1de70c49219cc55754951562cc72c549fc9e7a7ec9636d3be7a33c392e2/images/gfd3e800e20a1305512a60feb3cdf5fbc07ac64b8397398a9c12e3eb73ae28805d1ed5cbc941f302ab7186feedd38e4ff_640.jpeg', title: 'Ocean Sunset', }, ]; // ... rest of the class ... } ``` ### Image Object Properties | Property | Type | Description | |----------|------|-------------| | `category` | `string` | Filter category (nature, abstract, digital, photography) | | `src` | `string` | Image URL | | `title` | `string` | Image display name and alt text | ::: tip Production Implementation In production environments, replace the static `IMAGES` array with API calls to dynamically fetch images from your backend service or third-party image providers. Consider implementing pagination or lazy loading for better performance with large image collections. ::: ## Step 5: Generate the UI Structure Add methods to generate the complete HTML structure for your tab. Continue editing `src/ImageLibraryTabUI.js`: ```javascript /** * Generates the complete HTML structure * @returns {string} Complete HTML string for the tab content */ generateHTML() { return `
${this.generateNoticeHTML()} ${this.generateHeaderHTML()} ${this.generateFilterButtonsHTML()} ${this.generateImageGridHTML()}
`; } /** * Converts a style object to an inline style string * @param {Object} styleObj - Style object with camelCase properties * @returns {string} Inline CSS style string with kebab-case properties */ styleObjToString(styleObj) { return Object.entries(styleObj) .map(([key, value]) => { // Convert camelCase to kebab-case const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase(); return `${kebabKey}: ${value}`; }) .join('; '); } ``` ### Notice Banner Add an informational banner to explain the custom tab to users: ```javascript /** * Generates notice banner HTML * @returns {string} HTML string for the notice banner */ generateNoticeHTML() { return `

⚠️ Demo Tab: This is a custom tab integrated into the Stripo image gallery. It demonstrates how to add your own image sources.

`; } ``` ### Header Section Create the header with title and description: ```javascript /** * Generates the header section HTML * @returns {string} HTML string for the header section */ generateHeaderHTML() { return `

External Image Library

Select an image from our custom collection

`; } ``` ## Step 6: Generate Filter Buttons Create category filter buttons to help users navigate the image collection: ```javascript /** * Generates category filter buttons HTML * @returns {string} HTML string for filter buttons */ generateFilterButtonsHTML() { const categories = [ {id: 'all', label: 'All', active: true}, {id: 'nature', label: 'Nature', active: false}, {id: 'abstract', label: 'Abstract', active: false}, {id: 'digital', label: 'Digital', active: false}, {id: 'photography', label: 'Photography', active: false}, ]; const buttons = categories.map(cat => ` `).join(''); return `
${buttons}
`; } ``` ::: tip Category Customization Categories can be customized by modifying the `categories` array. Ensure your image data contains matching `category` values for proper filtering functionality. ::: ## Step 7: Generate Image Grid with Thumbnails Create the image grid that displays all available images: ```javascript /** * Generates image grid HTML with thumbnails * @returns {string} HTML string for the image grid */ generateImageGridHTML() { const thumbnails = ImageLibraryTabUI.IMAGES.map(image => `
${image.title}
${image.title}
`).join(''); return `
${thumbnails}
`; } ``` ## Step 8: Implement Event Handlers Add event listeners to handle user interactions: ```javascript /** * Attaches event listeners to interactive elements */ attachEventListeners() { // Use capture phase to ensure we catch the event before child handlers this.container.addEventListener('click', this.onImageClick.bind(this)); } /** * Handles click events on image thumbnail cards * @param {Event} e - Click event object */ onImageClick(e) { const thumbnail = e.target.closest('.thumbnail'); if (!thumbnail) { return; } const img = thumbnail.querySelector('img'); if (!img) { return; } const title = img.getAttribute('data-title') || img.getAttribute('alt') || ''; const category = img.getAttribute('data-category') || ''; // Create enhanced metadata using labels const labels = { category: category, source: 'External Library Tab', title: title, }; // Create callback object with image data and metadata const imageData = { originalName: img.src.split('/').pop() || '', width: 600, height: 410, sizeBytes: 169000, url: img.getAttribute('src') || '', altText: title, labels: labels, // Enhanced metadata (v3.2.0+) }; // Invoke the callback to insert the image into the editor this.imageSelectCallback(imageData); } ``` ### Enhanced Metadata with Labels The `labels` property allows you to attach custom metadata to each image: | Label Key | Example Value | Purpose | |-----------|---------------|---------| | `category` | "nature" | Organize images by type | | `source` | "External Library Tab" | Track image origin | | `title` | "Ocean Sunset" | Store descriptive information | | Custom keys | Any value | Add your own metadata fields | ## Step 9: Implement Category Filtering Add filtering functionality to help users find images by category: ```javascript /** * Initializes category filter button functionality */ initializeFilters() { const filterButtons = this.container.querySelectorAll('.filter-buttons button'); filterButtons.forEach(button => { button.addEventListener('click', (e) => { e.stopPropagation(); // Prevent event from bubbling to image click handler const category = e.target.getAttribute('data-category'); if (category) { this.filterImages(category); this.updateActiveButton(e.target); } }); }); } /** * Filters displayed images based on the selected category * @param {string} category - Category identifier to filter by (or 'all' for all images) */ filterImages(category) { this.activeCategory = category; const thumbnails = this.container.querySelectorAll('.thumbnail'); thumbnails.forEach(thumbnail => { const shouldShow = category === 'all' || thumbnail.getAttribute('data-category') === category; thumbnail.style.display = shouldShow ? 'block' : 'none'; }); } /** * Updates the visual state of category filter buttons * @param {HTMLElement} activeButton - The button element that should be marked active */ updateActiveButton(activeButton) { const buttons = this.container.querySelectorAll('.filter-buttons button'); buttons.forEach(button => { const isActive = button === activeButton; const styles = isActive ? ImageLibraryTabUI.STYLES.buttonActive : ImageLibraryTabUI.STYLES.buttonInactive; // Apply styles Object.assign(button.style, styles); }); } ``` ## Step 10: Register the Extension with Localization Create `src/extension.js` to register your image library tab with localization support: ```javascript import {ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; import MyExternalImageLibraryTab from './MyExternalImageLibraryTab.js'; const extension = new ExtensionBuilder() .withExternalImageLibraryTab(MyExternalImageLibraryTab) .withLocalization({ 'en': { 'Custom Images': 'Custom Images', }, 'uk': { 'Custom Images': 'Власні зображення', }, }) .build(); export default extension; ``` ### Extension Registration Explained The `ExtensionBuilder` provides a fluent API for registering your custom tab: * **`withExternalImageLibraryTab()`**: Registers your custom tab implementation * **`withLocalization()`**: Provides translations for different languages * **`build()`**: Constructs and returns the final extension object ### Localization Structure The localization object maps language codes to translation dictionaries: ```javascript { 'en': { // Language code (English) 'Key': 'Value' // Translation key-value pairs }, 'uk': { // Language code (Ukrainian) 'Key': 'Значення' } } ``` ::: tip Multiple Integrations You can chain multiple `.with*()` methods to register different integrations: ```javascript new ExtensionBuilder() .withExternalImageLibraryTab(MyImageTab) .withExternalVideosLibrary(MyVideoLibrary) .withLocalization({ /* translations */ }) .build(); ``` ::: ## Step 11: Run the Development Server Your implementation is now ready for testing! ### Start the Development Server ```bash npm run dev ``` This command will: 1. Start the Vite development server on `http://localhost:3000` 2. Automatically open your default browser 3. Load the Stripo Editor with your custom image library tab ## Complete Example For a full working example, check out the complete implementation in our [GitHub repository](https://github.com/stripoinc/stripo-plugin-samples/tree/main/extensions/javascript/external-image-library-tab). --- --- url: >- https://plugin.stripo.email/extensions/tutorials/examples/integrations/external-video-library.md --- # Integrate an External Video Library ## Overview The External Video Library integration enables users to browse and select videos from your video hosting service or third-party platforms such as YouTube, Vimeo, or Wistia directly within the Stripo Email Editor. This integration provides a seamless experience for embedding videos in email templates while automatically generating email-safe video thumbnails with play buttons. ### What You'll Build In this tutorial, you'll create a fully functional video library modal that: * Displays a responsive grid of video thumbnails with hover effects * Supports category-based filtering (All, Tutorials, Features, Overview) * Handles video selection with proper callback integration * Returns video data in the format expected by the Stripo editor ::: image-wrap ![](/img/plugin/new/image222.webp){width=1999 height=921} ::: ::: image-wrap ![](/img/plugin/new/image223.webp){width=1999 height=943} ::: ### Use Cases * **Custom Video Platform**: Connect your proprietary video hosting service * **Curated Content**: Provide pre-approved videos for brand consistency * **Third-Party Integration**: Connect to YouTube, Vimeo, Wistia, or other platforms * **Dynamic Content**: Fetch videos from your API or CMS in real-time ## Prerequisites Before starting this tutorial, ensure you have: * Node.js version 22.x or higher installed * Basic understanding of JavaScript ES6+ syntax * Familiarity with the Stripo Extensions SDK ## Understanding the Interface The [ExternalVideosLibrary](/extensions/reference/integrations/ExternalVideosLibrary) class must implement a single method: ```typescript openExternalVideosLibraryDialog( currentVideo: string, onVideoSelectCallback: (video: ExternalGalleryVideo) => void, onCancelCallback: () => void ): void ``` ### Parameters | Parameter | Type | Description | |-------------------------|------------|-------------------------------------------------------------| | `currentVideo` | `string` | Currently selected video URL | | `onVideoSelectCallback` | `Function` | Callback function invoked when a user selects a video | | `onCancelCallback` | `Function` | Callback function invoked when a user cancels the selection | ### ExternalGalleryVideo Object When a user selects a video, your library implementation must return an object with the following structure: ```typescript { originalVideoName: string; // Video title/name originalImageName: string; // Thumbnail image name urlImage: string; // Thumbnail image URL urlVideo: string; // Video URL (YouTube, Vimeo, etc.) hasCustomButton: boolean; // Use custom play button styling altText: string; // Alt text for accessibility } ``` ## Step 1: Project Setup Create your project structure and install dependencies according to the [Getting Started](/extensions/getting-started) guide. Your project directory structure should look like this: ```bash external-video-library/ ├── index.html ├── src/ │ ├── creds.js │ ├── index.js │ └── extension.js ├── package.json └── vite.config.js ``` ## Step 2: Create the Video Library Class Create a new file `src/MyExternalVideoLibrary.js` with the following basic class structure: ```javascript import {ExternalVideosLibrary} from '@stripoinc/ui-editor-extensions'; /** * External Video Library Implementation * This class implements a modal video gallery with filtering capabilities * for the Stripo Email Editor extension system. */ export class MyExternalVideoLibrary extends ExternalVideosLibrary { // Instance properties externalLibrary; videoSelectCallback = () => {}; cancelCallback = () => {}; activeCategory = 'all'; constructor() { super(); this.createModal(); this.attachEventListeners(); this.initializeFilters(); } /** * Required method called by the Stripo editor * Opens the video library modal dialog * @param {ExternalGalleryVideo} currentVideo - Currently selected video (if any) * @param {Function} onVideoSelectCallback - Callback invoked when a video is selected * @param {Function} onCancelCallback - Callback invoked when the modal is cancelled */ openExternalVideosLibraryDialog(currentVideo, onVideoSelectCallback, onCancelCallback) { // Store callbacks this.videoSelectCallback = onVideoSelectCallback; this.cancelCallback = onCancelCallback; // Show modal this.externalLibrary.style.display = 'flex'; // Reset filters to show all videos this.filterVideos('all'); const allButton = this.externalLibrary.querySelector('[data-category="all"]'); if (allButton) { this.updateActiveButton(allButton); } } } ``` ### Key Components Explained * **Instance Properties**: * `externalLibrary`: Reference to the modal DOM element * `videoSelectCallback`: Stores the success callback from the Stripo editor * `cancelCallback`: Stores the cancel callback from the Stripo editor * `activeCategory`: Tracks the currently active filter category * **Constructor**: Initializes the complete modal UI upon class instantiation * **openExternalVideosLibraryDialog**: Required method that performs the following: * Stores the callbacks for later invocation * Displays the modal dialog * Resets filters to display all videos * Updates the active button state ## Step 3: Define Video Data and Styles Add static properties for video data and UI configuration. Continue editing `src/MyExternalVideoLibrary.js`: ```javascript export class MyExternalVideoLibrary extends ExternalVideosLibrary { // ... existing properties ... // UI Style configurations static STYLES = { // Modal overlay styles overlay: { backgroundColor: 'rgba(0,0,0,.7)', position: 'fixed', top: '0', right: '0', bottom: '0', left: '0', zIndex: '1050', display: 'flex', alignItems: 'center', justifyContent: 'center', backdropFilter: 'blur(4px)', fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" }, // Modal container styles modal: { backgroundColor: '#ffffff', borderRadius: '12px', boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', maxWidth: '1000px', width: '90%', display: 'flex', flexDirection: 'column', position: 'relative' }, // Header styles header: { padding: '24px 32px', borderBottom: '1px solid #e5e7eb', display: 'flex', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#f9fafb', borderRadius: '12px 12px 0 0' }, // Content container styles content: { padding: '32px', height: '289px', overflowY: 'auto', overflowX: 'hidden', boxSizing: 'border-box' }, // Grid styles grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '20px', gridAutoRows: '125px' }, // Button styles buttonActive: { padding: '6px 14px', borderRadius: '6px', border: 'none', backgroundColor: '#34c759', color: 'white', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'background-color 0.2s' }, buttonInactive: { padding: '6px 14px', borderRadius: '6px', border: '1px solid #e5e7eb', backgroundColor: 'white', color: '#6b7280', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'all 0.2s' }, // Footer styles footer: { padding: '16px 32px', borderTop: '1px solid #e5e7eb', backgroundColor: '#fef3c7', borderRadius: '0 0 12px 12px', textAlign: 'center' } }; // Sample videos data static VIDEOS = [ { category: 'tutorial', src: 'https://psyrh.stripocdn.email/content/guids/videoImgGuid/images/23121555584914821.png', title: 'Create Easy & Quick Event Reminder Using Template for Food Industry', altText: 'Create Easy & Quick Event Reminder Using Template for Food Industry', urlVideo: 'https://www.youtube.com/watch?v=rNmAdmOMp0Y', hasButton: true }, { category: 'features', src: 'https://psyrh.stripocdn.email/content/guids/videoImgGuid/images/1641555585106902.png', title: 'How to Get Email Mobile & Browser Preview with Stripo', altText: 'How to Get Email Mobile & Browser Preview with Stripo', urlVideo: 'https://www.youtube.com/watch?v=R4NXtC3h598', hasButton: true }, { category: 'overview', src: 'https://psyrh.stripocdn.email/content/guids/videoImgGuid/images/1881555585513981', title: 'Stripo.email editor', altText: 'Stripo.email editor', urlVideo: 'https://www.youtube.com/watch?v=ryqOEPk51Lg', hasButton: false }, { category: 'tutorial', src: 'https://psyrh.stripocdn.email/content/guids/videoImgGuid/images/24481555585355917', title: 'How to Add Menu in Email with Stripo', altText: 'How to Add Menu in Email with Stripo', urlVideo: 'https://www.youtube.com/watch?v=XPFWthaa35Q', hasButton: false } ]; // ... rest of the class ... } ``` ### Video Object Properties | Property | Type | Description | |----------|------|-------------| | `category` | `string` | Filter category (tutorial, features, overview) | | `src` | `string` | Thumbnail image URL | | `title` | `string` | Video display name | | `altText` | `string` | Alt text for accessibility | | `urlVideo` | `string` | YouTube, Vimeo, or other video URL | | `hasButton` | `boolean` | Whether to show custom play button overlay | ::: tip Production Implementation In production environments, replace the static `VIDEOS` array with API calls to fetch videos dynamically from your backend service. ::: ## Step 4: Build the Modal UI Structure Implement the core modal creation methods. These methods generate the modal HTML structure and inject it into the page. ### Create Modal Method ```javascript /** * Creates the modal HTML structure and appends it to the document body */ createModal() { const modalHtml = this.generateModalHTML(); const container = document.createElement('div'); container.innerHTML = modalHtml; document.body.appendChild(container); // Store reference to the modal element this.externalLibrary = document.getElementById('externalVideoLibrary'); // Initially hide the modal this.externalLibrary.style.display = 'none'; } /** * Generates the complete modal HTML structure * @returns {string} Complete HTML string for the modal */ generateModalHTML() { return `
${this.generateHeaderHTML()} ${this.generateContentHTML()} ${this.generateFooterHTML()}
`; } ``` ### Style Conversion Helper Add this utility method that converts JavaScript style objects to inline CSS strings: ```javascript /** * Converts a style object to an inline style string * @param {Object} styleObj - Style object with camelCase properties * @returns {string} Inline CSS style string with kebab-case properties */ styleObjToString(styleObj) { return Object.entries(styleObj) .map(([key, value]) => { // Convert camelCase to kebab-case const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase(); return `${kebabKey}: ${value}`; }) .join('; '); } ``` ::: warning Important The `styleObjToString()` method is essential for converting the `STYLES` object into inline CSS strings. This method is required for proper modal rendering. ::: ### Modal Structure Overview The modal consists of three main sections: 1. **Header**: Title, category filter buttons, and close button 2. **Content**: Scrollable grid of video thumbnails 3. **Footer**: Informational disclaimer ## Step 5: Generate Header with Filters Create the header section with title, filter buttons, and close button. ### Header HTML Generator ```javascript /** * Generates the modal header section HTML * @returns {string} HTML string for the header section */ generateHeaderHTML() { return `

Video Library

${this.generateFilterButtons()}
${this.generateCloseButton()}
`; } ``` ### Filter Buttons Generator ```javascript /** * Generates category filter buttons HTML * @returns {string} HTML string for all filter buttons */ generateFilterButtons() { const categories = [ { id: 'all', label: 'All', active: true }, { id: 'tutorial', label: 'Tutorials', active: false }, { id: 'features', label: 'Features', active: false }, { id: 'overview', label: 'Overview', active: false } ]; return categories.map(cat => ` `).join(''); } ``` ::: tip Category Customization Categories can be added or modified by updating the `categories` array. Ensure that your video data contains matching `category` values for proper filtering. ::: ### Close Button Generator ```javascript /** * Generates close button HTML with hover effects * @returns {string} HTML string for the close button */ generateCloseButton() { return ` `; } ``` ## Step 6: Generate Content with Video Grid Create the content section that displays the video thumbnails in a responsive grid. ### Content Container Generator ```javascript /** * Generates the modal content section HTML with video grid * @returns {string} HTML string for the content section */ generateContentHTML() { return `
${this.generateVideoThumbnails()}
`; } ``` ### Video Thumbnails Generator ```javascript /** * Generates video thumbnail cards HTML * @returns {string} HTML string for all video thumbnail cards */ generateVideoThumbnails() { return MyExternalVideoLibrary.VIDEOS.map(video => `
${video.title}

${video.title}

`).join(''); } ``` ### Key Features 1. **Responsive Grid**: Utilizes CSS Grid with `auto-fill` to create a responsive layout that adapts to different screen sizes 2. **Fixed Row Height**: The `gridAutoRows: '125px'` property ensures consistent thumbnail heights across the grid 3. **Hover Effects**: Inline event handlers provide smooth transition animations on user interaction 4. **Data Attributes**: Video metadata (`data-url-video`, `data-has-button`) is stored in data attributes for efficient retrieval 5. **Play Button Overlay**: SVG play icon with gradient background overlay for enhanced visual presentation 6. **Text Truncation**: Automatic ellipsis handling prevents long video titles from breaking the layout ## Step 7: Generate Footer with Disclaimer Add a footer section to display important notices or disclaimers. ```javascript /** * Generates the modal footer section HTML with disclaimer notice * @returns {string} HTML string for the footer section */ generateFooterHTML() { return `

⚠️ Notice: This popup window is not part of the plugin. It is intended solely for demonstration purposes and can be implemented independently in any desired way.

`; } ``` ## Step 8: Implement Event Handlers Add event listeners to handle user interactions with the modal. ### Attach Event Listeners ```javascript /** * Attaches event listeners to modal elements after creation */ attachEventListeners() { // Close button click handler this.externalLibrary.querySelector('.close') .addEventListener('click', this.cancelAndClose.bind(this)); // Video click handler (using event delegation) this.externalLibrary.addEventListener('click', this.onVideoClick.bind(this)); } ``` ### Handle Video Selection ```javascript /** * Handles click events on video thumbnail cards * @param {Event} e - Click event object */ onVideoClick(e) { // Check if clicked on thumbnail or any of its children const thumbnail = e.target.closest('.thumbnail'); if (!thumbnail) return; // Get the image element within the thumbnail const img = thumbnail.querySelector('img'); if (!img) return; // Create callback object with video data const videoData = { originalVideoName: img.getAttribute('alt'), originalImageName: img.getAttribute('alt'), urlImage: img.getAttribute('src'), urlVideo: img.getAttribute('data-url-video'), hasCustomButton: img.getAttribute('data-has-button') === 'true', altText: img.getAttribute('alt') }; // Close modal and execute callback this.close(); this.videoSelectCallback(videoData); } ``` ### Handle Modal Closure ```javascript /** * Closes the modal and invokes the cancel callback */ cancelAndClose() { this.close(); this.cancelCallback(); } /** * Closes the modal dialog by hiding it from view */ close() { this.externalLibrary.style.display = 'none'; } ``` ### Event Delegation Benefits Using event delegation by listening on the parent container provides several advantages: * **Improved Performance**: A single event listener replaces multiple individual listeners for each thumbnail * **Simplified Maintenance**: Eliminates the need to attach and detach listeners dynamically * **Future-Proof Implementation**: Automatically handles dynamically added video elements * **Memory Efficiency**: Reduces memory footprint when managing numerous elements ## Step 9: Implement Category Filtering Add filtering functionality to help users find videos by category. ### Initialize Filter Buttons ```javascript /** * Initializes category filter button functionality */ initializeFilters() { const filterButtons = this.externalLibrary.querySelectorAll('.filter-buttons button'); filterButtons.forEach(button => { button.addEventListener('click', (e) => { const category = e.target.getAttribute('data-category'); this.filterVideos(category); this.updateActiveButton(e.target); }); }); } ``` ### Filter Videos by Category ```javascript /** * Filters displayed videos based on the selected category * @param {string} category - Category identifier to filter by (or 'all' for all videos) */ filterVideos(category) { this.activeCategory = category; const thumbnails = this.externalLibrary.querySelectorAll('.thumbnail'); thumbnails.forEach(thumbnail => { const shouldShow = category === 'all' || thumbnail.getAttribute('data-category') === category; thumbnail.style.display = shouldShow ? 'block' : 'none'; }); } ``` ### Update Button Visual States ```javascript /** * Updates the visual state of category filter buttons * @param {HTMLElement} activeButton - The button element that was clicked and should be marked active */ updateActiveButton(activeButton) { const buttons = this.externalLibrary.querySelectorAll('.filter-buttons button'); buttons.forEach(button => { const isActive = button === activeButton; const styles = isActive ? MyExternalVideoLibrary.STYLES.buttonActive : MyExternalVideoLibrary.STYLES.buttonInactive; // Apply styles Object.assign(button.style, styles); }); } ``` ## Step 10: Register the Extension Create `src/extension.js` to register your video library with the Stripo extension system: ```javascript import {ExtensionBuilder} from "@stripoinc/ui-editor-extensions"; import {MyExternalVideoLibrary} from "./MyExternalVideoLibrary"; export default new ExtensionBuilder() .withExternalVideosLibrary(MyExternalVideoLibrary) .build(); ``` ### Extension Registration Explained The `ExtensionBuilder` class provides a fluent API for registering integrations: * **`withExternalVideosLibrary()`**: Registers your custom video library implementation * **`build()`**: Constructs the final extension object for the editor ::: tip Multiple Integrations Multiple `.with*()` methods can be chained to register different integrations within a single extension: ```javascript new ExtensionBuilder() .withExternalVideosLibrary(MyExternalVideoLibrary) .withExternalImageLibrary(MyExternalImageLibrary) .withExternalMergeTagsSelector(MyMergeTagsSelector) .build(); ``` ::: ## Step 11: Run the Development Server Your implementation is now ready for testing. ### Start the Development Server ```bash npm run dev ``` This command will: 1. Start the Vite development server on `http://localhost:3000` 2. Automatically open your default browser 3. Load the Stripo editor with your video library extension integrated ## Complete Example For a full working example, check out the complete implementation in our [GitHub repository](https://github.com/stripoinc/stripo-plugin-samples/tree/main/extensions/javascript/external-video-library). --- --- url: >- https://plugin.stripo.email/extensions/tutorials/examples/integrations/external-smart-elements-library.md --- # Integrate an External Smart Elements Library ## Overview The External Smart Elements Library integration enables users to browse and select dynamic, data-driven content elements from your product catalog or third-party sources directly within the Stripo Email Editor. Smart elements are pre-configured, reusable components that include dynamic content such as product recommendations, personalized offers, or any complex HTML structures that adapt based on data. ### What You'll Build In this tutorial, you'll create a fully functional smart elements library modal that: * Displays a responsive grid of product cards with images, prices, and ratings * Supports category-based filtering (All, Electronics, Accessories, Fitness, Home) * Handles element selection with proper callback integration * Returns smart element data in the format expected by the Stripo editor ::: image-wrap ![](/img/plugin/new/image209.webp){width=1999 height=858} ::: ::: image-wrap ![](/img/plugin/new/image210.webp){width=1999 height=965} ::: ::: image-wrap ![](/img/plugin/new/image211.webp){width=1452 height=1304} ::: ::: image-wrap ![](/img/plugin/new/image212.webp){width=1999 height=965} ::: ### Use Cases * **Product Catalogs**: Connect to your e-commerce platform to display products * **Dynamic Content**: Provide personalized product recommendations * **Third-Party Integration**: Connect to platforms like Shopify, WooCommerce, or custom APIs * **Data-Driven Templates**: Create templates that adapt based on user data or behavior3 * **Personalization**: Insert context-aware content blocks with merge tags ## Prerequisites Before starting this tutorial, ensure you have: * Node.js version 22.x or higher installed * Basic understanding of JavaScript ES6+ syntax * Familiarity with the Stripo Extensions SDK ## Understanding the Interface The [ExternalSmartElementsLibrary](/extensions/reference/integrations/ExternalSmartElementsLibrary) class must implement a single method: ```typescript openSmartElementsLibrary( onDataSelectCallback: (smartElement: ExternalSmartElement) => void, onCancelCallback: () => void ): void ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `onDataSelectCallback` | `Function` | Callback function invoked when a user selects an element | | `onCancelCallback` | `Function` | Callback function invoked when a user cancels the selection | ### ExternalSmartElement Object When a user selects an element, your library implementation must return an object with string key-value pairs: ```typescript type ExternalSmartElement = Record ``` **Common Properties** (customizable based on your needs): ```typescript { p_name: string; // Product name p_price: string; // Product price (e.g., "$89.99") p_image: string; // Product image URL // ... any custom properties } ``` ::: tip Property Names Property names starting with `p_` are commonly used for product-related smart elements, but you can define any property names that suit your use case. All values must be strings. ::: ## Step 1: Project Setup Create your project structure and install dependencies according to the [Getting Started](/extensions/getting-started) guide. Your project directory structure should look like this: ```bash external-smart-elements/ ├── index.html ├── src/ │ ├── creds.js │ ├── index.js │ └── extension.js ├── package.json └── vite.config.js ``` ## Step 2: Create the Smart Elements Library Class Create a new file `src/MyExternalSmartElementsLibrary.js` with the following basic class structure: ```javascript import {ExternalSmartElementsLibrary} from '@stripoinc/ui-editor-extensions'; /** * External Smart Elements Library Implementation * This class implements a modal product gallery with filtering capabilities * for the Stripo Email Editor extension system. */ export class MyExternalSmartElementsLibrary extends ExternalSmartElementsLibrary { // Instance properties externalLibrary; dataSelectCallback = () => {}; cancelCallback = () => {}; activeCategory = 'all'; constructor() { super(); this.createModal(); this.attachEventListeners(); this.initializeFilters(); } /** * Required method called by the Stripo editor * Opens the smart elements library modal dialog * @param {Function} onDataSelectCallback - Callback invoked when an element is selected * @param {Function} onCancelCallback - Callback invoked when the modal is cancelled */ openSmartElementsLibrary(onDataSelectCallback, onCancelCallback) { // Store callbacks this.dataSelectCallback = onDataSelectCallback; this.cancelCallback = onCancelCallback; // Show modal this.externalLibrary.style.display = 'flex'; // Reset filters to show all products this.filterProducts('all'); const allButton = this.externalLibrary.querySelector('[data-category="all"]'); if (allButton) { this.updateActiveButton(allButton); } } } ``` ### Key Components Explained * **Instance Properties**: * `externalLibrary`: Reference to the modal DOM element * `dataSelectCallback`: Stores the success callback from the Stripo editor * `cancelCallback`: Stores the cancel callback from the Stripo editor * `activeCategory`: Tracks the currently active filter category * **Constructor**: Initializes the complete modal UI when the class is instantiated * **openSmartElementsLibrary**: Required method that: * Stores the callbacks for later invocation * Displays the modal dialog * Resets filters to display all products * Updates the active button state ## Step 3: Define Smart Elements Data and Styles Add static properties for product data and UI configuration. Continue editing `src/MyExternalSmartElementsLibrary.js`: ```javascript export class MyExternalSmartElementsLibrary extends ExternalSmartElementsLibrary { // ... existing properties ... // UI Style configurations static STYLES = { // Modal overlay styles overlay: { backgroundColor: 'rgba(0,0,0,.7)', position: 'fixed', top: '0', right: '0', bottom: '0', left: '0', zIndex: '1050', display: 'flex', alignItems: 'center', justifyContent: 'center', backdropFilter: 'blur(4px)', fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" }, // Modal container styles modal: { backgroundColor: '#ffffff', borderRadius: '12px', boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', maxWidth: '1000px', width: '90%', display: 'flex', flexDirection: 'column', position: 'relative' }, // Header styles header: { padding: '24px 32px', borderBottom: '1px solid #e5e7eb', display: 'flex', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#f9fafb', borderRadius: '12px 12px 0 0' }, // Content container styles content: { padding: '32px', height: '731px', overflowY: 'auto', overflowX: 'hidden', boxSizing: 'border-box' }, // Grid styles grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: '24px' }, // Button styles buttonActive: { padding: '6px 14px', borderRadius: '6px', border: 'none', backgroundColor: '#34c759', color: 'white', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'background-color 0.2s' }, buttonInactive: { padding: '6px 14px', borderRadius: '6px', border: '1px solid #e5e7eb', backgroundColor: 'white', color: '#6b7280', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'all 0.2s' }, // Footer styles footer: { padding: '16px 32px', borderTop: '1px solid #e5e7eb', backgroundColor: '#fef3c7', borderRadius: '0 0 12px 12px', textAlign: 'center' } }; // Sample smart elements data static SMART_ELEMENTS = [ { category: 'electronics', p_name: 'Wireless Headphones', p_price: '$89.99', p_original_price: '$129.99', p_image: 'https://rf.stripocdn.email/content/guids/CABINET_6832604a6dbd8f35c4c45dc999af6fe2144259d656ce5a5ea76e6969ed796bbd/images/gc0859fd762dc386caf67532ca5d9b968b19ba37572e19b72126eb421bee4adfc410dc64eea3dee23cf33c3da5ae06b88_640.jpeg', p_rating: '4.5', p_discount: '31% OFF' }, { category: 'electronics', p_name: 'Smart Watch Pro', p_price: '$249.00', p_original_price: '$299.00', p_image: 'https://rf.stripocdn.email/content/guids/CABINET_6832604a6dbd8f35c4c45dc999af6fe2144259d656ce5a5ea76e6969ed796bbd/images/g5b54a78c579f1641216bab7b119c28b0b3dfa50ab44655c45039c8ac164e6d1835ad29ae242af635a8efe74a03f636a0_640.jpeg', p_rating: '4.8', p_discount: '17% OFF' }, { category: 'accessories', p_name: 'Premium Leather Case', p_price: '$39.99', p_original_price: '$59.99', p_image: 'https://rf.stripocdn.email/content/guids/CABINET_6832604a6dbd8f35c4c45dc999af6fe2144259d656ce5a5ea76e6969ed796bbd/images/gc7a4da5fc1d4a3c14ca8964200ede6290826a825cea3dc704d47db7c9938b64c099879d2feeebfb24f3ae749d85736f1_640.png', p_rating: '4.2', p_discount: '33% OFF' }, { category: 'fitness', p_name: 'Yoga Mat Pro', p_price: '$45.00', p_original_price: '$65.00', p_image: 'https://rf.stripocdn.email/content/guids/CABINET_6832604a6dbd8f35c4c45dc999af6fe2144259d656ce5a5ea76e6969ed796bbd/images/g3d9ab12046aedd0dd772da9bea9768d5fba2840db28046cf73de1f1bded09ad666ef5788d4812a07ad8cfa531487251f_640.jpeg', p_rating: '4.7', p_discount: '31% OFF' }, { category: 'fitness', p_name: 'Resistance Bands Set', p_price: '$29.99', p_original_price: '$39.99', p_image: 'https://rf.stripocdn.email/content/guids/CABINET_6832604a6dbd8f35c4c45dc999af6fe2144259d656ce5a5ea76e6969ed796bbd/images/g939d0a14c9627c0476e3b6cdbee39819532d8823d6e23a3c2e6c651e4466402be0ea7a0384f1cd8e15d04fa52b27fa46_640.jpeg', p_rating: '4.6', p_discount: '25% OFF' }, { category: 'home', p_name: 'Smart LED Bulb', p_price: '$19.99', p_original_price: '$29.99', p_image: 'https://rf.stripocdn.email/content/guids/CABINET_6832604a6dbd8f35c4c45dc999af6fe2144259d656ce5a5ea76e6969ed796bbd/images/g2b88c1f3019297a862fe221399e7cc8a69a6e0549a7d280cdbbef815ed22d0c5b9beed9e477ca16497218e9670c152e1_640.jpeg', p_rating: '4.4', p_discount: '33% OFF' } ]; // ... rest of the class ... } ``` ### Smart Element Object Properties | Property | Type | Description | |----------|------|-------------| | `category` | `string` | Filter category (electronics, accessories, fitness, home) | | `p_name` | `string` | Product name | | `p_price` | `string` | Product price | | `p_original_price` | `string` | Original price before discount | | `p_image` | `string` | Product image URL | | `p_rating` | `string` | Product rating (0-5) | | `p_discount` | `string` | Discount label (optional) | ::: tip Production Implementation In production environments, replace the static `SMART_ELEMENTS` array with API calls to dynamically fetch products from your backend service, e-commerce platform, or product recommendation engine. ::: ## Step 4: Build the Modal UI Structure Implement the core modal creation methods. These methods generate the modal HTML structure and inject it into the page. ### Create Modal Method ```javascript /** * Creates the modal HTML structure and appends it to the document body */ createModal() { const modalHtml = this.generateModalHTML(); const container = document.createElement('div'); container.innerHTML = modalHtml; document.body.appendChild(container); // Store reference to the modal element this.externalLibrary = document.getElementById('externalSmartElementsLibrary'); // Initially hide the modal this.externalLibrary.style.display = 'none'; } /** * Generates the complete modal HTML structure * @returns {string} Complete HTML string for the modal */ generateModalHTML() { return `
${this.generateHeaderHTML()} ${this.generateContentHTML()} ${this.generateFooterHTML()}
`; } ``` ### Style Conversion Helper Add the following utility method that converts JavaScript style objects to inline CSS strings: ```javascript /** * Converts a style object to an inline style string * @param {Object} styleObj - Style object with camelCase properties * @returns {string} Inline CSS style string with kebab-case properties */ styleObjToString(styleObj) { return Object.entries(styleObj) .map(([key, value]) => { // Convert camelCase to kebab-case const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase(); return `${kebabKey}: ${value}`; }) .join('; '); } ``` ::: warning Important The `styleObjToString()` method is essential for converting the `STYLES` object into inline CSS strings. This method is required for proper modal rendering. ::: ### Modal Structure Overview The modal consists of three main sections: 1. **Header**: Title, category filter buttons, and close button 2. **Content**: Scrollable grid of product cards 3. **Footer**: Informational disclaimer ## Step 5: Generate Header with Filters Create the header section with title, filter buttons, and close button. ### Header HTML Generator ```javascript /** * Generates the modal header section HTML * @returns {string} HTML string for the header section */ generateHeaderHTML() { return `

Smart Elements Library

${this.generateFilterButtons()}
${this.generateCloseButton()}
`; } ``` ### Filter Buttons Generator ```javascript /** * Generates category filter buttons HTML * @returns {string} HTML string for all filter buttons */ generateFilterButtons() { const categories = [ { id: 'all', label: 'All', active: true }, { id: 'electronics', label: 'Electronics', active: false }, { id: 'accessories', label: 'Accessories', active: false }, { id: 'fitness', label: 'Fitness', active: false }, { id: 'home', label: 'Home', active: false } ]; return categories.map(cat => ` `).join(''); } ``` ::: tip Category Customization Categories can be added or modified by updating the `categories` array. Ensure your smart elements data contains matching `category` values for proper filtering. ::: ### Close Button Generator ```javascript /** * Generates close button HTML with hover effects * @returns {string} HTML string for the close button */ generateCloseButton() { return ` `; } ``` ## Step 6: Generate Content with Product Grid Create the content section that displays the product cards in a responsive grid. ### Content Container Generator ```javascript /** * Generates the modal content section HTML with product grid * @returns {string} HTML string for the content section */ generateContentHTML() { return `
${this.generateProductCards()}
`; } ``` ### Product Cards Generator ```javascript /** * Generates product card HTML * @returns {string} HTML string for all product cards */ generateProductCards() { return MyExternalSmartElementsLibrary.SMART_ELEMENTS.map(element => `
${element.p_name} ${element.p_discount ? ` ${element.p_discount} ` : ''}

${element.p_name}

${this.generateStarRating(parseFloat(element.p_rating))} (${element.p_rating})
${element.p_price} ${element.p_original_price}
`).join(''); } ``` ### Star Rating Generator ```javascript /** * Generates star rating HTML * @param {number} rating - Rating value (0-5) * @returns {string} HTML string for star rating */ generateStarRating(rating) { const fullStars = Math.floor(rating); const hasHalfStar = rating % 1 !== 0; const emptyStars = 5 - Math.ceil(rating); let stars = ''; // Full stars for (let i = 0; i < fullStars; i++) { stars += ''; } // Half star if (hasHalfStar) { stars += ''; } // Empty stars for (let i = 0; i < emptyStars; i++) { stars += ''; } return `
${stars}
`; } ``` ### Key Features 1. **Responsive Grid**: Utilizes CSS Grid with `auto-fill` to create a responsive layout that adapts to different screen sizes 2. **Product Cards**: Each card displays product image, name, rating, price, and discount badge 3. **Hover Effects**: Inline event handlers provide smooth transition animations during user interaction 4. **Data Attributes**: Product metadata is stored in data attributes for efficient retrieval 5. **Discount Badges**: Conditionally rendered discount labels positioned absolutely 6. **Star Ratings**: Dynamic star rating visualization based on numeric rating value ## Step 7: Generate Footer with Disclaimer Add a footer section to display important notices or disclaimers. ```javascript /** * Generates the modal footer section HTML with disclaimer notice * @returns {string} HTML string for the footer section */ generateFooterHTML() { return `

⚠️ Notice: This popup window is not part of the plugin. It is intended solely for demonstration purposes and can be implemented independently in any desired way.

`; } ``` ## Step 8: Implement Event Handlers Add event listeners to handle user interactions with the modal. ### Attach Event Listeners ```javascript /** * Attaches event listeners to modal elements after creation */ attachEventListeners() { // Close button click handler this.externalLibrary.querySelector('.close') .addEventListener('click', this.cancelAndClose.bind(this)); // Product card click handler (using event delegation) this.externalLibrary.addEventListener('click', this.onProductClick.bind(this)); } ``` ### Handle Product Selection ```javascript /** * Handles click events on product cards * @param {Event} e - Click event object */ onProductClick(e) { // Check if clicked on product card or any of its children const productCard = e.target.closest('.product-card'); if (!productCard) return; // Create callback object with product data // Note: All values must be strings as per ExternalSmartElement type const smartElementData = { category: productCard.getAttribute('data-category'), p_name: productCard.getAttribute('data-name'), p_price: productCard.getAttribute('data-price'), p_image: productCard.getAttribute('data-image'), p_original_price: productCard.getAttribute('data-original-price'), p_rating: productCard.getAttribute('data-rating'), p_discount: productCard.getAttribute('data-discount') || '' }; // Close modal and execute callback this.close(); this.dataSelectCallback(smartElementData); } ``` ::: tip Data Format The `ExternalSmartElement` type requires all values to be strings. When storing numeric data like ratings in data attributes, ensure they remain as strings. The consumer of the data can parse them as needed. ::: ### Handle Modal Closure ```javascript /** * Closes the modal and invokes the cancel callback */ cancelAndClose() { this.close(); this.cancelCallback(); } /** * Closes the modal dialog by hiding it from view */ close() { this.externalLibrary.style.display = 'none'; } ``` ### Event Delegation Benefits Using event delegation by listening to the parent container provides several advantages: * **Improved Performance**: A single event listener replaces multiple individual listeners for each card * **Simplified Maintenance**: Eliminates the need to dynamically attach and detach listeners * **Future-Proof Implementation**: Automatically handles dynamically added product elements * **Memory Efficiency**: Reduces the memory footprint when managing numerous elements ## Step 9: Implement Category Filtering Add filtering functionality to help users find products by category. ### Initialize Filter Buttons ```javascript /** * Initializes category filter button functionality */ initializeFilters() { const filterButtons = this.externalLibrary.querySelectorAll('.filter-buttons button'); filterButtons.forEach(button => { button.addEventListener('click', (e) => { const category = e.target.getAttribute('data-category'); this.filterProducts(category); this.updateActiveButton(e.target); }); }); } ``` ### Filter Products by Category ```javascript /** * Filters displayed products based on the selected category * @param {string} category - Category identifier to filter by (or 'all' for all products) */ filterProducts(category) { this.activeCategory = category; const productCards = this.externalLibrary.querySelectorAll('.product-card'); productCards.forEach(card => { const shouldShow = category === 'all' || card.getAttribute('data-category') === category; card.style.display = shouldShow ? 'flex' : 'none'; }); } ``` ### Update Button Visual States ```javascript /** * Updates the visual state of category filter buttons * @param {HTMLElement} activeButton - The button element that was clicked and should be marked active */ updateActiveButton(activeButton) { const buttons = this.externalLibrary.querySelectorAll('.filter-buttons button'); buttons.forEach(button => { const isActive = button === activeButton; const styles = isActive ? MyExternalSmartElementsLibrary.STYLES.buttonActive : MyExternalSmartElementsLibrary.STYLES.buttonInactive; // Apply styles Object.assign(button.style, styles); }); } ``` ## Step 10: Register the Extension Create `src/extension.js` to register your smart elements library with the Stripo extension system: ```javascript import {ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; import {MyExternalSmartElementsLibrary} from './MyExternalSmartElementsLibrary'; export default new ExtensionBuilder() .withExternalSmartElementsLibrary(MyExternalSmartElementsLibrary) .build(); ``` ### Extension Registration Explained The `ExtensionBuilder` class provides a fluent API for registering integrations: * **`withExternalSmartElementsLibrary()`**: Registers your custom smart elements library implementation * **`build()`**: Constructs and returns the final extension object for the editor ::: tip Multiple Integrations Multiple `.with*()` methods can be chained to register different integrations within a single extension: ```javascript new ExtensionBuilder() .withExternalSmartElementsLibrary(MyExternalSmartElementsLibrary) .withExternalImageLibrary(MyExternalImageLibrary) .withExternalVideosLibrary(MyExternalVideosLibrary) .build(); ``` ::: ## Step 11: Run the Development Server Your implementation is now ready for testing. ### Start the Development Server ```bash npm run dev ``` This command will: 1. Start the Vite development server on `http://localhost:3000` 2. Automatically open your default browser 3. Load the Stripo Editor with your smart elements library extension integrated ## Complete Example For a full working example, check out the complete implementation in our [GitHub repository](https://github.com/stripoinc/stripo-plugin-samples/tree/main/extensions/javascript/external-smart-elements). --- --- url: >- https://plugin.stripo.email/extensions/tutorials/examples/integrations/external-merge-tags-selector.md --- # Integrate an External Merge Tags Selector ## Overview The External Merge Tags Selector integration enables users to browse and select dynamic merge tags from your customer data platform, CRM, or custom data sources directly within the Stripo Email Editor. Merge tags are placeholders that get replaced with actual customer data when emails are sent, enabling personalized email content at scale. ### What You'll Build In this tutorial, you'll create a fully functional merge tags selector modal that: * Displays a responsive grid of merge tag cards with labels, values, descriptions, and previews * Supports category-based filtering (All, Personal, Contact, Company, Date/Time, Custom) * Handles merge tag selection with proper callback integration * Shows real-time preview values for each merge tag * Detects and displays module context with a visual badge indicator * Returns merge tag data in the format expected by the Stripo editor ::: image-wrap ![](/img/plugin/new/image213.webp){width=1999 height=971} ::: ::: image-wrap ![](/img/plugin/new/image214.webp){width=1916 height=1056} ::: ::: image-wrap ![](/img/plugin/new/image215.webp){width=1070 height=1032} ::: ### Use Cases * **CRM Integration**: Connect to Salesforce, HubSpot, or other CRM systems to access contact fields * **Customer Data Platforms**: Integrate with Segment, mParticle, or custom CDP solutions * **E-commerce Platforms**: Access customer purchase history, cart data, and product recommendations * **Marketing Automation**: Connect to Mailchimp, SendGrid, or custom email service providers * **Custom Data Sources**: Fetch merge tags from your API or internal systems * **Multi-System Integration**: Combine merge tags from multiple data sources in a single interface ## Prerequisites Before starting this tutorial, ensure you have: * Node.js version 22.x or higher installed * Basic understanding of JavaScript ES6+ syntax * Familiarity with the Stripo Extensions SDK * Understanding of how merge tags work in email marketing ## Understanding the Interface The External Merge Tags integration requires two key components: ### 1. Custom UI Element Create a custom UI element that extends `UIElement` to replace the default merge tags selector: ```typescript class MergeTagsUiElement extends UIElement { getId(): string; // Unique element ID getTemplate(): string; // HTML template for the button onRender(container: HTMLElement): void; // Setup event listeners onDestroy(): void; // Cleanup onAttributeUpdated(name: string, value: any): void; // Handle attribute changes } ``` ### 2. UI Element Tag Registry Register your custom UI element to replace the default merge tags selector: ```typescript class ExtensionTagRegistry extends UIElementTagRegistry { registerUiElements(uiElementsTagsMap: Record): void; } ``` ### Merge Tag Object Structure When a user selects a merge tag, your implementation must return an object with the following structure: ```typescript { value: string; // The merge tag value (e.g., "*|FNAME|*") label: string; // Human-readable label (e.g., "First Name") } ``` ::: tip Merge Tag Formats Merge tags can follow different formats depending on your email service provider: * Mailchimp: `*|FIELD|*` * Campaign Monitor: `%%field%%` * Custom: Any format your system supports ::: ## Step 1: Project Setup Create your project structure and install dependencies according to the [Getting Started](/extensions/getting-started) guide. Your project directory structure should look like this: ```bash external-merge-tags/ ├── index.html ├── src/ │ ├── creds.js │ ├── index.js │ ├── extension.js ├── package.json └── vite.config.js ``` ## Step 2: Create the Merge Tags Library Class Create a new file `src/MyExternalMergeTagsLibrary.js` with the following basic class structure: ```javascript /** * External Merge Tags Library Implementation * This class implements a modal merge tags selector with filtering capabilities * for the Stripo Email Editor extension system. */ export class MyExternalMergeTagsLibrary { // Instance properties externalLibrary; selectedMergetag = null; dataSelectCallback = () => {}; activeCategory = 'all'; isModule = false; constructor() { this.createModal(); this.attachEventListeners(); this.initializeFilters(); this.addStyles(); } /** * Opens the merge tags library modal * @param {string} mergeTag - Currently selected merge tag value (if any) * @param {boolean} isModule - Whether the merge tag is being used in a module context * @param {Function} onDataSelectCallback - Callback invoked when a tag is selected */ openMergeTagsLibrary(mergeTag, isModule, onDataSelectCallback) { // Store callback and selected tag this.selectedMergetag = mergeTag; this.isModule = isModule; this.dataSelectCallback = onDataSelectCallback; // Update module badge visibility const moduleBadge = this.externalLibrary.querySelector('.module-badge'); if (moduleBadge) { moduleBadge.style.display = this.isModule ? 'inline-block' : 'none'; } // Update selected state this.updateSelectedTag(); // Show modal this.externalLibrary.style.display = 'flex'; // Reset filters to show all tags this.filterTags('all'); const allButton = this.externalLibrary.querySelector('[data-category="all"]'); if (allButton) { this.updateActiveButton(allButton); } } } ``` ### Key Components Explained * **Instance Properties**: * `externalLibrary`: Reference to the modal DOM element * `selectedMergetag`: Currently selected merge tag value * `dataSelectCallback`: Callback function from the Stripo editor * `activeCategory`: Currently active filter category * `isModule`: Boolean flag indicating if the merge tag is used in a module context * **Constructor**: Initializes the modal UI, event listeners, filters, and custom styles * **openMergeTagsLibrary**: Required method that: * Stores the selected merge tag, module context, and callback * Shows/hides the "Module" badge based on context * Updates visual selection state * Displays the modal * Resets filters to show all tags ## Step 3: Define Merge Tags Data and Styles Add static properties for merge tags data and UI configuration. Continue editing `src/MyExternalMergeTagsLibrary.js`: ```javascript export class MyExternalMergeTagsLibrary { // ... existing properties ... // UI Style configurations static STYLES = { // Modal overlay styles overlay: { backgroundColor: 'rgba(0,0,0,.7)', position: 'fixed', top: '0', right: '0', bottom: '0', left: '0', zIndex: '1050', display: 'flex', alignItems: 'center', justifyContent: 'center', backdropFilter: 'blur(4px)', fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" }, // Modal container styles modal: { backgroundColor: '#ffffff', borderRadius: '12px', boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', maxWidth: '900px', width: '90%', display: 'flex', flexDirection: 'column', position: 'relative' }, // Header styles header: { padding: '24px 32px', borderBottom: '1px solid #e5e7eb', display: 'flex', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#f9fafb', borderRadius: '12px 12px 0 0' }, // Content container styles content: { padding: '32px', height: '315px', overflowY: 'auto', overflowX: 'hidden', boxSizing: 'border-box' }, // Grid styles grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: '16px' }, // Button styles buttonActive: { padding: '6px 14px', borderRadius: '6px', border: 'none', backgroundColor: '#34c759', color: 'white', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'background-color 0.2s' }, buttonInactive: { padding: '6px 14px', borderRadius: '6px', border: '1px solid #e5e7eb', backgroundColor: 'white', color: '#6b7280', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'all 0.2s' }, // Footer styles footer: { padding: '16px 32px', borderTop: '1px solid #e5e7eb', backgroundColor: '#fef3c7', borderRadius: '0 0 12px 12px', textAlign: 'center' } }; // Sample merge tags data static MERGE_TAGS = [ { category: 'personal', value: '*|FNAME|*', label: 'First Name', preview: 'John', description: 'Recipient\'s first name' }, { category: 'personal', value: '*|LNAME|*', label: 'Last Name', preview: 'Doe', description: 'Recipient\'s last name' }, { category: 'personal', value: '*|EMAIL|*', label: 'Email Address', preview: 'john.doe@example.com', description: 'Recipient\'s email address' }, { category: 'contact', value: '%%Phone%%', label: 'Phone Number', preview: '+1 (555) 123-4567', description: 'Recipient\'s phone number' }, { category: 'company', value: '{{company}}', label: 'Company Name', preview: 'Acme Corp', description: 'Recipient\'s company' }, { category: 'date', value: '*|DATE|*', label: 'Current Date', preview: new Date().toLocaleDateString(), description: 'Today\'s date' }, { category: 'custom', value: '*|CUSTOM_FIELD|*', label: 'Custom Field', preview: 'Custom Value', description: 'Custom merge field' } ]; // ... rest of the class ... } ``` ### Merge Tag Object Properties | Property | Type | Description | |----------|------|-------------| | `category` | `string` | Filter category (personal, contact, company, date, custom) | | `value` | `string` | The actual merge tag value used in templates | | `label` | `string` | Human-readable display name | | `preview` | `string` | Sample value shown to users | | `description` | `string` | Helpful description of the merge tag's purpose | ::: tip Production Implementation In production environments, replace the static `MERGE_TAGS` array with API calls to dynamically fetch merge tags from your CRM, CDP, or custom data sources. ::: ## Step 4: Build the Modal UI Structure Implement the core modal creation methods. These methods generate the modal HTML structure and inject it into the page. ### Create Modal Method ```javascript /** * Creates the modal HTML structure and appends it to the document body */ createModal() { const modalHtml = this.generateModalHTML(); const container = document.createElement('div'); container.innerHTML = modalHtml; document.body.appendChild(container); // Store reference to the modal element this.externalLibrary = document.getElementById('externalMergeTags'); // Initially hide the modal this.externalLibrary.style.display = 'none'; } /** * Generates the complete modal HTML structure * @returns {string} Complete HTML string for the modal */ generateModalHTML() { return `
${this.generateHeaderHTML()} ${this.generateContentHTML()} ${this.generateFooterHTML()}
`; } ``` ### Style Conversion Helper Add the following utility method that converts JavaScript style objects to inline CSS strings: ```javascript /** * Converts a style object to an inline style string * @param {Object} styleObj - Style object with camelCase properties * @returns {string} Inline CSS style string with kebab-case properties */ styleObjToString(styleObj) { return Object.entries(styleObj) .map(([key, value]) => { // Convert camelCase to kebab-case const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase(); return `${kebabKey}: ${value}`; }) .join('; '); } ``` ### Add Custom Styles for Selection State ```javascript /** * Adds custom styles for the selected merge tag state */ addStyles() { const style = document.createElement('style'); style.innerHTML = ` #externalMergeTags .tag-card.selected { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } `; document.head.appendChild(style); } ``` ::: warning Important The `styleObjToString()` method is essential for converting the `STYLES` object into inline CSS strings. The `addStyles()` method adds CSS for visual selection feedback. ::: ### Modal Structure Overview The modal consists of three main sections: 1. **Header**: Title, category filter buttons, and close button 2. **Content**: Scrollable grid of merge tag cards with labels, values, descriptions, and previews 3. **Footer**: Informational disclaimer ## Step 5: Generate Header with Filters Create the header section with title, filter buttons, and close button. ### Header HTML Generator ```javascript /** * Generates the modal header section HTML * @returns {string} HTML string for the header section */ generateHeaderHTML() { return `

Merge Tags

${this.generateFilterButtons()}
${this.generateCloseButton()}
`; } ``` ::: tip Module Badge Feature The header includes a "Module" badge that displays when the merge tag selector is opened from within a module context. This badge is hidden by default and only becomes visible when `isModule` is true, helping users understand whether they're working with merge tags in a regular template or within a reusable module component. ::: ### Filter Buttons Generator ```javascript /** * Generates category filter buttons HTML * @returns {string} HTML string for all filter buttons */ generateFilterButtons() { const categories = [ { id: 'all', label: 'All', active: true }, { id: 'personal', label: 'Personal', active: false }, { id: 'contact', label: 'Contact', active: false }, { id: 'company', label: 'Company', active: false }, { id: 'date', label: 'Date/Time', active: false }, { id: 'custom', label: 'Custom', active: false } ]; return categories.map(cat => ` `).join(''); } ``` ::: tip Category Customization Categories can be added or modified by updating the `categories` array. Ensure your merge tags data contains matching `category` values for proper filtering. Common categories include personal info, contact details, company data, transactional data, and custom fields. ::: ### Close Button Generator ```javascript /** * Generates close button HTML with hover effects * @returns {string} HTML string for the close button */ generateCloseButton() { return ` `; } ``` ## Step 6: Generate Content with Merge Tags Grid Create the content section that displays merge tag cards in a responsive grid. ### Content Container Generator ```javascript /** * Generates the modal content section HTML with merge tags grid * @returns {string} HTML string for the content section */ generateContentHTML() { return `
${this.generateMergeTagCards()}
`; } ``` ### Merge Tag Cards Generator ```javascript /** * Generates merge tag card HTML * @returns {string} HTML string for all merge tag cards */ generateMergeTagCards() { return MyExternalMergeTagsLibrary.MERGE_TAGS.map(tag => `

${tag.label}

${tag.value}

${tag.description}

Preview: ${tag.preview}
`).join(''); } ``` ### Key Features 1. **Responsive Grid**: Utilizes CSS Grid with `auto-fill` to create a responsive layout that adapts to different screen sizes 2. **Merge Tag Cards**: Each card displays the merge tag label, value (in monospace), description, and sample preview 3. **Hover Effects**: Inline event handlers provide smooth transition animations with conditional logic to preserve selected state 4. **Data Attributes**: Merge tag metadata is stored in data attributes for efficient retrieval on selection 5. **Visual Feedback**: Selected cards have distinct styling to indicate current selection 6. **Preview Display**: Shows sample data to help users understand what the merge tag will display ::: tip Design Considerations The monospace font for merge tag values helps users distinguish the actual merge tag syntax from the display labels. The preview section provides crucial context about what data the merge tag represents. ::: ## Step 7: Generate Footer with Disclaimer Add a footer section to display important notices or disclaimers. ```javascript /** * Generates the modal footer section HTML with disclaimer notice * @returns {string} HTML string for the footer section */ generateFooterHTML() { return `

⚠️ Notice: This popup window is not part of the plugin. It is intended solely for demonstration purposes and can be implemented independently in any desired way.

`; } ``` ## Step 8: Implement Event Handlers Add event listeners to handle user interactions with the modal. ### Attach Event Listeners ```javascript /** * Attaches event listeners to modal elements after creation */ attachEventListeners() { // Close button click handler this.externalLibrary.querySelector('.close') .addEventListener('click', this.cancelAndClose.bind(this)); // Tag card click handler (using event delegation) this.externalLibrary.addEventListener('click', this.onTagClick.bind(this)); } ``` ### Handle Merge Tag Selection ```javascript /** * Handles click events on merge tag cards * @param {Event} e - Click event object */ onTagClick(e) { // Check if clicked on tag card or any of its children const tagCard = e.target.closest('.tag-card'); if (!tagCard) return; // Create callback object with tag data const tagData = { value: tagCard.getAttribute('data-value'), label: tagCard.getAttribute('data-label') }; // Close modal and execute callback this.close(); this.dataSelectCallback(tagData); } ``` ::: tip Data Format The callback receives an object with `value` and `label` properties. The `value` is the actual merge tag syntax that will be inserted into the template, while the `label` provides the human-readable name for display purposes. ::: ### Handle Selected State Updates ```javascript /** * Updates the selected state of merge tag cards */ updateSelectedTag() { // Remove selected class from all cards const selectedElement = this.externalLibrary.querySelector('.tag-card.selected'); if (selectedElement) { selectedElement.classList.remove('selected'); // Reset styles selectedElement.style.borderColor = '#e5e7eb'; selectedElement.style.transform = 'translateY(0)'; selectedElement.style.boxShadow = 'none'; } // Add selected class to current tag if (this.selectedMergetag) { const currentTag = this.externalLibrary.querySelector(`[data-value="${this.selectedMergetag}"]`); if (currentTag) { currentTag.classList.add('selected'); } } } ``` ### Handle Modal Closure ```javascript /** * Closes the modal and executes cancel callback */ cancelAndClose() { this.close(); } /** * Closes the modal by hiding it from view */ close() { this.externalLibrary.style.display = 'none'; } ``` ### Event Delegation Benefits Using event delegation by listening to the parent container provides several advantages: * **Improved Performance**: A single event listener replaces multiple individual listeners for each card * **Simplified Maintenance**: Eliminates the need to dynamically attach and detach listeners * **Future-Proof Implementation**: Automatically handles dynamically added merge tag elements * **Memory Efficiency**: Reduces the memory footprint when managing numerous elements ## Step 9: Implement Category Filtering Add filtering functionality to help users find merge tags by category. ### Initialize Filter Buttons ```javascript /** * Initializes category filter button functionality */ initializeFilters() { const filterButtons = this.externalLibrary.querySelectorAll('.filter-buttons button'); filterButtons.forEach(button => { button.addEventListener('click', (e) => { const category = e.target.getAttribute('data-category'); this.filterTags(category); this.updateActiveButton(e.target); }); }); } ``` ### Filter Merge Tags by Category ```javascript /** * Filters displayed merge tags based on the selected category * @param {string} category - Category identifier to filter by (or 'all' for all tags) */ filterTags(category) { this.activeCategory = category; const tagCards = this.externalLibrary.querySelectorAll('.tag-card'); tagCards.forEach(card => { const shouldShow = category === 'all' || card.getAttribute('data-category') === category; card.style.display = shouldShow ? 'flex' : 'none'; }); } ``` ### Update Button Visual States ```javascript /** * Updates the visual state of category filter buttons * @param {HTMLElement} activeButton - The button element that was clicked and should be marked active */ updateActiveButton(activeButton) { const buttons = this.externalLibrary.querySelectorAll('.filter-buttons button'); buttons.forEach(button => { const isActive = button === activeButton; const styles = isActive ? MyExternalMergeTagsLibrary.STYLES.buttonActive : MyExternalMergeTagsLibrary.STYLES.buttonInactive; // Apply styles Object.assign(button.style, styles); }); } ``` ## Step 10: Create the Merge Tags UI Element Create `src/MergeTagsUiElement.js` to define the custom UI element that will replace the default merge tags selector: ```javascript import {UIElement, UIElementType} from '@stripoinc/ui-editor-extensions'; import {MyExternalMergeTagsLibrary} from './MyExternalMergeTagsLibrary'; export const EXTERNAL_MERGE_TAGS_UI_ELEMENT_ID = 'external-merge-tags-ui-element'; export class MergeTagsUiElement extends UIElement { isModuleNode = false; /** * Returns the unique identifier for this UI element */ getId() { return EXTERNAL_MERGE_TAGS_UI_ELEMENT_ID; } /** * Returns the HTML template for the merge tags button */ getTemplate() { return `
<${UIElementType.BUTTON} id="mergeTagsButton" class="btn btn-primary">Open merge tags
`; } /** * Called when the element is rendered in the editor * @param {HTMLElement} container - The container element */ onRender(container) { this.listener = this._onClick.bind(this); this.mergeTagsButton = container.querySelector('#mergeTagsButton'); this.mergeTagsButton.addEventListener('click', this.listener); } /** * Called when the element is destroyed */ onDestroy() { this.mergeTagsButton.removeEventListener('click', this.listener); } /** * Handles button click events */ _onClick(event) { this.openMergeTagLibrary(); } /** * Opens the external merge tags library modal */ openMergeTagLibrary() { if (!this.mergeTagsLibrary) { this.mergeTagsLibrary = new MyExternalMergeTagsLibrary(); } this.mergeTagsLibrary.openMergeTagsLibrary(this.selectedMergeTag?.value, this.isModuleNode, (data) => { this.api.triggerValueChange(data); }); } /** * Called when an attribute is updated * @param {string} name - Attribute name * @param {any} value - New attribute value */ onAttributeUpdated(name, value) { if (name === 'blockNode') { this.isModuleNode = !!value.getClosestModuleId(); } if (name === 'mergeTag') { this.selectedMergeTag = value; // If a merge tag is selected, open the library immediately this.selectedMergeTag && this.openMergeTagLibrary(); } } } ``` ### UI Element Lifecycle Methods | Method | Purpose | |--------|---------| | `getId()` | Returns unique identifier for the UI element | | `getTemplate()` | Returns HTML template for the button | | `onRender(container)` | Sets up event listeners when rendered | | `onDestroy()` | Cleans up event listeners when destroyed | | `onAttributeUpdated(name, value)` | Handles attribute changes from the editor | ### Module Context Detection The UI element tracks whether the merge tag is being used within a module context through the `blockNode` attribute: * **`isModuleNode` property**: Boolean flag that indicates if the current node is within a module * **`blockNode` attribute**: When updated, the code checks if the node has a closest module ID using `value.getClosestModuleId()` * **Module Badge**: The modal displays a "Module" badge when `isModuleNode` is true, helping users understand the context ::: tip API Integration The `this.api.triggerValueChange(data)` method notifies the Stripo editor of the selected merge tag. The editor will then insert or update the merge tag in the template based on the current context. The `isModuleNode` flag is passed to the library to enable context-aware UI features like the module badge. ::: ## Step 11: Create the UI Element Tag Registry Create `src/ExtensionTagRegistry.js` to register your custom UI element to replace the default merge tags selector: ```javascript import {UIElementTagRegistry, UIElementType} from '@stripoinc/ui-editor-extensions'; import {EXTERNAL_MERGE_TAGS_UI_ELEMENT_ID} from './MergeTagsUiElement'; export class ExtensionTagRegistry extends UIElementTagRegistry { /** * Registers custom UI elements to replace default editor elements * @param {Object} uiElementsTagsMap - Map of UI element types to custom element IDs */ registerUiElements(uiElementsTagsMap) { uiElementsTagsMap[UIElementType.MERGETAGS] = EXTERNAL_MERGE_TAGS_UI_ELEMENT_ID; } } ``` ### How Tag Registry Works The `UIElementTagRegistry` allows you to map standard Stripo UI element types to your custom implementations: 1. **UIElementType.MERGETAGS**: The standard merge tags selector type 2. **EXTERNAL\_MERGE\_TAGS\_UI\_ELEMENT\_ID**: Your custom element's unique ID 3. When the editor needs a merge tags selector, it will use your custom implementation instead ::: warning Important The tag registry is what enables your custom UI element to replace the default merge tags selector throughout the editor. Without this registration, your custom element would exist but not be automatically used by the editor. ::: ## Step 12: Register the Extension Create `src/extension.js` to register your merge tags integration with the Stripo extension system: ```javascript import { ExtensionBuilder } from '@stripoinc/ui-editor-extensions'; import {MergeTagsUiElement} from './MergeTagsUiElement'; import {ExtensionTagRegistry} from './ExtensionTagRegistry'; const extension = new ExtensionBuilder() .addUiElement(MergeTagsUiElement) .withUiElementTagRegistry(ExtensionTagRegistry) .build(); export default extension; ``` ### Extension Registration Explained The `ExtensionBuilder` class provides a fluent API for registering integrations: 1. **`.addUiElement(MergeTagsUiElement)`**: Registers your custom UI element with the extension system 2. **`.withUiElementTagRegistry(ExtensionTagRegistry)`**: Registers your tag registry to replace default UI elements 3. **`.build()`**: Constructs and returns the final extension object for the editor ### Registration Order The order of operations is important: 1. First, register the UI element using `addUiElement()` 2. Then, register the tag registry using `withUiElementTagRegistry()` 3. The tag registry maps the `UIElementType.MERGETAGS` to your custom element's ID 4. When the editor needs a merge tags selector, it will use your custom element ::: tip Multiple Integrations Multiple integration methods can be chained to register different types of extensions within a single extension object: ```javascript new ExtensionBuilder() .addUiElement(MergeTagsUiElement) .withUiElementTagRegistry(ExtensionTagRegistry) .withExternalImageLibrary(MyExternalImageLibrary) .withExternalVideosLibrary(MyExternalVideosLibrary) .build(); ``` ::: ## Step 13: Configure the Editor Integration Update your `src/index.js` to properly integrate the extension with the Stripo editor: ```javascript import extension from './extension.js'; import {PLUGIN_ID, SECRET_KEY, EDITOR_URL, EMAIL_ID, USER_ID} from './creds'; // Initialize the editor with your extension function _runEditor(template, extension) { window.UIEditor.initEditor( document.querySelector('#stripoEditorContainer'), { html: template.html, css: template.css, metadata: { emailId: EMAIL_ID }, locale: 'en', onTokenRefreshRequest: function (callback) { _request('POST', 'https://plugins.stripo.email/api/v1/auth', JSON.stringify({ pluginId: PLUGIN_ID, secretKey: SECRET_KEY, userId: USER_ID, role: 'user' }), function(data) { callback(JSON.parse(data).token); } ); }, // ... other configuration ... ignoreClickOutsideSelectors: ['#externalMergeTags'], extensions: [ extension ] } ); } ``` ### Critical Configuration Options | Option | Purpose | |--------|---------| | `ignoreClickOutsideSelectors` | Prevents editor from closing your modal when clicking inside it | | `extensions` | Array of extension objects to register with the editor | ::: warning Important The `ignoreClickOutsideSelectors: ['#externalMergeTags']` configuration is critical. Without it, clicking inside your modal may trigger the editor's click-outside handlers and cause unexpected behavior. Make sure the selector matches your modal's ID. ::: ## Step 14: Run the Development Server Your implementation is now ready for testing. ### Start the Development Server ```bash npm run dev ``` This command will: 1. Start the Vite development server on `http://localhost:3000` 2. Automatically open your default browser 3. Load the Stripo Editor with your merge tags extension integrated ## Complete Example For a full working example, check out the complete implementation in our [GitHub repository](https://github.com/stripoinc/stripo-plugin-samples/tree/main/extensions/javascript/external-merge-tags). --- --- url: >- https://plugin.stripo.email/extensions/tutorials/examples/integrations/external-ai-assistant.md --- # Integrate an External AI Assistant ## Overview The External AI Assistant integration enables users to enhance and transform text content using AI-powered suggestions directly within the Stripo Email Editor. This integration provides a seamless experience for improving email copy with various text transformations including tone adjustment, length modification, and professional formatting. ### What You'll Build In this tutorial, you'll create a fully functional AI assistant modal that: * Displays the original text content for reference * Provides quick action buttons for common text transformations * Includes an editable text area for manual adjustments * Supports multiple transformation types (professional, casual, expand, shorten, generate) * Handles text selection with proper callback integration * Returns transformed text in the format expected by the Stripo editor ::: image-wrap ![](/img/plugin/new/image219.webp){width=1626 height=756} ::: ::: image-wrap ![](/img/plugin/new/image220.webp){width=1736 height=1360} ::: ::: image-wrap ![](/img/plugin/new/image221.webp){width=1426 height=1034} ::: ### Use Cases * **Content Enhancement**: Improve email copy with AI-powered suggestions * **Tone Adjustment**: Transform text between professional and casual tones * **Length Optimization**: Expand or shorten content to fit design requirements * **Content Generation**: Generate new paragraphs when starting from scratch * **Multi-language Support**: Connect to translation APIs for internationalization * **Brand Voice**: Ensure consistent brand voice across email campaigns * **A/B Testing**: Generate variations of content for testing ## Prerequisites Before starting this tutorial, ensure you have: * Node.js version 22.x or higher installed * Basic understanding of JavaScript ES6+ syntax * Familiarity with the Stripo Extensions SDK ## Understanding the Interface The [ExternalAiAssistant](/extensions/reference/integrations/ExternalAiAssistant) class must implement a single method: ```typescript openAiAssistant({ value: string, onDataSelectCallback: (transformedText: string) => void, onCancelCallback: () => void }): void ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `value` | `string` | The original text content to enhance or transform | | `onDataSelectCallback` | `Function` | Callback function invoked when user applies transformed text | | `onCancelCallback` | `Function` | Callback function invoked when user cancels the operation | ### Return Value When a user applies transformed text, your assistant implementation must invoke the callback with a string containing the enhanced text: ```javascript onDataSelectCallback(textarea.value); ``` ## Step 1: Project Setup Create your project structure and install dependencies according to the [Getting Started](/extensions/getting-started) guide. Your project directory structure should look like this: ```bash external-ai-assistant/ ├── index.html ├── src/ │ ├── creds.js │ ├── index.js │ ├── extension.js ├── package.json └── vite.config.js ``` ## Step 2: Create the AI Assistant Class Create a new file `src/MyExternalAiAssistant.js` with the following basic class structure: ```javascript import {ExternalAiAssistant} from '@stripoinc/ui-editor-extensions'; /** * External AI Assistant Implementation * This class implements a modal AI text assistant with various text transformation capabilities * for the Stripo Email Editor extension system. */ export class MyExternalAiAssistant extends ExternalAiAssistant { // Instance properties externalAiAssistant; dataSelectCallback = () => {}; cancelCallback = () => {}; originalText = ''; constructor() { super(); this.createModal(); this.attachEventListeners(); } /** * Required method called by the Stripo editor * Opens the AI assistant modal dialog * @param {Object} params - Parameters object * @param {string} params.value - The text to work with * @param {Function} params.onDataSelectCallback - Callback when text is selected * @param {Function} params.onCancelCallback - Callback when modal is cancelled */ openAiAssistant({value, onDataSelectCallback, onCancelCallback}) { // Store callbacks this.dataSelectCallback = onDataSelectCallback; this.cancelCallback = onCancelCallback; this.originalText = value || ''; // Display original text const originalTextDiv = this.externalAiAssistant.querySelector('#originalText'); originalTextDiv.textContent = this.originalText || 'No text provided'; // Set the textarea value this.externalAiAssistant.querySelector('#text').value = this.originalText; // Show modal this.externalAiAssistant.style.display = 'flex'; } } ``` ### Key Components Explained * **Instance Properties**: * `externalAiAssistant`: Reference to the modal DOM element * `dataSelectCallback`: Stores the success callback from the Stripo editor * `cancelCallback`: Stores the cancel callback from the Stripo editor * `originalText`: Stores the original text for reference and transformations * **Constructor**: Initializes the complete modal UI when the class is instantiated * **openAiAssistant**: Required method that: * Stores the callbacks for later invocation * Displays the original text in a read-only section * Pre-fills the editable textarea with the original text * Shows the modal dialog ## Step 3: Define Text Transformations and Styles Add static properties for transformation logic and UI configuration. Continue editing `src/MyExternalAiAssistant.js`: ```javascript export class MyExternalAiAssistant extends ExternalAiAssistant { // ... existing properties ... // UI Style configurations static STYLES = { // Modal overlay styles overlay: { backgroundColor: 'rgba(0, 0, 0, 0.6)', backdropFilter: 'blur(4px)', position: 'fixed', top: '0', right: '0', bottom: '0', left: '0', zIndex: '1050', fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif", display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '20px' }, // Modal container styles modal: { background: '#ffffff', borderRadius: '12px', boxShadow: '0 20px 60px rgba(0, 0, 0, 0.15)', maxWidth: '900px', width: '100%', maxHeight: '90vh', display: 'flex', flexDirection: 'column' }, // Header styles header: { padding: '24px 30px', borderBottom: '1px solid #e5e7eb', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, // Body styles body: { padding: '30px', overflowY: 'auto', flex: '1' }, // Footer styles footer: { padding: '20px 30px', borderTop: '1px solid #e5e7eb', display: 'flex', justifyContent: 'flex-end', gap: '12px' }, // Button styles suggestionButton: { background: 'linear-gradient(135deg, #f5f7fa 0%, #e9ecef 100%)', border: '1px solid #dee2e6', borderRadius: '8px', padding: '10px 20px', fontSize: '14px', fontWeight: '500', color: '#495057', cursor: 'pointer', transition: 'all 0.3s ease', display: 'flex', alignItems: 'center', gap: '8px' }, primaryButton: { backgroundColor: '#34c759', color: 'white', padding: '10px 24px', borderRadius: '8px', fontSize: '14px', fontWeight: '500', border: 'none', cursor: 'pointer', transition: 'all 0.3s ease' }, cancelButton: { backgroundColor: '#f3f4f6', color: '#6b7280', padding: '10px 24px', borderRadius: '8px', fontSize: '14px', fontWeight: '500', border: 'none', cursor: 'pointer', transition: 'all 0.3s ease' }, // Disclaimer footer styles disclaimerFooter: { padding: '16px 30px', borderTop: '1px solid #e5e7eb', backgroundColor: '#fef3c7', borderRadius: '0 0 12px 12px', textAlign: 'center' } }; // Text transformation templates static TEXT_TRANSFORMATIONS = { paragraph: { icon: '📝', label: 'Generate Paragraph', transform: () => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." }, professional: { icon: '💼', label: 'Make Professional', transform: (text) => text ? `Dear valued recipient,\n\nI hope this message finds you well. ${text}\n\nPlease do not hesitate to contact me if you require any further information or clarification.\n\nBest regards` : "Dear valued recipient,\n\nI hope this message finds you well. I am writing to bring to your attention a matter of significant importance that requires your immediate consideration.\n\nPlease do not hesitate to contact me if you require any further information or clarification.\n\nBest regards" }, casual: { icon: '😊', label: 'Make Casual', transform: (text) => text ? `Hey there! 👋\n\n${text}\n\nLet me know if you need anything else!\n\nCheers!` : "Hey there! 👋\n\nJust wanted to drop you a quick message. Hope everything's going great on your end!\n\nLet me know if you need anything else!\n\nCheers!" }, shorten: { icon: '✂️', label: 'Shorten Text', transform: (text) => text && text.length > 50 ? text.substring(0, Math.min(text.length / 2, 100)) + "..." : "Brief and concise message." }, expand: { icon: '📏', label: 'Expand Text', transform: (text) => text ? `${text}\n\nFurthermore, it is important to consider the broader implications of this matter. Additional context and supporting information can provide valuable insights that enhance our understanding of the subject at hand. By examining various perspectives and taking into account all relevant factors, we can arrive at a more comprehensive and well-informed conclusion.` : "This is an expanded version of the text with additional details, context, and supporting information. It provides a more comprehensive view of the subject matter, exploring various aspects and implications that might not have been immediately apparent in the original version." } }; // ... rest of the class ... } ``` ### Transformation Object Properties Each transformation in `TEXT_TRANSFORMATIONS` contains: | Property | Type | Description | |----------|------|-------------| | `icon` | `string` | Emoji icon displayed on the button | | `label` | `string` | Button label text | | `transform` | `Function` | Transformation function that takes original text and returns transformed text | ::: tip Production Implementation In production environments, replace the static transformation functions with actual API calls to AI services like OpenAI, Anthropic Claude, or your custom AI backend. ::: ## Step 4: Build the Modal UI Structure Implement the core modal creation methods. These methods generate the modal HTML structure and inject it into the page. ### Create Modal Method ```javascript /** * Creates the modal HTML structure and appends it to the document body */ createModal() { const modalHtml = this.generateModalHTML(); const container = document.createElement('div'); container.innerHTML = modalHtml; document.body.appendChild(container); // Store reference to the modal element this.externalAiAssistant = document.getElementById('externalAiAssistant'); // Initially hide the modal this.externalAiAssistant.style.display = 'none'; } /** * Generates the complete modal HTML structure * @returns {string} Complete HTML string for the modal */ generateModalHTML() { return `
${this.generateHeaderHTML()} ${this.generateBodyHTML()} ${this.generateFooterHTML()} ${this.generateDisclaimerFooterHTML()}
`; } ``` ### Style Conversion Helper Add the following utility method that converts JavaScript style objects to inline CSS strings: ```javascript /** * Converts a style object to an inline style string * @param {Object} styleObj - Style object with camelCase properties * @returns {string} Inline CSS style string with kebab-case properties */ styleObjToString(styleObj) { return Object.entries(styleObj) .map(([key, value]) => { // Convert camelCase to kebab-case const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase(); return `${kebabKey}: ${value}`; }) .join('; '); } ``` ::: warning Important The `styleObjToString()` method is essential for converting the `STYLES` object into inline CSS strings. This method is required for proper modal rendering. ::: ### Modal Structure Overview The modal consists of four main sections: 1. **Header**: Title with AI icon and close button 2. **Body**: Original text display, action buttons, and text editor 3. **Footer**: Cancel and Apply buttons 4. **Disclaimer Footer**: Informational notice ## Step 5: Generate Header with Branding Create the header section with AI branding and close button. ### Header HTML Generator ```javascript /** * Generates the modal header section HTML * @returns {string} HTML string for the header section */ generateHeaderHTML() { return `

AI
AI Text Assistant

${this.generateCloseButton()}
`; } ``` ### Close Button Generator ```javascript /** * Generates close button HTML with hover effects * @returns {string} HTML string for the close button */ generateCloseButton() { return ` `; } ``` ## Step 6: Generate Body with Three Sections Create the body section containing original text, action buttons, and text editor. ### Body Container Generator ```javascript /** * Generates the modal body section HTML * @returns {string} HTML string for the body section */ generateBodyHTML() { return `
${this.generateOriginalTextSection()} ${this.generateActionsSection()} ${this.generateTextEditorSection()}
`; } ``` ### Original Text Section ```javascript /** * Generates the original text section HTML * @returns {string} HTML string for original text section */ generateOriginalTextSection() { return `
Original Text
`; } ``` ### Actions Section with Quick Actions ```javascript /** * Generates the actions section HTML * @returns {string} HTML string for actions section */ generateActionsSection() { return `
Quick Actions
${this.generateActionButtons()}
`; } /** * Generates action button HTML * @returns {string} HTML string for action buttons */ generateActionButtons() { return Object.entries(MyExternalAiAssistant.TEXT_TRANSFORMATIONS).map(([action, config]) => ` `).join(''); } ``` ### Text Editor Section ```javascript /** * Generates the text editor section HTML * @returns {string} HTML string for text editor section */ generateTextEditorSection() { return `
Modified Text
`; } ``` ### Key Features 1. **Three-Section Layout**: Clear separation of original text, actions, and editable output 2. **Scrollable Original Text**: Maximum height with overflow for long content 3. **Flexible Action Grid**: Wraps buttons on smaller screens 4. **Hover Effects**: Gradient transformation on button hover with smooth animations 5. **Editable Textarea**: Users can manually edit generated text before applying 6. **Focus States**: Visual feedback when textarea is focused ## Step 7: Generate Footer with Action Buttons Add footer section with cancel and apply buttons. ```javascript /** * Generates the modal footer section HTML * @returns {string} HTML string for the footer section */ generateFooterHTML() { return `
`; } /** * Generates the disclaimer footer section HTML * @returns {string} HTML string for the disclaimer footer */ generateDisclaimerFooterHTML() { return `

⚠️ Notice: This popup window is not part of the plugin. It is intended solely for demonstration purposes and can be implemented independently in any desired way.

`; } ``` ## Step 8: Implement Event Handlers Add event listeners to handle user interactions with the modal. ### Attach Event Listeners ```javascript /** * Attaches event listeners to modal elements after creation */ attachEventListeners() { // Close button click handler this.externalAiAssistant.querySelector('.close') .addEventListener('click', this.cancelAndClose.bind(this)); // Cancel button click handler this.externalAiAssistant.querySelector('.cancelButton') .addEventListener('click', this.cancelAndClose.bind(this)); // OK button click handler this.externalAiAssistant.querySelector('.okButton') .addEventListener('click', this.onOkClick.bind(this)); // Suggestion button click handlers const suggestionButtons = this.externalAiAssistant.querySelectorAll('.suggestion-btn'); suggestionButtons.forEach(btn => { btn.addEventListener('click', (e) => this.handleSuggestion(e.currentTarget.dataset.action)); }); } ``` ### Handle Text Transformations ```javascript /** * Handles suggestion button clicks * @param {string} action - The action to perform */ handleSuggestion(action) { const transformation = MyExternalAiAssistant.TEXT_TRANSFORMATIONS[action]; if (!transformation) return; const textarea = this.externalAiAssistant.querySelector('#text'); textarea.value = transformation.transform(this.originalText); } ``` ::: tip Real AI Integration In production, the `handleSuggestion` method should make actual API calls to AI services. ::: ### Handle Apply and Cancel Actions ```javascript /** * Handles OK button click */ onOkClick() { const text = this.externalAiAssistant.querySelector('#text').value.replaceAll('\n', '
'); this.close(); this.dataSelectCallback(text); } /** * Closes the modal and invokes the cancel callback */ cancelAndClose() { this.close(); this.cancelCallback(); } /** * Closes the modal dialog by hiding it from view */ close() { this.externalAiAssistant.style.display = 'none'; } ``` ### Key Implementation Details 1. **Data Attribute Pattern**: Uses `data-action` attributes to identify which transformation to apply 2. **Newline Conversion**: Converts `\n` to `
` for HTML email compatibility 3. **Callback Invocation**: Ensures callbacks are called in correct order 4. **Modal State Management**: Simple show/hide toggle using display property ## Step 9: Register the Extension Create `src/extension.js` to register your AI assistant with the Stripo extension system: ```javascript import {ExtensionBuilder} from '@stripoinc/ui-editor-extensions'; import {MyExternalAiAssistant} from './MyExternalAiAssistant'; export default new ExtensionBuilder() .withExternalAiAssistant(MyExternalAiAssistant) .build(); ``` ### Extension Registration Explained The `ExtensionBuilder` class provides a fluent API for registering integrations: * **`withExternalAiAssistant()`**: Registers your custom AI assistant implementation * **`build()`**: Constructs and returns the final extension object for the editor ::: tip Multiple Integrations Multiple `.with*()` methods can be chained to register different integrations within a single extension: ```javascript new ExtensionBuilder() .withExternalAiAssistant(MyExternalAiAssistant) .withExternalImageLibrary(MyExternalImageLibrary) .withExternalVideosLibrary(MyExternalVideosLibrary) .build(); ``` ::: ## Step 10: Run the Development Server Your implementation is now ready for testing. ### Start the Development Server ```bash npm run dev ``` This command will: 1. Start the Vite development server on `http://localhost:3000` 2. Automatically open your default browser 3. Load the Stripo Editor with your AI assistant extension integrated ## Complete Example For a full working example, check out the complete implementation in our [GitHub repository](https://github.com/stripoinc/stripo-plugin-samples/tree/main/extensions/javascript/external-ai-assistant). --- --- url: >- https://plugin.stripo.email/extensions/tutorials/examples/integrations/external-display-conditions.md --- # Integrate an External Display Conditions Library ## Overview The External Display Conditions Library integration enables users to create conditional display rules for email content directly within the Stripo Email Editor. This powerful feature allows you to show or hide specific content blocks based on user attributes, creating personalized email experiences for different audience segments. ### What You'll Build In this tutorial, you'll create a fully functional display conditions modal that: * Provides a user-friendly interface for creating conditional logic rules * Supports multiple condition fields (Email Address, Phone Number) * Offers various operations (Equals, Contains) for flexible matching * Allows combining multiple conditions with AND/OR logic * Validates user input to ensure proper condition structure * Returns properly formatted condition scripts for the Stripo editor ::: image-wrap ![](/img/plugin/new/image225.webp){width=1999 height=830} ::: ::: image-wrap ![](/img/plugin/new/image226.webp){width=1852 height=1114} ::: ::: image-wrap ![](/img/plugin/new/image227.webp){width=1520 height=1136} ::: ### Use Cases * **Personalization**: Show different content to users based on email domain (e.g., @gmail.com vs @company.com) * **Segmentation**: Display specialized offers for specific user groups * **A/B Testing**: Create conditional content variations for testing * **Localization**: Show region-specific content based on user attributes * **Dynamic Pricing**: Display different pricing for different customer segments * **VIP Content**: Reveal exclusive content for premium users ## Prerequisites Before starting this tutorial, ensure you have: * Node.js version 22.x or higher installed * Basic understanding of JavaScript ES6+ syntax * Familiarity with the Stripo Extensions SDK ## Understanding the Interface The [ExternalDisplayConditionsLibrary](/extensions/reference/integrations/ExternalDisplayConditionsLibrary) class must implement a single method: ```typescript openExternalDisplayConditionsDialog( currentCondition: DisplayCondition | null, onSelectCallback: (condition: DisplayCondition | null) => void, onCancelCallback: () => void ): void ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `currentCondition` | `DisplayCondition \| null` | Currently applied display condition (if any) | | `onSelectCallback` | `Function` | Callback function invoked when conditions are applied or removed | | `onCancelCallback` | `Function` | Callback function invoked when the modal is cancelled | ### DisplayCondition Object When a user applies conditions, your library implementation must return an object with the following structure: ```typescript { id: string; // ID of the condition name: string; // Display name description: string; // User-friendly description beforeScript: string; // Opening condition script afterScript: string; // Closing condition script extraData: string; // Extra custom data, can be set by user conditionsCount?: number; // Number of individual conditions represented } ``` To remove all conditions, pass `null` to the `onSelectCallback`. ## Step 1: Project Setup Create your project structure and install dependencies according to the [Getting Started](/extensions/getting-started) guide. Your project directory structure should look like this: ```bash external-display-conditions/ ├── index.html ├── src/ │ ├── creds.js │ ├── index.js │ └── extension.js ├── package.json └── vite.config.js ``` ## Step 2: Create the Display Conditions Library Class Create a new file `src/MyExternalDisplayConditions.js` with the following basic class structure: ```javascript import {ExternalDisplayConditionsLibrary} from '@stripoinc/ui-editor-extensions'; /** * External Display Conditions Library Implementation * Provides UI for creating conditional display rules for email content */ export class MyExternalDisplayConditions extends ExternalDisplayConditionsLibrary { // Instance properties selectConditionsCallback = null; conditionsPopupElement = null; onCancelCallback = null; /** * Required method called by the Stripo editor * Opens the display conditions modal dialog * @param {DisplayCondition|null} currentCondition - Currently applied condition * @param {Function} onSelectCallback - Callback invoked when conditions are applied/removed * @param {Function} onCancelCallback - Callback invoked when the modal is cancelled */ openExternalDisplayConditionsDialog(currentCondition, onSelectCallback, onCancelCallback) { // Store callbacks this.selectConditionsCallback = onSelectCallback; this.onCancelCallback = onCancelCallback; // Show modal this.activateConditionsPopup(currentCondition); } /** * Gets the category name displayed in the editor UI * @returns {string} The category name */ getCategoryName() { return 'External display conditions'; } /** * Determines if the context action should be enabled in the editor * @returns {boolean} true if enabled */ getIsContextActionEnabled() { return true; } /** * Gets the index position for the context action in the context menu * @returns {number} The index position (1-based) */ getContextActionIndex() { return 1; } } ``` ### Key Components Explained * **Instance Properties**: * `selectConditionsCallback`: Stores the success callback from the Stripo editor * `conditionsPopupElement`: Reference to the modal DOM element * `onCancelCallback`: Stores the cancel callback from the Stripo editor * **openExternalDisplayConditionsDialog**: Required method that opens the modal and manages callbacks * **getCategoryName**: Returns the category name shown in the editor's UI * **Context Action Methods**: Control how the display conditions action appears in the editor's context menu ## Step 3: Define Configuration Constants and Styles Add configuration constants and UI styles. Continue editing `src/MyExternalDisplayConditions.js`: ```javascript export class MyExternalDisplayConditions extends ExternalDisplayConditionsLibrary { // ... existing properties ... // Configuration constants static AVAILABLE_CONDITION_NAMES = [ {label: 'Email Address', value: '$EMAIL'}, {label: 'Phone number', value: '$PHONE'}, ]; static AVAILABLE_CONDITION_OPERATIONS = [ {label: 'Equals (Is)', value: 'equals'}, {label: 'Contains', value: 'in_array'}, ]; static AVAILABLE_CONDITION_CONCATENATIONS = [ {label: 'all', value: '&&'}, {label: 'any', value: '||'} ]; static DEFAULT_CONDITION = { name: '$EMAIL', operation: 'equals', value: '' }; // CSS class names static CSS_CLASSES = { DROPDOWN_NAME: 'dropdownConditionField', DROPDOWN_OPERATION: 'dropdownConditionOperation', DROPDOWN_CONCATENATION: 'dropdownConcatenation', CONDITION_ROW: 'condition-row', CONDITION_VALUE: 'condition-value', CONDITIONS_TABLE: 'conditionsTable', VALIDATION_ERROR: 'validation-error', DELETE_ACTION_PREFIX: 'condition-delete-action-' }; // Validation messages static MESSAGES = { VALIDATION_ERROR: 'Please enter a value for at least one condition.', CONDITION_NAME: 'Conditions applied', CONDITION_DESCRIPTION: 'Only users that fit conditions will see this part of the email.' }; // UI Style configurations static STYLES = { // Modal overlay styles overlay: { backgroundColor: 'rgba(0,0,0,.7)', position: 'fixed', top: '0', right: '0', bottom: '0', left: '0', zIndex: '1050', display: 'flex', alignItems: 'center', justifyContent: 'center', backdropFilter: 'blur(4px)', fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" }, // Modal container styles modal: { backgroundColor: '#ffffff', borderRadius: '12px', boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', maxWidth: '700px', width: '90%', maxHeight: '90vh', display: 'flex', flexDirection: 'column', position: 'relative' }, // Header styles header: { padding: '24px 32px', borderBottom: '1px solid #e5e7eb', display: 'flex', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#f9fafb', borderRadius: '12px 12px 0 0' }, // Content styles content: { padding: '32px', overflowY: 'auto', flex: '1' }, // Form control styles select: { width: '100%', padding: '8px 12px', border: '1px solid #e5e7eb', borderRadius: '6px', fontSize: '14px', backgroundColor: 'white', cursor: 'pointer', transition: 'border-color 0.2s', outline: 'none' }, input: { width: '100%', padding: '8px 12px', border: '1px solid #e5e7eb', borderRadius: '6px', fontSize: '14px', transition: 'border-color 0.2s', outline: 'none' }, // Button styles buttonPrimary: { padding: '8px 20px', borderRadius: '6px', border: 'none', backgroundColor: '#34c759', color: 'white', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'background-color 0.2s' }, buttonSecondary: { padding: '8px 20px', borderRadius: '6px', border: '1px solid #e5e7eb', backgroundColor: 'white', color: '#6b7280', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'all 0.2s' }, buttonAdd: { padding: '6px 16px', borderRadius: '6px', border: '1px solid #3b82f6', backgroundColor: 'white', color: '#3b82f6', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'all 0.2s', display: 'inline-flex', alignItems: 'center', gap: '6px' } }; // ... rest of the class ... } ``` ### Configuration Explained 1. **AVAILABLE\_CONDITION\_NAMES**: Defines the available user attributes (fields) that can be used in conditions 2. **AVAILABLE\_CONDITION\_OPERATIONS**: Defines the comparison operations (equals, contains) 3. **AVAILABLE\_CONDITION\_CONCATENATIONS**: Defines how multiple conditions are combined (AND/OR) 4. **DEFAULT\_CONDITION**: Default values when adding a new condition row 5. **CSS\_CLASSES**: Centralized CSS class names for consistent element selection 6. **MESSAGES**: User-facing messages for validation and display 7. **STYLES**: Complete styling configuration for the modal UI ::: tip Customization You can easily extend the available conditions by adding more field types to `AVAILABLE_CONDITION_NAMES` or operations to `AVAILABLE_CONDITION_OPERATIONS`. For example, you could add fields for `$LOCATION`, `$AGE_GROUP`, or operations like `starts_with`, `ends_with`. ::: ## Step 4: Build Utility Methods Implement helper methods for style conversion and dropdown management: ```javascript /** * Converts style object to inline style string * @param {Object} styleObj - Style object with camelCase properties * @returns {string} Inline CSS style string with kebab-case properties */ styleObjToString(styleObj) { return Object.entries(styleObj) .map(([key, value]) => { // Convert camelCase to kebab-case const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase(); return `${kebabKey}: ${value}`; }) .join('; '); } /** * Gets the value of a dropdown element * @param {HTMLElement} baseElement - The base element to search within * @param {string} identifierClass - The CSS class of the dropdown * @returns {string|null} The selected value or null if not found */ getDropdownValue(baseElement, identifierClass) { if (!baseElement) { baseElement = this.conditionsPopupElement; } const selectElement = baseElement.querySelector('select.' + identifierClass); return selectElement ? selectElement.value : null; } /** * Sets the value of a dropdown element * @param {HTMLElement} baseElement - The base element to search within * @param {string} identifierClass - The CSS class of the dropdown * @param {string} value - The value to set */ setDropdownValue(baseElement, identifierClass, value) { if (!baseElement) { baseElement = this.conditionsPopupElement; } const selectElement = baseElement.querySelector('select.' + identifierClass); if (selectElement) { selectElement.value = value; } } /** * Sets dropdown options and attaches event listeners * @param {HTMLElement} baseElement - The base element to search within * @param {string} identifierClass - The CSS class of the dropdown * @param {Array} options - Array of {label, value} option objects */ setDropdownOptions(baseElement, identifierClass, options) { if (!baseElement) { baseElement = this.conditionsPopupElement; } const selectElement = baseElement.querySelector('select.' + identifierClass); if (!selectElement) return; // Clear existing options selectElement.innerHTML = ''; // Add new options options.forEach(option => { const optionElement = document.createElement('option'); optionElement.value = option.value; optionElement.innerHTML = option.label; selectElement.appendChild(optionElement); }); // Remove existing event listeners to avoid duplicates const newSelectElement = selectElement.cloneNode(true); selectElement.parentNode.replaceChild(newSelectElement, selectElement); // Add focus/hover styles newSelectElement.addEventListener('focus', function() { this.style.borderColor = '#3b82f6'; this.style.boxShadow = '0 0 0 3px rgba(59, 130, 246, 0.1)'; }); newSelectElement.addEventListener('blur', function() { this.style.borderColor = '#e5e7eb'; this.style.boxShadow = 'none'; }); // Add change event to clear validation error newSelectElement.addEventListener('change', () => { this.hideValidationError(); }); } /** * Creates dropdown markup * @param {string} className - CSS class for the dropdown * @returns {string} HTML for the dropdown */ getDropdownMarkup(className) { return ``; } ``` ::: warning Important The `styleObjToString()` method is essential for converting JavaScript style objects into inline CSS strings. This approach ensures consistent styling across all modal elements. ::: ## Step 5: Create the Modal Structure Implement the modal creation method that generates the complete UI: ```javascript /** * Creates the modal HTML structure and appends it to the document body */ createConditionsPopup() { const div = document.createElement('div'); div.innerHTML = ` `; document.body.appendChild(div); this.conditionsPopupElement = document.getElementById('externalDisplayConditionsPopup'); // Attach event listeners this.attachModalEventListeners(); } /** * Generates the modal header section HTML * @returns {string} HTML string for the header section */ generateHeaderHTML() { return `

Display Conditions

`; } /** * Generates the modal content section HTML * @returns {string} HTML string for the content section */ generateContentHTML() { const CSS = MyExternalDisplayConditions.CSS_CLASSES; return `

Condition Rules

Remove all conditions
`; } /** * Generates the modal footer section HTML with disclaimer notice * @returns {string} HTML string for the footer section */ generateFooterHTML() { return `

⚠️ Notice: This popup window is not part of the plugin. It is intended solely for demonstration purposes and can be implemented independently in any desired way.

`; } /** * Attaches event listeners to modal elements after creation */ attachModalEventListeners() { this.conditionsPopupElement.querySelector('#closePopupButton') .addEventListener('click', this.closePopup.bind(this)); this.conditionsPopupElement.querySelector('#closeConditionsPopup') .addEventListener('click', this.cancelConditions.bind(this)); this.conditionsPopupElement.querySelector('#applyConditionsAction') .addEventListener('click', this.applyConditions.bind(this)); this.conditionsPopupElement.querySelector('#addNewCondition') .addEventListener('click', this.addConditionRow.bind(this)); this.conditionsPopupElement.querySelector('#removeConditionsPopup') .addEventListener('click', this.removeConditions.bind(this)); // Set up concatenation dropdown const CSS = MyExternalDisplayConditions.CSS_CLASSES; this.setDropdownOptions( this.conditionsPopupElement, CSS.DROPDOWN_CONCATENATION, MyExternalDisplayConditions.AVAILABLE_CONDITION_CONCATENATIONS ); this.setDropdownValue( this.conditionsPopupElement, CSS.DROPDOWN_CONCATENATION, MyExternalDisplayConditions.AVAILABLE_CONDITION_CONCATENATIONS[0].value ); } ``` ### Modal Structure Overview The modal consists of three main sections: 1. **Header**: Title and close button 2. **Content**: * Conditions table (dynamically populated) * Add Condition button * Concatenation selector (all/any) * Action buttons (Remove All, Cancel, Apply) 3. **Footer**: Informational disclaimer ## Step 6: Implement Condition Row Management Add methods for creating and managing individual condition rows: ```javascript /** * Creates HTML for a condition row * @param {string} deleteActionClass - Unique class for the delete button * @returns {string} HTML string for the condition row */ createConditionRowHTML(deleteActionClass) { const CSS = MyExternalDisplayConditions.CSS_CLASSES; return `
${this.getDropdownMarkup(CSS.DROPDOWN_NAME)} ${this.getDropdownMarkup(CSS.DROPDOWN_OPERATION)}

Welcome!

Your content here

Click Me