Skip navigation

Integrating your own content editing features

If you enjoy the Kentico Kontent editing experience but want to add features to cover your individual needs or integrate external services for things like e-commerce, you can extend your options by implementing your own features. Custom elements help you extend the built-in functionality of the Kentico Kontent UI and take the content editing experience to another level.

Premium feature

Custom elements require a Business plan or higherOpens in a new window.

Table of contents

    Custom elements are small HTML apps that exist in a sandboxed <iframe> and interact with Kentico Kontent through the Custom Elements API. They let you extend the set of default content type elements to include your own elements to fit your needs.

    To get an idea of the possibilities, imagine you have content editors who are used to writing in MarkdownOpens in a new window. You'd like to give them the opportunity to keep writing in the style they've become accustomed to and make transferring among your existing systems easier. Why not add a Markdown editor directly inside Kentico Kontent?

    This tutorial will show you how to create your own custom element to implement a Markdown editor. It's based on one of our sample custom elementsOpens in a new window that uses SimpleMDEOpens in a new window to create a text area where users can edit text in Markdown syntax and get a preview of how it might look.

    Creating your custom element

    1. Create a page

    The first thing to do is to create a basic HTML page to hold your custom element.

    • HTML
    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Markdown</title> <!-- libs --> <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script> </head> <body> <!-- Custom element HTML --> <textarea id="editor"></textarea> <!-- Custom element code --> <script> // The place for your code </script> </body> </html>
    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Markdown</title> <!-- libs --> <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script> </head> <body> <!-- Custom element HTML --> <textarea id="editor"></textarea> <!-- Custom element code --> <script> // The place for your code </script> </body> </html>

    Here you can see a basic page with jQueryOpens in a new window and SimpleMDEOpens in a new window in the head and a body with places for the editor itself (the text area) and code that will fill in the editor with what you want.

    2. Include the Custom Elements API

    The Custom Elements API is a JavaScript API that allows you to create Custom elements for Kentico Kontent. Add it to your page in the head.

    • HTML
    <script src="https://app.kontent.ai/js-api/custom-element/v1/custom-element.min.js"></script>
    <script src="https://app.kontent.ai/js-api/custom-element/v1/custom-element.min.js"></script>

    3. Add styles

    In this case, you might want your editor to look slightly different from the rest of the Kentico Kontent UI, so it's clear to your editors that they'll be writing differently there. You can use the CSS for SimpleMDE and just a touch of custom styles to make it fit.

    • HTML
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css"> <style> body { margin: 0; padding: 0; overflow: hidden; } </style>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css"> <style> body { margin: 0; padding: 0; overflow: hidden; } </style>

    If you wanted to make your custom element look consistent with the rest of the Kontent UI, you can use the prepared styling in GitHubOpens in a new window. There you'll find custom-element.css with styles and kentico-icons-v1.6.0.woff for all the Kontent icons, plus some examples in examples.html.

    If you'd like to go this route, it'd be best to clone the files and host them yourself.

    4. Initialize your custom element

    To get your element started, you need to get information from Kentico Kontent about where your element is. This helps you determine things like the size the element should be and whether the item can be edited by the current user, which will let you know whether or not to allow editing of your custom editor.

    To get this information, use the init method from the API by adding the following code.

    • JavaScript
    function initCustomElement() { try { CustomElement.init((element, _context) => { // Sets up editor with initial value, state (whether disabled) , and configuration options initializeEditor(element.value, element.disabled, element.config); updateSize(); }); // Reacts to changes in disabling (e.g., when publishing the item) CustomElement.onDisabledChanged(updateDisabled); } catch (err) { // Sends message to console and editor if initialization failed (for example, the page is displayed outside the Kontent UI) console.error(err); initializeEditor(err.toString()); } } initCustomElement();
    function initCustomElement() { try { CustomElement.init((element, _context) => { // Sets up editor with initial value, state (whether disabled) , and configuration options initializeEditor(element.value, element.disabled, element.config); updateSize(); }); // Reacts to changes in disabling (e.g., when publishing the item) CustomElement.onDisabledChanged(updateDisabled); } catch (err) { // Sends message to console and editor if initialization failed (for example, the page is displayed outside the Kontent UI) console.error(err); initializeEditor(err.toString()); } } initCustomElement();

    Here, you've added the method and used it to call a function to set up your editor. That function's parameters will tell your editor whether there's already a value, if the editor should be disabled, and what options you've set up inside Kentico Kontent.

    The initialization also includes a call to update the size of the editor to fit the UI. Additionally, it uses the onDisabledChanged method to detect any changes in the element's state so that if the item is disabled in the UI (for example, if it's published), the element will update its state.

    There's also error handling that will place the error both in the console and in the editor itself as its initial value, so you'll be sure to find any errors.

    5. Initialize the Markdown editor

    Now that the element is set up and ready to call the Markdown editor, you have to define the function for the editor.

    • JavaScript
    var editorInstance = null; function initializeEditor(initialValue, isDisabled, config = {}) { // Sets up a SimpleMDE editor editorInstance = new SimpleMDE({ initialValue: initialValue || '', element: document.getElementById('editor'), // Checks the configuration set inside Kontent for whether to check spelling spellChecker: config.spellChecker, status: ["lines", "words"], // Sets the actions available in the editor toolbar, including a custom action toolbar: [ "bold", "italic", "heading", "code", "|", "quote", "unordered-list", "ordered-list", "|", "link", { name: "image", action: insertImage, className: "fa fa-picture-o", title: "Insert Image" }, "|", "preview" ] }); // Updates initial state (see step 8) updateDisabled(isDisabled); // Reacts to window resize to adjust the height window.addEventListener('resize', updateSize); }
    var editorInstance = null; function initializeEditor(initialValue, isDisabled, config = {}) { // Sets up a SimpleMDE editor editorInstance = new SimpleMDE({ initialValue: initialValue || '', element: document.getElementById('editor'), // Checks the configuration set inside Kontent for whether to check spelling spellChecker: config.spellChecker, status: ["lines", "words"], // Sets the actions available in the editor toolbar, including a custom action toolbar: [ "bold", "italic", "heading", "code", "|", "quote", "unordered-list", "ordered-list", "|", "link", { name: "image", action: insertImage, className: "fa fa-picture-o", title: "Insert Image" }, "|", "preview" ] }); // Updates initial state (see step 8) updateDisabled(isDisabled); // Reacts to window resize to adjust the height window.addEventListener('resize', updateSize); }

    The first section sets up an instance of a SimpleMDEOpens in a new window editor. It takes its initial value from the value it got in step 4 or it uses an empty string. Then it finds the text area with the ID element and replaces it with the editor. Then it looks in the configuration options to see if you've enabled spellcheck. Lastly, it presents a selected set of tools to add to the toolbar, including one custom tool to pick images from Kentico Kontent that you'll define later.

    The function to set up the editor also includes a recheck of the disabled state and a listener to resize the element if the browser window changes its size.

    6. Set the element's value

    In order for Kentico Kontent to recognize the value of the element and include it in the item, you have to tell Kontent what the value is. Add the following code to the end of your initializeEditor function.

    • JavaScript
    editorInstance.codemirror.on('changes', () => { setValue(editorInstance.value()); updateSize(); });
    editorInstance.codemirror.on('changes', () => { setValue(editorInstance.value()); updateSize(); });

    And define a separate function to send the value to Kentico Kontent.

    • JavaScript
    function setValue(value) { if (!isDisabled) { CustomElement.setValue(value || null); } }
    function setValue(value) { if (!isDisabled) { CustomElement.setValue(value || null); } }

    Now any time the value of your editor changes (and the element is not disabled), it will use the setValue method to tell Kentico Kontent what the value is. And of course, once again there's a check that the size is correct. Maybe it's time to finally do something about that?

    7. Set the element's size

    It's important to make sure your element fits within the editing space in the Kontent UI. It's also important to do it quickly to avoid screen flickering while scrolling and other related issues. Now you can define a function to make sure it does.

    • JavaScript
    function updateSize() { // Updates the custom element height in the Kentico UI. const height = Math.ceil($("html").height()); CustomElement.setHeight(height); }
    function updateSize() { // Updates the custom element height in the Kentico UI. const height = Math.ceil($("html").height()); CustomElement.setHeight(height); }

    You have a nifty little function to find how tall it should be and then you set it with the setHeight method

    You also saw a couple of times above a call to update the disabled state through updateDisabled, so you should similarly fix that.

    8. Ensure the element is disabled correctly

    When an item is disabled in the Kontent UI, it's important to disable your custom editor, too. Otherwise, you'll run into issues with updates to published items or users editing items they shouldn't have permissions for. Define your function for that now.

    • JavaScript
    var isDisabled = false; function updateDisabled(disabled) { if (editorInstance && editorInstance.codemirror) { const $toolbar = $('.editor-toolbar'); editorInstance.codemirror.options.readOnly = disabled; if (disabled) { $toolbar.hide(); } else { $toolbar.show(); } updateSize(); } isDisabled = disabled; }
    var isDisabled = false; function updateDisabled(disabled) { if (editorInstance && editorInstance.codemirror) { const $toolbar = $('.editor-toolbar'); editorInstance.codemirror.options.readOnly = disabled; if (disabled) { $toolbar.hide(); } else { $toolbar.show(); } updateSize(); } isDisabled = disabled; }

    This function uses its single parameter to determine whether to show or hide the editor toolbar and then whether to disable changes to the element's value (by properly setting isDisabled). 

    There's only one more function you've used but haven't defined: insertImage.

    9. Add the option to select assets from Kentico Kontent

    One of the advantages to the editor inside Kentico Kontent is the ability to select assets from the same place as your content is. If you were to use a custom editor without any changes, you'd lose that advantage. Luckily, SimpleMDEOpens in a new window allows you to simply define your own tools for the toolbar so you can make use of the assets you already have inside Kontent.

    In step 6, you used the action insertImage to let you add a button to select images from Kontent. Now it's time to define that function.

    • JavaScript
    async function insertImage() { // Opens an asset selection dialog in Kontent const assets = await CustomElement.selectAssets({ allowMultiple: false, fileType: 'images' }); // Gets an ID of the first selected asset const assetId = assets[0].id; // Retrieves details of the asset specified by its ID const assetDetails = await CustomElement.getAssetDetails([assetId]); const cm = editorInstance.codemirror; const stat = editorInstance.getState(cm); const insertText = ["![", "](#url#)"]; _replaceSelection(cm, stat.image, insertText, assetDetails[0].url); }
    async function insertImage() { // Opens an asset selection dialog in Kontent const assets = await CustomElement.selectAssets({ allowMultiple: false, fileType: 'images' }); // Gets an ID of the first selected asset const assetId = assets[0].id; // Retrieves details of the asset specified by its ID const assetDetails = await CustomElement.getAssetDetails([assetId]); const cm = editorInstance.codemirror; const stat = editorInstance.getState(cm); const insertText = ["![", "](#url#)"]; _replaceSelection(cm, stat.image, insertText, assetDetails[0].url); }

    Here, you're using the selectAssets method to allow editors to select assets inside Kontent. You've got the configuration set up so you only select one asset at a time and they have to be images. The selectAssets method returns an array of asset references, each with an asset id property.

    You then feed the only object in the array to the getAssetDetails method and retrieve details about the selected asset into the assetDetails variable. You use the variable to access properties of the asset object, such as its URL. This URL is what will be added to your Markdown editor.

    Now, define your function to add this URL to your editor (this is just a modified version of the function from SimpleMDEOpens in a new window, so you don't have to worry about the details very much).

    • JavaScript
    function _replaceSelection(cm, active, startEnd, url){ var text; var start = startEnd[0]; var end = startEnd[1]; var startPoint = cm.getCursor("start"); var endPoint = cm.getCursor("end"); if(url) { end = end.replace("#url#", url); } if(active) { text = cm.getLine(startPoint.line); start = text.slice(0, startPoint.ch); end = text.slice(startPoint.ch); cm.replaceRange(start + end, { line: startPoint.line, ch: 0 }); } else { text = cm.getSelection(); cm.replaceSelection(start + text + end); startPoint.ch += start.length; if(startPoint !== endPoint) { endPoint.ch += start.length; } } cm.setSelection(startPoint, endPoint); cm.focus(); }
    function _replaceSelection(cm, active, startEnd, url){ var text; var start = startEnd[0]; var end = startEnd[1]; var startPoint = cm.getCursor("start"); var endPoint = cm.getCursor("end"); if(url) { end = end.replace("#url#", url); } if(active) { text = cm.getLine(startPoint.line); start = text.slice(0, startPoint.ch); end = text.slice(startPoint.ch); cm.replaceRange(start + end, { line: startPoint.line, ch: 0 }); } else { text = cm.getSelection(); cm.replaceSelection(start + text + end); startPoint.ch += start.length; if(startPoint !== endPoint) { endPoint.ch += start.length; } } cm.setSelection(startPoint, endPoint); cm.focus(); }

    Now you should have all your definitions to have a custom element.

    Final HTML page

    Your custom element might now look something like the following.

    • HTML
    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Markdown</title> <!-- libs --> <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css"> <script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script> <!-- Kentico Custom elements API--> <script src="https://app.devkontentmasters.com/js-api/custom-element/v1/custom-element.js"></script> <!-- Custom elements styles --> <style> body { margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <!-- Custom element HTML --> <textarea id="editor"></textarea> <!-- Custom element code --> <script> var isDisabled = false; var editorInstance = null; function updateDisabled(disabled) { if (editorInstance && editorInstance.codemirror) { const $toolbar = $('.editor-toolbar'); editorInstance.codemirror.options.readOnly = disabled; if (disabled) { $toolbar.hide(); } else { $toolbar.show(); } updateSize(); } isDisabled = disabled; } function initializeEditor(initialValue, isDisabled, config = {}) { // Sets up a SimpleMDE editor editorInstance = new SimpleMDE({ initialValue: initialValue || '', element: document.getElementById('editor'), // Checks the configuration set inside Kontent for whether to check spelling spellChecker: config.spellChecker, // Sets the actions available in the editor toolbar, including a custom action toolbar: [ "bold", "italic", "heading", "code", "|", "quote", "unordered-list", "ordered-list", "|", "link", { name: "image", action: insertImage, className: "fa fa-picture-o", title: "Insert Image" }, "|", "preview" ] }); // Updates initial state (see step 8) updateDisabled(isDisabled); // Reacts to window resize to adjust the height window.addEventListener('resize', updateSize); // Reacts to window editor value changes to update the value in Kontent editorInstance.codemirror.on('changes', () => { setValue(editorInstance.value()); updateSize(); }); } function updateSize() { // Updates the custom element height in the Kontent UI const height = Math.ceil($("html").height()); CustomElement.setHeight(height); } function setValue(value) { // Sends updated value to Kontent (if the editor is empty, will send null so the element won't meet a required condition). if (!isDisabled) { CustomElement.setValue(value || null); } } function initCustomElement() { try { CustomElement.init((element, _context) => { // Sets up editor with initial value, state (whether disabled) , and configuration options initializeEditor(element.value, element.disabled, element.config); updateSize(); }); // Reacts to changes in disabling (e.g., when publishing the item) CustomElement.onDisabledChanged(updateDisabled); } catch (err) { // Sends message to console and editor if initialization failed (for example, if the page is displayed outside the Kontent UI) console.error(err); initializeEditor(err.toString()); } } initCustomElement(); async function insertImage() { // Opens an asset selection dialog in Kontent const assets = await CustomElement.selectAssets({ allowMultiple: false, fileType: 'images' }); // Gets an ID of the first selected asset const assetId = assets[0].id; // Retrieves details of the asset specified by its ID const assetDetails = await CustomElement.getAssetDetails([assetId]); const cm = editorInstance.codemirror; const stat = editorInstance.getState(cm); // Specifies the text to insert into the editor after picking an asset const insertText = ["![", "](#url#)"]; // Replaces #url# with the actual URL to the asset _replaceSelection(cm, stat.image, insertText, assetDetails[0].url); } function _replaceSelection(cm, active, startEnd, url) { var text; var start = startEnd[0]; var end = startEnd[1]; var startPoint = cm.getCursor("start"); var endPoint = cm.getCursor("end"); if (url) { end = end.replace("#url#", url); } if (active) { text = cm.getLine(startPoint.line); start = text.slice(0, startPoint.ch); end = text.slice(startPoint.ch); cm.replaceRange(start + end, { line: startPoint.line, ch: 0 }); } else { text = cm.getSelection(); cm.replaceSelection(start + text + end); startPoint.ch += start.length; if (startPoint !== endPoint) { endPoint.ch += start.length; } } cm.setSelection(startPoint, endPoint); cm.focus(); } </script> </body> </html>
    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Markdown</title> <!-- libs --> <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css"> <script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script> <!-- Kentico Custom elements API--> <script src="https://app.devkontentmasters.com/js-api/custom-element/v1/custom-element.js"></script> <!-- Custom elements styles --> <style> body { margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <!-- Custom element HTML --> <textarea id="editor"></textarea> <!-- Custom element code --> <script> var isDisabled = false; var editorInstance = null; function updateDisabled(disabled) { if (editorInstance && editorInstance.codemirror) { const $toolbar = $('.editor-toolbar'); editorInstance.codemirror.options.readOnly = disabled; if (disabled) { $toolbar.hide(); } else { $toolbar.show(); } updateSize(); } isDisabled = disabled; } function initializeEditor(initialValue, isDisabled, config = {}) { // Sets up a SimpleMDE editor editorInstance = new SimpleMDE({ initialValue: initialValue || '', element: document.getElementById('editor'), // Checks the configuration set inside Kontent for whether to check spelling spellChecker: config.spellChecker, // Sets the actions available in the editor toolbar, including a custom action toolbar: [ "bold", "italic", "heading", "code", "|", "quote", "unordered-list", "ordered-list", "|", "link", { name: "image", action: insertImage, className: "fa fa-picture-o", title: "Insert Image" }, "|", "preview" ] }); // Updates initial state (see step 8) updateDisabled(isDisabled); // Reacts to window resize to adjust the height window.addEventListener('resize', updateSize); // Reacts to window editor value changes to update the value in Kontent editorInstance.codemirror.on('changes', () => { setValue(editorInstance.value()); updateSize(); }); } function updateSize() { // Updates the custom element height in the Kontent UI const height = Math.ceil($("html").height()); CustomElement.setHeight(height); } function setValue(value) { // Sends updated value to Kontent (if the editor is empty, will send null so the element won't meet a required condition). if (!isDisabled) { CustomElement.setValue(value || null); } } function initCustomElement() { try { CustomElement.init((element, _context) => { // Sets up editor with initial value, state (whether disabled) , and configuration options initializeEditor(element.value, element.disabled, element.config); updateSize(); }); // Reacts to changes in disabling (e.g., when publishing the item) CustomElement.onDisabledChanged(updateDisabled); } catch (err) { // Sends message to console and editor if initialization failed (for example, if the page is displayed outside the Kontent UI) console.error(err); initializeEditor(err.toString()); } } initCustomElement(); async function insertImage() { // Opens an asset selection dialog in Kontent const assets = await CustomElement.selectAssets({ allowMultiple: false, fileType: 'images' }); // Gets an ID of the first selected asset const assetId = assets[0].id; // Retrieves details of the asset specified by its ID const assetDetails = await CustomElement.getAssetDetails([assetId]); const cm = editorInstance.codemirror; const stat = editorInstance.getState(cm); // Specifies the text to insert into the editor after picking an asset const insertText = ["![", "](#url#)"]; // Replaces #url# with the actual URL to the asset _replaceSelection(cm, stat.image, insertText, assetDetails[0].url); } function _replaceSelection(cm, active, startEnd, url) { var text; var start = startEnd[0]; var end = startEnd[1]; var startPoint = cm.getCursor("start"); var endPoint = cm.getCursor("end"); if (url) { end = end.replace("#url#", url); } if (active) { text = cm.getLine(startPoint.line); start = text.slice(0, startPoint.ch); end = text.slice(startPoint.ch); cm.replaceRange(start + end, { line: startPoint.line, ch: 0 }); } else { text = cm.getSelection(); cm.replaceSelection(start + text + end); startPoint.ch += start.length; if (startPoint !== endPoint) { endPoint.ch += start.length; } } cm.setSelection(startPoint, endPoint); cm.focus(); } </script> </body> </html>

    If you have something similar to this, you're ready to host the element and prepare to put it into use.

    Ensuring secure hosting

    Custom elements are HTML applications loaded in an <iframe>. They need to be hosted so the browser can fetch the file. The hosting of your Custom elements source code needs to be done on your end.

    • Always use a secured connection (HTTPS) for your hosted code URL. For example, https://example.com/custom.html.
    • If you first want to test your implementation locally, you need to generate a self-signed HTTPS certificate.
    • You can use the X-frame-option HTTP response header to specify an allowed origin, in this case, https://app.kontent.ai. For more details, see the MDN Web DocsOpens in a new window.

    Your extension is separated from the rest of Kentico Kontent through the sandbox attributeOpens in a new window on the <iframe>. The following sandbox flags are enabled:

    • allow-forms – allows form submission
    • allow-scripts – allows JavaScript to run inside the <iframe>
    • allow-popups – allows popups
    • allow-same-origin – allows the document to maintain its origin

    Taking all this into account, make sure you have your HTML page hosted somewhere secure and get its URL. Once you have secure hosting ensured, you can add your element to your project.

    Displaying your custom editor in Kentico Kontent

    To have your editor appear in items, you need to add a custom element to a content type.

    1. In the Kentico Kontent app menu, choose .
    2. Choose an existing content type or create a new one by clicking Create new.
    3. Click Custom element on the right to add a new element.
    4. Give it a name.
    5. Add your Hosted code URL (HTTPS).
    6. Under Parameters {JSON}, add in a JSON object { "spellChecker": true } (or false if you don't want the spell check enabled).
    7. (Optional) Set the element as required.
    8. Click Save changes.
    A screenshot of a configured custom element.

    Note that if you set a custom element as required, Kentico Kontent will check if the element contains any value other than null. Any other value is considered valid and so won't prevent an item from getting published.

    The JSON parameters enable you to configure your element and reuse it in various contexts. In some cases you might want different settings for different projects, content types, or even elements, and you can configure that through the JSON parameters. So in this case, you can turn spellcheck on and off in different places. You could also change the HTML so that all of the tools could be set via JSON parameters so each custom element would have a different toolset. It's up to you.

    Never store your API keys or any other sensitive data in the JSON parameters field. It's not secure!

    Seeing the final result

    Your Markdown editor can now be used inside content items similarly as in the animation below.

    Writing content in a markdown editor

    How your custom Markdown editor might appear inside a content item.

    And then when you call that item via the Delivery API, you'll get your markdown in the value of your element, similar to the following.

    • JSON
    { ... "markdown": { "type": "custom", "name": "Markdown", "value": "# Heading\nThis is markdown\n\n* Item 1\n* Item 2\n\n![A coffee farmer](https://assets-us-01.kc-usercontent.com/975bf280-fd91-488c-994c-2f04416e5ee3/c01cb1d7-5479-41dd-8bf9-902a9667360a/Coffee-Farmer.jpg)" } ... }
    { ... "markdown": { "type": "custom", "name": "Markdown", "value": "# Heading\nThis is markdown\n\n* Item 1\n* Item 2\n\n![A coffee farmer](https://assets-us-01.kc-usercontent.com/975bf280-fd91-488c-994c-2f04416e5ee3/c01cb1d7-5479-41dd-8bf9-902a9667360a/Coffee-Farmer.jpg)" } ... }

    Sample custom elements

    A variety of sample custom elements have been prepared for you to use. You can see how they look in action in the Custom Elements GalleryOpens in a new window or look at their code in the custom elements GitHub repositoryOpens in a new window. By using these prepared elements, you won't need to start implementing your UI extensions completely from scratch.

    The examples are open source so you can adjust them to fit to your specific needs. If you plan on using these sample elements in your own production projects, we recommend cloning the repository so any changes to the elements won't affect your applications.

    What's next?

    • Check out the Custom Elements API specification to see all the available API methods.
    • See how you can use custom elements to integrate third-party services such as Shopify and Optimizely.