> For the complete documentation index, see [llms.txt](https://ctracker-docs.keywordrush.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ctracker-docs.keywordrush.com/for-developers/shop-page-blocks.md).

# Adding your own blocks to a shop page

A shop page is assembled from small templates called **parts** — the logo and rating, the rate, the button, the facts list, the terms, the related shops. A **layout** arranges them, and the two layouts (`Sidebar panel` and `Hero band`) draw the same parts in different places.

That means you can add your own content to a shop page without copying a single template. There are three ways in, and they suit different jobs.

| You want to                                   | Use                                                                      |
| --------------------------------------------- | ------------------------------------------------------------------------ |
| Add a block of your own somewhere on the page | An **action**                                                            |
| Change what an existing part says             | A **filter**                                                             |
| Change how a part is built                    | A [template override](/putting-it-on-your-pages/overriding-templates.md) |

Reach for an override last. A theme copy of a template stops receiving improvements the moment you make it, and every one of them is a file you now maintain.

### The two empty slots

Cashback Tracker ships two sidebar parts with nothing in them, waiting for you:

| Slot             | Was going to say                   | Filled by                 |
| ---------------- | ---------------------------------- | ------------------------- |
| **How it works** | Three steps from click to cashback | `cbtrkr_shop_how_steps`   |
| **Trust**        | Which payout methods you offer     | `cbtrkr_shop_trust_items` |

Neither has default text, on purpose. Both are claims about *your* site — how long your cashback takes, how you pay — and a sentence you cannot edit from wp-admin is worse than no sentence. The slots, their headings and their styling are all there in both layouts. A filter turns each on.

#### How it works

```php
add_filter('cbtrkr_shop_how_steps', function ($steps, $post_id) {
    return array(
        __('Click Go to shop from this page.', 'my-theme'),
        __('Shop as you normally would.', 'my-theme'),
        __('Your cashback appears once the shop confirms the order.', 'my-theme'),
    );
}, 10, 2);
```

That is the whole job. The block appears in the panel layout's sidebar card and the hero layout's right rail, styled to match the rest of the page, and it disappears again the moment you return an empty array.

The heading is filterable too, so the slot need not be about steps at all:

```php
add_filter('cbtrkr_shop_how_title', function ($title, $post_id) {
    return __('Before you buy', 'my-theme');
}, 10, 2);
```

Return `''` for the heading and you get a bare list.

{% hint style="info" %}
Both filters receive the shop's post ID, so the block can differ per shop — different steps for a shop with a long validation period, say, or nothing at all on shops in one category.
{% endhint %}

#### Trust

The second slot is a plain list, styled as a grey card. Strings are escaped for you:

```php
add_filter('cbtrkr_shop_trust_items', function ($items, $post_id) {
    return array(
        __('Free to join, no minimum payout.', 'my-theme'),
        __('Paid by PayPal or bank transfer.', 'my-theme'),
    );
}, 10, 2);
```

Cashback Tracker knows which payout methods you have configured and deliberately does **not** volunteer them here. Whether to promise anything about payment on a shop page, and in what words, is a decision about your business — see [Payout methods and the minimum](/members-and-payouts/payout-methods.md) for where they really live.

### Putting a block anywhere else

Those two slots sit where the layout puts them. For anywhere else, hook an action. Each one receives the shop's post ID and fires in both layouts.

| Action                       | Where it lands                             |
| ---------------------------- | ------------------------------------------ |
| `cbtrkr_shop_before`         | Above everything, outside the page wrapper |
| `cbtrkr_shop_sidebar_top`    | Top of the sidebar column, above the offer |
| `cbtrkr_shop_offer_after`    | Directly under the Go to shop button       |
| `cbtrkr_shop_sidebar_bottom` | Bottom of the sidebar column               |
| `cbtrkr_shop_content_before` | Above the shop description                 |
| `cbtrkr_shop_content_after`  | Below the shop description                 |
| `cbtrkr_shop_before_reviews` | Above Member reviews                       |
| `cbtrkr_shop_before_related` | Above the related shops                    |
| `cbtrkr_shop_after`          | Below everything                           |

An action prints; it does not return.

```php
add_action('cbtrkr_shop_offer_after', function ($post_id) {
    if (!$note = get_post_meta($post_id, '_my_delivery_note', true))
        return;

    echo '<div class="cbtrkr_shop_trust">'
        . '<div class="cbtrkr_shop_trust_item">' . esc_html($note) . '</div>'
        . '</div>';
});
```

Borrowing the plugin's own class names, as above, is the quickest way to make an addition look like it belongs. `cbtrkr_shop_trust` gives you the bordered grey card; `cbtrkr_shop_how` with `cbtrkr_shop_how_title` gives you the bordered card with a small uppercase heading.

{% hint style="warning" %}
**Escape what you print.** These actions write straight into the page. `esc_html()` for text, `wp_kses_post()` for markup you are letting through on purpose.
{% endhint %}

### Changing what a part says

Where a part builds a list, a filter hands you that list before it is drawn.

**The facts list** — the label/value rows under the offer:

```php
add_filter('cbtrkr_shop_facts', function ($rows, $post_id) {
    $rows[] = array(
        'label' => __('Ships from', 'my-theme'),
        'value' => esc_html(get_post_meta($post_id, '_my_ships_from', true)),
    );
    return $rows;
}, 10, 2);
```

Each row is `array('label' => ..., 'value' => ...)`. A row with an empty value is skipped. The plugin's own rows arrive already escaped, because a value may legitimately be a link — **so escape the value of any row you add.** Rows are drawn in array order, so `array_unshift()` puts yours first and `array_filter()` removes one you do not want.

**Where the buttons point:**

```php
add_filter('cbtrkr_go_shop_link', function ($url, $post_id) {
    return add_query_arg('via', 'shop-page', $url);
}, 10, 2);
```

This one moves the Go to shop button **and** the Website row in the facts list, because both resolve through the same helper. That is deliberate — the two must never send a visitor to different places.

**Which related shops appear:**

| Filter                       | Takes                                                                    |
| ---------------------------- | ------------------------------------------------------------------------ |
| `cbtrkr_related_shops_limit` | `int $limit, int $post_id` — how many tiles to aim for                   |
| `cbtrkr_related_shops_args`  | `array $args, int $post_id` — the `WP_Query` arguments, before each pass |
| `cbtrkr_related_shops_posts` | `WP_Post[] $posts, int $post_id` — the last word                         |

**Which layout a shop uses**, overriding the setting for one shop or one category:

```php
add_filter('cbtrkr_shop_layout', function ($layout, $post_id) {
    return has_term('electronics', 'cbtrkr_shop_category', $post_id) ? 'hero' : $layout;
}, 10, 2);
```

Anything other than `hero` or `panel` falls back to `panel`, so a typo cannot produce a blank page.

### Where to put the code

A small [must-use plugin](https://wordpress.org/documentation/article/must-use-plugins/) is the right home — `wp-content/mu-plugins/my-shop-tweaks.php`. It survives theme changes and cannot be deactivated by accident.

Your theme's `functions.php` works too, at the cost of losing the code the day you change theme.

{% hint style="info" %}
**Parts that have nothing to show render nothing.** No categories means no breadcrumb tail; no terms filled in means no terms section; no filter means neither empty slot appears. Your own additions should do the same — return early rather than printing an empty box.
{% endhint %}

### The parts, and what each one draws

| Part               | Draws                                                | Wrapper class            |
| ------------------ | ---------------------------------------------------- | ------------------------ |
| `_shop-breadcrumb` | All shops › Category                                 | `cbtrkr_shop_breadcrumb` |
| `_shop-identity`   | Logo, shop name, star rating                         | `cbtrkr_shop_identity`   |
| `_shop-rate`       | The cashback rate, or the top offer on a coupon site | `cbtrkr_shop_rate`       |
| `_shop-cta`        | The Go to shop button                                | `cbtrkr_shop_cta`        |
| `_shop-facts`      | Approved in, paid out in, categories, website        | `cbtrkr_shop_facts`      |
| `_shop-how`        | Empty until filtered — see above                     | `cbtrkr_shop_how`        |
| `_shop-trust`      | Empty until filtered — see above                     | `cbtrkr_shop_trust`      |
| `_shop-terms`      | Cashback terms and exclusions                        | `cbtrkr_shop_terms`      |
| `_shop-related`    | Related shop tiles                                   | `cbtrkr_shop_related`    |
| `_shop-actionbar`  | The bar pinned to the bottom on phones               | `cbtrkr_shop_actionbar`  |

Every one takes a single variable, `$post_id`, and every one can be overridden in your theme by copying it from the plugin's `templates/` folder. See [Overriding templates in your theme](/putting-it-on-your-pages/overriding-templates.md).
