Getting Started

The Element API allows developers to extend Pro and Cornerstone through the creation of custom elements. This article will guide you through the process of creating an element, while also breaking down the different parts of an element.

You can enable the application dev tools with this constant:

You should now see a bug icon at the bottom of the Bar while using the builders. At the time of this writing, its only feature is to display the values of your currently inspected element.

An element is created by defining values, connecting them to controls to allow user customization, then displaying them on the site using a render function. All of this in done through the element's definition file. Let's begin creating an element.


The following code can be placed inside functions.php of a child theme, or in your custom plugin. It assumes we've created a my-element.php file.

And here's our first version of my-element.php.

This is what we call an Element Definition. You can browse the native element definitions in cornerstone/includes/elements/definitions. Keep in mind many of them use more advanced concepts than the file above. This primer will start with an element in the simplest form, then add several layers to make it more powerful.

Try it out! You'll have a working element. After adding it in the builder and inspecting the element you can try changing the content and see it update in the live preview. I know, plenty going on here. Let's start breaking down what this code means and how it works.


Working from the top down, the title is pretty self explanatory so let's jump right into values.

We are declaring a single value with the name text_content That key is what we will use anytime we want to refer to this value in other parts of our code. Here is the function signature for cs_value

The first argument ($default) defines the initial value. You probably already realized that when you noticed your text was pre-populated with "I am a custom element" when you added it the first time. Skipping briefly to the third argument ($protected), we can use this to flag this key as "Content" for when element presets are saved and applied. When applying

Using the correct value of the second argument ($designation) is important for managing the live preview performance of your element. It tells the rendering system the kind of value we are working with. Here are the main designation types:


The key take away is that the builders are smart enough to use the least amount of resources when rendering elements. Use style (covered later) and attr when possible to avoid unnecessary requests to the server. Let's try adding an attr designated value. Update your my-element.php to the following:

Here are the changes we introduced:

You'll also notice a new function: cs_atts. This is simply a helper method that returns a string of HTML attributes from an array and applies the correct output sanitization.

Using your browser's developer tools, inspect your element in the DOM. See how data-my-attribute is now on your element? Start typing in the text input we've connected to that value. Watch how it updates in realtime without any HTTP requests to the server!

Control Navigation

Before looking at controls themselves, let's start by understanding control_navand how it is used for organization. Here is what we have currently:

This is used to organize your controls into digestible sections. Using a colon delineates the subsections of a parent group. This means "My Element" will be a top level group with "Setup" and "Design" as navigation links inside it. Our Testimonial element is a good example of using more groups.

If you try out the Testimonial element, you'll see how the code relates to the resulting navigation structure. (You might be wondering how "Customize" got in there. It comes from a "partial" which we'll learn about later.)

For smaller groups we use :setup and :design as sub navigation items by convention. We would recommend using this when appropriate as Cornerstone and Pro users are already familiar with it, but you're more than welcome to add whatever navigation items you want.

Remember that a navigation item will be hidden if it has no controls associated with it, or all of its controls are hidden (see Control Conditions).


If you're thinking about how you could integrate a shortcode from your plugin as a cornerstone element, you've probably already caught on to how powerful this is. But honestly, this element isn't all that interesting just yet. Now let's talk about how you can add more controls to give your users more customization power.

Control Types

There are obviously more control types than text editors. Let's pretend that our custom_attribute is something that users can toggle on or off rather. This means we can use a choose control. Update the type of your control to choose and add an options array with some choices that include On and Off.

We also want to update our value to have a boolean default to match our new control

Now if you inspect the element you will see the choose control that allows toggling between on and off. You can use your browser's developer tools to observe it switch back and forth.

Control Groups

Compared to other elements you've seen before, you'll notice we're wasting a bunch of space by placing our two controls in separate boxes. By default, every top level control has a separate "box". This isn't very efficient when using the Inspector horizontally. Fix this by combining our two controls into a single group control. This is a special control type used for organizing controls.

The nested controls don't need to specify which navigation item them belong to because that is the responsibility of the group. Not all control types (border or link for example) can be used inside of a group because they require more space. Use the control reference to identify which controls can be used this way.

Control Conditions

What if we want another control to appear if our Custom Attribute is turned on? Let's add a new control that employs a condition. First we need a value:

Next we'll add a text control. We'll also present it as monospace. Place this immediately after the existing choose control

If you test this out, you'll notice toggling On/Off doesn't do anything. We need to add a condition to our control. Here's the updated code:

Now the content text input only shows when the choose control is turned on. We're using the simplest condition shorthand that says "Show this control if the value of custom_attribute is true". The API supports checking multiple conditions and offer several operation types. Learn more about this [LINK]

First Styling Controls

Ready for some styling? Let's add some values that we will connect to some styling controls. Add the following lines to your values array:

Some controls allow you to connect multiple values. For example, a single color control can output two colors. Let's use this to add a background color. Add the following at the end of your group control's control list (after our choose for custom_attribute ).

Inspect your element and you'll see two color pickers side by side. Because we don't have generated CSS yet, now would be a good time to try out the dev tools. If you enabled them with the CS_APP_DEV_TOOLS constant you can click the "Bug" icon in the Bar. You'll see an overlay cover the preview area and show the values of your element. Adjust the color pickers and you can see the output changing.

Next we'll add a margin control that we will use to allow customizing spacing on our main element. It can only be used top level (not inside a group), so add this control immediately following the group control.

Now we're back to two top level boxes!

Generated CSS

