How To Insert Other Content Into Oxygen Repeaters and Easy Posts - Mega Tutorial

By Robert Chan
 on June 28, 2022

How To Insert Other Content Into Oxygen Repeaters and Easy Posts - Mega Tutorial

By Robert Chan
 on June 28, 2022

Oxygen Builder's Repeater and Easy Posts elements are quick and easy ways to display a list of posts.

This guide will show you different ways of inserting "other items" within a list for whatever reason you need.

To avoid confusion I will use "Other Item(s)" for items that are not queried by the original Repeater/Easy Post query

This is a single Repeater


Repeater/Easy Posts elements show a list of posts. That list is restricted by the parameters set in the query used to fetch the posts.

You can format the appearance of each item, and the layout of the list, and that's all. Sometimes that's all you need but some other times you may want to alter the list to include other items that were not originally fetched. Other items like:

  • a call to action item (such as a "Read More" link to the whole archive) in the middle of the list instead of the end of the list
  • some informative text describing the items inside the list, breaking up the flow of information to keep it interesting
  • some decorative element
  • a whole other list within the list
  • an ad banner or other promotional item

Oxygen Builder Course - Coming Soon!

The Oxygen Builder Mastery course will bring you from beginner to professional - ACF, MetaBox & WooCommerce modules included.


I will use some of the examples above to show you the various ways of adding other items to a list.

The methods from super simple to complicated are:

  • JQuery
  • using the the_posts hook
  • editing PHP templates (Easy Posts)
  • using the the_posts hook again but adding non-Post items like Terms (Categories/Taxonomies)

In my examples I will use a custom post type Destinations which is just a list of cities.


  • The other item will be a child of the Repeater/Easy Post element because it is inserted inside of it and will be subject to any layout rules set from the parent unless overridden, which I won't do in my examples.
  • The list changes will not be seen in the Oxygen editor.
  • The Easy Post PHP Template method accesses a non-documented variable that could change in a future Oxygen update but not likely.

Adding some other element inside a Repeater with JQuery

Add Banner inside Repeater

We'll use JQuery to insert an element at some index inside the Repeater/Easy Post.

The Steps

  1. Create a Repeater or Easy Post with a Custom Query with post type destinations. Set its ID to example-repeater-1.
  2. The items are the post title and featured image as the background.
  3. In this example, I used a Grid Layout and set the 4th item to span 2 columns in the row. This is where my item will be inserted.
  4. The other item is a div with height 100% and width 100% and I made it a simple banner with a call to action button. Set its ID to guide-ad-banner.
  5. I place the other item below the repeater but it can go anywhere since the script will move it.

The JQuery

Insert a Code Block into your page and add the following to the Javascript section, or inside <script> tags in the PHP / HTML section.

jQuery(($) => {
  const allRepeaterDivs = $("#example-repeater-1 > div");
  const insertionIndex = allRepeaterDivs.length > 2 ? 2 : allRepeaterDivs.length - 1;
});Code language: JavaScript (javascript)

Here is a quick explanation of the script.

  1. After the page is loaded / ready, we get the list of all divs that are children of #example-repeater-1. These are the individual items in the repeater.
  2. Calculate the index to insert this item, ideally I want to insert it after the 3rd item (index 2, indices start from 0 here), but if the number of items is less than 2 then insert it at the end. Of course, I already know the size of your result beforehand so this calculation is just a precaution in case I change it to a query where less than 3 items are returned.
  3. Lastly, it inserts the element that has the id #guide-ad-banner after the 3rd item, making my other item the 4th item.


Add Banner inside Repeater Result

On the front end, it takes the item with id guide-ad-banner and inserts it into the 4th spot of repeater with id #example-repeater-1.

Adding Other Item inside Repeater using the_posts hook

The the_posts filter hook in WordPress gives you access to the posts that have been fetched by a query. reference
We can use this hook to insert other posts that were not included in the original query.

For this example, I will insert a regular Post at the end of my repeater that used a queried for my destinations.