We have a unique "Generated CSS" system that takes a style template (a string) and uses it to create unique styles for each element. It is optimized so that when elements of the same type have the same values, a combined CSS selector is used with one property declaration. The first thing we need to do is update our render function to output the element's unique classname. This is automatically available for you as it is passed through $data under the key mod_id. Here is the updated render function:

The cs_attr_class function accepts unlimited string arguments and combines them into one class. Now we have a unique class attached to our element that we can use to apply styles. On the front end the class will be a unique identified based on the current context (page, header, etc.) and in the builders it will be a random string.

To begin outputting styles, we need to add a style template to our definition. In the array passed to cs_register_element add the following. Putting it above the render callback is a good place.

Here is the function itself which can be added above your my_element_render function.

The function needs to return a string, so we use some output buffering for the sake of readability. You could also place these style in a separate file. This is how the sample element download is setup. For example:

Now that our template is loading, we can test the element. As you adjust your controls the CSS will update in the live preview. You can learn more about how to write these templates in the Style Template Syntax section of the API Reference.

Upgrading to a Builder callback

Before we advance to some of the most powerful API features like Partials, we'll need to make a slight adjustment to our definition file. Here's an updated my-element.php including all the changes we've made so far as well as the builder callback.

What we've done is taken our code definition and split it into parts. This line tells the builder to call the my_element_builder_callback function.

The array returned from your callback will update the element definition with the new values. The reason we do this is because the builder callback doesn't run on the front end. For performance, we should avoid running any code that isn't essential to the front end. The cumulative native library represents thousands of controls which would mean considerable memory usage if they were all defined on every front end page load.

This adjustment also opens us up to builder only API features like some abstraction techniques and of course Partials.


This is where you really start to see how the API can be leveraged. What if you want to start with our native button element and add a few additional controls? Or what if you want to reproduce one of our modal elements but supply your own content? Partials are the solution.

Adding the Customize Partial

For our first example, we will learn how to add those "Customize" controls. We called this the Omega partial, because it goes at the end of nearly ever native element. Remember that elements require a combination of both values and controls. We'll start by defining the valueswe need. Update your definitions values to look like this:

The cs_compose_values function takes an unlimited number of arguments. If you pass an array, those values will pass through. If you pass a string, it will locate predefined values for that key. If you take a look in cornerstone/includes/elements/values.php you can see all the predefined values available. To summarize foromega, we are defining the values below that will also have controls associated with them.

We'll also want to compose our controls in a similar fashion. Update your builder callback to look like this (actual controls omitted for brevity.)

The cs_partial_controls function returns an array containing controls and control_nav. Then cs_compose_controls combines them into a single output. To make this work on the front end we need to update our render function to use these new values. We only need to add the ID attribute and update the class attribute. Element CSS and breakpoint visibility are automatically handled behind the scenes.



Another common partial you may want to use is the advanced background. As with Omega, we need to update our values, controls, and render function. The code examples in this section have areas shortened for brevity. Don't remove any existing values or controls.

First we'll update our values. Simply add bg to the list.


Here's what we need to add the partial controls. Notice I left a commented out condition line. Some partials like bg support conditions to hide/show all their controls. We use this in native elements to allow users to turn advanced backgrounds on or off.

Finally, let's get this outputting in our render function.

Two new functions have just been introduced. cs_get_partial_view is used to get markup for partials that have their own view. The first parameter is the partial view name, and the second is the data you want to pass into that partial. This is very much like our element's render function and partial views expect data that corresponds with the partial values.

To populate the correct data, we use cs_extract to filter our element data into exactly what this partial needs. For the background, we're requesting that we only receive values that start with a bg prefix.

Did you try it out? Uh oh, you'll have noticed that the background partial bleeds out into the parent element. This is because bg outputs a div that has position: absolute. To correct this, we need to make our element use relative positioning. We'll make that adjustment in the style template:

That's it for the walkthrough! The remainder of this article doesn't use the same code examples we've been working with. We'll continue to scratch the surface of other important Element API concepts, and there's more to learn by combing through the API Reference.

Custom Element Icon

You can add a custom element icon by providing an inline SVG in your definition under the icon key. For example:

Standard / Advanced Controls

Users can toggle Advanced Mode from their preferences. Everything we've done so far is what is shown when this is turned on. When Advanced Mode is turned off, we can provide a standard mapping that is more simple and doesn't have multiple control navigations. There are three sections we can map controls to:

Here's a code example of adding these keys to your builder callback:

cs_compose_controls always adds controls to the list in the order that they are passed in. You may run into a situation where you want more advanced controls to appear after the background partial. To accomplish this, just add another array after the partial controls.

For a complex example of this, see the native Card element definition (cornerstone/includes/elements/definitions/card.php).

Common Abstractions

Frequently used data

Remember how we created a choose control with On/Off? This is something very common in our native elements, so we used an abstraction to help reduce repeated code. You can replace the choices with the following cs_remember call:

You can find a handful of other common things we abstracted in the cornerstone/includes/elements/registry-setup.php file. You can also make your own by using the cs_remember function. This is handy if you have multiple elements that need similar options.

These can only be used inside the builder functions to avoid unnecessary memory usage on the front end.

Control Shorthand

Styling controls like margin, padding, border, etc. are prolific in our native elements, so to reduce boilerplate and enforce a convention we offer the cs_control function. To demonstrate this, the margin control we created in the styling section above can be replaced with this code:

Using cs_control is completely optional - but we rely on it heavily so you should be aware of what it does as you explore the native element code.


We've delved into creating custom elements for the Cornerstone in the X and Content Builder in the Pro theme. We've learned how every element has a definition which is primarily composed of values, controls, and a render function. To go more in-depth be sure to look over the API Reference.