The Steps

  1. Create a repeater for the destinations post type and style the items with the title and featured image as div background.
  2. Add Code Blocks above and below the repeater.

The Code Blocks

First Code Block

Add this PHP code to the Code Block that is above the repeater.

function b58_add_cta_last( $posts, $query ) {

  $cta_post = get_post( 347 );
  $posts[] = $cta_post;

  return $posts;

add_filter( 'the_posts', 'b58_add_cta_last', 10, 2 ); 
?>Code language: HTML, XML (xml)

Let's go over this code.

  1. I use get_post to get the post with ID 347. This is the post that I want to insert at the end of the repeater.
  2. I append the previous post to the end of the the $posts array.
  3. Return the modified array.
  4. I add the previously created function to the the_posts filter hook.

Second Code Block

Add the following PHP to the Code Block under the repeater

remove_filter( 'the_posts', 'b58_add_cta_last', 10, 2 ); 
?>Code language: HTML, XML (xml)

This removes the previously added function as one to be used the the_posts filter hook is called. If it is not removed it will affect other queries run later on.


Add a regular post to end of Repeater

The post that we got with ID 347 was appended to the end of the repeater. From the image you see that there is a list of destinations and at the end is our regular post. So it could have been an article that talks about travel or how to book, or whatever.

If you add dynamic data like custom fields to the repeated items and that meta key does not exist on the other item, then it will be blank in that div. So you should use a condition to check if the meta key exists to avoid any weird layout issues.

Adding Another List inside Easy Posts Element

Add a List inside Easy Posts

This will involve editing the PHP Template for the Easy Posts element.

For this example, I will create a Reusable Part that contains a repeater with a list of posts, then insert it into an Easy Post element that queried for a different set of posts. I won't be using the Destinations CPT for this.

Create list as Reusable Part

Structure of Reusable Part
  1. Create a new Reusable Part that contains 3 main elements, a heading, a div for the repeater, and text.
  2. The repeater runs a query for the default post type and a category of news, and I set no_found_rows = true to disable pagination.
  3. The top text element just says BREAKING NEWS and the bottom text is a link to the news post archive.
  4. Add code blocks above and below the repeater, we have to alter the repeater query using these code blocks.

That is the structure of the Reusable Part.

Code Blocks

First Code Block

While this repeater exists inside the Easy Post element, its query should be independent of the Easy Post. One way the Easy Post element can affect the inner list is via pagination. If someone clicks on page 2 for the Easy Post element, by default the inner list will also fetch page 2. We have to prevent that.

First Code block above the repeater will be adding a pre_get_posts action that sets the paged query argument to 1, which makes it always return the first page.
If this repeater is on a static home page then use the page = 1 instead. (reference: WP_Query documentation)

function b58_set_paged( $query ) {
  // use paged if the repeater is on an archive page 
  // or page other than a static home page.
  $query->set( "paged", 1 ); 
  // use page if this repeater is on a static home page.
  // $query->set( "page", 1 ); 
  return $query;

add_action( 'pre_get_posts', 'b58_set_paged' );
?>Code language: HTML, XML (xml)

Second Code Block

The second code block removes the previously added action to prevent it from effecting future queries.

remove_action( 'pre_get_posts', 'b58_set_paged' );
?>Code language: HTML, XML (xml)

So now the repeater will always show the first page of results.

Take note of this Reusable Part's template ID, you can get from the address bar in the WP Dashboard.


Oxygen Builder Course - Coming Soon!

The Oxygen Builder Mastery course will bring you from beginner to professional - ACF, MetaBox & WooCommerce modules included.

The Easy Post

  1. On the actual page where I want to add the easy post, I add it to that page and I picked the Masonry preset.
  2. I change the query to posts of some other category other than news.
  3. I set the number of posts per page to an odd number because I'll be manually adding 1 extra post to make it even so the grid is neat.

Template PHP

In the Easy Posts style panel, I go to Template PHP. This is where you to edit the template used for every item in the Easy Post container (the template is repeated.)

Under the default template, paste the following php

$current_index = $this->query->current_post;
// Place this element after the 3rd post item, or 
// the last post if the # of posts is less than 3
// this ensure this element is added even if there 
// are less than 3 posts on the page.
$other_post_index = $this->query->post_count < 3 ? $this->query->post_count - 1 : 2;
if( $current_index == $other_post_index ) {
  echo "<div class='oxy-post'>";
  echo do_oxygen_elements( json_decode( get_post_meta( 321, 'ct_builder_json', true ), true ) );
  echo "</div>";
?>Code language: HTML, XML (xml)

Let's go over this code.

  1. First, we get the index of the item that is currently being displayed and set it to $current_index. Behind the scenes, Oxygen Builder is looping over every item returned from the query set in the Easy Posts element, and is executing the code inside the template for every post inside the list. We have a way of accessing the query variable with $this->query and the index of the currently processing item is current_post inside the query.
  2. I determine the index at which my other item should be inserted. I do this by checking the total number of items that will be processed, if there are less than 3 items then I will add the other item as the last item of the Easy Post. Otherwise, I will add it after the 3rd element (index starts at 0), making my other item the 4th item in the Easy Post grid.
  3. If we're at the index to add our item then I use Oxygen's built-in function do_oxygen_elements to render the reusable item (template ID 321) and wrap it in a div with class oxy-post.


Reusable Part inside Easy Posts

The Reusable Part is placed as the 4th item in the Easy Posts grid.

The reusable part's list is scrollable inside the list.

The pre_get_posts hook in the reusable part makes it always load the first page of posts so I can go to the other pages of the Easy Post and the results remain the same.

Using this method, you can replace the do_oxygen_elements part of the above code and write your own HTML to add whatever you want inside the Easy Posts element.

Remember your added element should conform to whatever sizing rules that have been set in the oxy-post class otherwise it may throw the rest of the list off.

Insert Terms elements inside Repeater

Terms Items added to Repeater

This method builds upon using the the_posts hook to insert links to terms inside a list of posts sorted by their terms.

So imagine a repeated list like:
Blue A, Blue B, Blue C, See all Blues, Red X, Red Y, Red Z, See all Reds, etc.

Super helpful on an e-commerce site when you want to list a few of featured items then link the term archive.

This method involves a lot of steps but it's pretty straightforward.

The Steps

  1. Setup Custom Fields
  2. Create a set of Helper functions
  3. Add a repeater that queries the types of posts we want.
  4. Modify the list of posts fetched with the above query with the the_posts hook

Setup Custom Fields

This is an optional step if you want to use a Featured Image with your Term object.

In my example, I use a Featured image as the background for the item. Terms by default do not have a Featured Image, so I added one to my Custom Taxonomy with Advanced Custom Fields.

The custom field is an Image field that returns the ID.

Helper Functions

I add the following helper functions in Code Snippets, do the same with whatever method you prefer.

function b58_create_post_from_term( $term, $post_type="post" ) {
  $post_id = -1 * $term->term_id; // negative ID, to avoid clash with a valid post
  $post = new stdClass();
  $post->ID = $post_id;
  $post->post_author = 1;
  $post->post_date = current_time( "mysql" );
  $post->post_date_gmt = current_time( "mysql", 1 );
  $post->post_title = $term->name;
  $post->post_content = $term->description;
  $post->post_status = "publish";
  $post->comment_status = "closed";
  $post->ping_status = "closed";
  $post->post_name = "regions/" . $term->slug;
  $post->post_type = $post_type;
  $post->filter = "raw"; // important!

  $wp_post = new WP_Post( $post );
  wp_cache_add( $post_id, $wp_post, "posts" );
  return $wp_post;

function b58_get_the_featured_image( $get_url, $size = 'thumbnail' ) {
  global $post;
  $post_id = $post->ID;
  $thumbnail_id = 0;

  if( $post_id > 0 ) { // this is a regular post.
    $thumbnail_id = get_post_thumbnail_id( $post_id );
  } else {
    // this is our fake post and it doesn't 
    // have a thumbnail ID so we have to use the
    // custom field we set for this term.
    $pos_term_id = -1 * $post_id;
    $thumbnail_id = get_field( "term_background", "term_" . $pos_term_id );

  if( $get_url ) {
    return wp_get_attachment_image_url( $thumbnail_id, $size );
  return $thumbnail_id;

function b58_get_the_link() {
  global $post;
  $post_id = $post->ID;
  if( $post_id > 0 ) {
    return get_permalink( $post_id );
  // post id is negative, we use a negative post id in our dummy post object
  $pos_term_id = -1 * $post_id;
  $term_link = get_term_link( $pos_term_id );
  return $term_link;
}Code language: PHP (php)

Let's review each function in this snippet.

b58_create_post_from_term( $term, $post_type="post" )

This takes a WP Term object ($term) and creates a fake post of type set in $post_type.
It sets the ID to the negative of its actual ID first as kind of a "flag" to indicate this is a term and not an actual post.
It sets some other variables necessary for the WP_Post object, but the only one of note for our purpose is the post_title.
Then it adds this post to the wp cache, in case something requests this post and since it has a negative ID it will fail if it tries going to the database.

b58_get_the_featured_image( $get_url, $size = 'thumbnail' )Code language: PHP (php)

If you did not add a custom field with image for the term then skip this function.
This function takes 2 arguments, $get_url which would be a boolean, this determines if it returns the image's ID or the URL.
The 2nd argument sets the size.

First it checks if the current post's ID is negative, if it is negative then this is a fake post that is a term, otherwise it is a real post.
If it is a fake post then we retrieve the image ID with get_field for that term's ID.
If it is a real post then we use the built-in get_post_thumbnail_id function.

Second, if $get_url is false then only the ID is returned. Otherwise we use wp_get_attachment_image_url to get the image url and return that.


This returns the link (permalink or term link) for this post.
If the post ID is negative then it is a fake post we we use get_term_link to get the link, instead of get_permalink if it's a real post.

That is it for the helper functions.

The Repeater

I add a repeater to the page, with a query for destinations. I use the same layout as previous examples, centered Post Title with Featured Image background, and the div links to either the post permalink or term link.

For the featured image background, I don't use the usual Featured Image method in Dynamic Data. I use the PHP Function Return Value method because I want to use my helper function to fetch an image rather than the other way because the item might be a "fake post" (term.) So for the div background URL, I use the PHP Function Return Value with function name b58_get_the_featured_image, and the paramter true. I could specify a size here too but I don't.

Same idea for the link. I can't use the Permalink dynamic data as I would normally because it would be wrong for the fake post, so I use b58_get_the_link instead.

the_posts hook

Just like before I add code blocks before and after the repeater, this way we can set a function to be called with the hook and remove that function afterwards so it won't affect other queries.

Here is the code for the code block above the repeater

First Code Block

function b58_add_tax_links( $posts, $query ) {

  // ignore if in admin
  if( is_admin() ) {
    return $posts;
  try {
    $terms = get_terms([
      "taxonomy" => "regions",
      "hide_empty" => true,
      "order" => "ASC"
  } catch (Exception $e) {
    echo 'Caught exception: ',  $e->getMessage(), "\n";
    return $posts;

  if( empty( $terms ) ) {
    return $posts;

  $new_posts = array();

  foreach( $terms as $term ) {
    for ( $i = 0; $i < count($posts); $i++ ) {
      if( has_term( $term->slug, "regions", $posts[$i] ) ) {
        $posts[$i]->post_title = $posts[$i]->post_title;
        $new_posts[] = $posts[$i];
    // create a post object from this term.
    $term_post = b58_create_post_from_term( $term, "destinations" );
    $new_posts[] = $term_post;

  return $new_posts;

add_filter( 'the_posts', 'b58_add_tax_links', 10, 2 ); 
?>Code language: HTML, XML (xml)

Let's go over this code.

  1. Do nothing if in the admin dashboard.
  2. Otherwise, I run a get_terms query for the taxonomy with the slug regions, sorted by name in ascending order. The Regions taxonomy is attached to the Destinations custom post type, it is one of the 7 continents where the destination is located, so Nairobi and Cairo are in Africa, Hong Kong is in Asia, and so on.
  3. Add some checks and error handling in case there is a typo with the terms name or if there are no terms for a taxonomy, then we just return the original result.
  4. If the terms query passes all checks, declare a new empty array called $new_posts, I will add the posts into this array and return it instead of the actual posts array.
  5. Iterate through the $terms array, and for each term I iterate through the $posts array and find the ones that have that term and append it to $new_posts.
  6. After I finish searching the $posts array, I create a fake post with the current $term and give it a post type of destinations (it could be anything, really.) Then I add this fake post to $new_posts.

When the function returns, the $new_posts array items should look like:
Cairo, Nairobi, Africa, Hong Kong, Asia, London, Europe … so on. Africa, Asia, and Europe, are the fake posts.

Finally, we add the above function to the the_posts hook.

Second Code Block

The Code Block below the repeater is:

remove_filter( 'the_posts', 'b58_add_tax_links' ); 
?>Code language: HTML, XML (xml)

This removes the function from the hook.

Format the list

At this point the repeater is finished. Items with the same Taxonomy are grouped together, and at the end of each group there is a link to the Term archive. However, the list is a bit jumbled, it all runs together in a large grid. I want each group of items and their archive link to be in a row by themselves, and this is how to do it.

Set up the elements

  1. Select the repeated item div under the repeater and add an attribute named data-post-id and use dynamic data Post ID for its value.
  2. Select the repeater and give it some ID or just copy the current ID, mine is _dynamic_list-5-343.

The JQuery

In the top Code Block (either one works, or even a new one), I add the following code to the Javascript section.

jQuery(($) => {
  $("#_dynamic_list-5-343 [data-post-id^=\"-\"]").after(
    $("<div />")
        height: "0px",
        "flex-basis": "100%"
  });Code language: JavaScript (javascript)

This adds a div after the Terms div (post ID starts with "-", negative) and the div has flex-basis: 100% and height: 0px which is like adding a line break in the repeater row.

And that's it.


Terms Items added to Repeater

Each repeated item has their respective post titles and featured image as the background. For the Terms items I added the word "Explore" and set it to be conditionally shown when the post ID is negative (< 0).


Hopefully with these techniques you can spice up your Oxygen Builder repeaters or Easy Posts.

If you have any questions you can DM me on twitter @robchankh or leave a comment on FB where I'll post this.

Meet CodeWP, an AI Code Generator
Prompt CodeWP in plain English and it'll output high quality code, specific for WordPress.
Join The IsoGroup
A vibrant Facebook Group to talk about web design and the business of agency. Daily curated resources and conversations.
Join Now
Bricks Builder Course - Coming Soon
The Bricks Builder Mastery course will bring you up to speed on this popular builder.
Get Launch Notification & Discount
Subscribe & Share
If you liked this content, subscribe for our monthly roundup of WordPress news, website inspiration, exclusive deals and interesting articles.
Unsubscribe at any time. We do not spam and will never sell or share your email.
Notify of
Newest Most Voted
Inline Feedbacks
View all comments
1 year ago

Very helpful! Thank you Robert 🙂

1 year ago

A great tutorial. Thanks

1 year ago

Great article!

But a quick question, I have a custom taxonomy attached to a custom post type.

I want to use an Oxygen repeater to just show the terms for the custom taxonomy displaying title, description and an image field that I added using a custom field.

So, your code above shows how to add the terms to the posts (a mixture), but I purely want the terms.

How would I go about this?

Thanks in advance for your help.


James LePage
1 year ago
Reply to  Rob

Oxygen repeaters only query posts. No terms or users. You can do that with a custom loop.

We just launched an AI code generate that might help you 🙂 You can use code isotropic_alpha for free access:

Article By
Robert Chan
Robert Chan runs out of Hong Kong. He's worked with all sorts of web technologies, but Wordpress keeps coming up. His current focus is getting better with TypeScript and his photography.
We're looking for new authors. Explore Isotropic Jobs.
linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram