---
title: PHP actions
description: Every action CartPops V2 fires, where each drawer position sits in the markup, and how to use them safely.
sidebar:
  label: PHP actions
  icon: plug
---

CartPops V2 fires one lifecycle action and 18 drawer position actions. All of them take no arguments, and they work the same in Free and Pro.

## How the drawer actions behave

- **They fire during the server render of the Cart Drawer block.** The block renders once per page, so each position fires once per page load.
- **They do not run again when the cart changes.** The drawer updates in the browser after the first render. Your callback is not called again when a shopper adds an item, applies a coupon or removes a line. Print static markup, not cart-dependent markup.
- **You own the output.** Escape everything, print balanced HTML, and keep it accessible. Do not add `data-wp-*` directives or change the dialog's attributes.
- **Your markup is not styled by your theme.** Inside the drawer panel, CartPops resets inherited styles, so theme and page builder rules do not reach your elements. Give your elements a class and style them in [Custom CSS](/developers/css-customization).
- **CartPops' own sections are not hooked on these actions.** You cannot use priority to place something between the native header, items and totals. The positions below are the complete contract.

:::note
If a page contains more than one Cart Drawer block, CartPops keeps the first and discards the others. The discarded copy can still run through PHP, so your callbacks may be called more than once in a request even though only one copy is printed. Do not do work with side effects inside these callbacks.
:::

## `cartpops_loaded`

Fires on `plugins_loaded`, after CartPops has initialized. Use it to register your own hooks that depend on CartPops being ready.

It does not fire when WooCommerce is not active. It also does not fire when the Free and Pro editions are both active, because CartPops pauses itself in that case.

```php functions.php
add_action( 'cartpops_loaded', static function (): void {
	// CartPops is ready. Register your integration here.
	add_action( 'cartpops_drawer_footer_after', 'my_prefix_print_returns_note' );
} );
```

## Drawer positions

The table lists the positions from the outside of the drawer to the inside, in the order they run.

| Action | Where it fires |
| --- | --- |
| `cartpops_drawer_wrapper_start` | First child of the drawer's outer wrapper, before the overlay and the panel. This is outside the dialog and stays visible when the drawer is closed. |
| `cartpops_drawer_panel_wrapper_start` | First child of the drawer dialog, before the header. |
| `cartpops_drawer_header_before` | Inside the header, before the title and the close button. |
| `cartpops_drawer_header_after` | Inside the header, after the title and the close button. |
| `cartpops_drawer_content` | At the start of the scrollable content area, before CartPops' notifications, shipping meter, items, empty-cart message, recommendations and totals. |
| `cartpops_drawer_footer_before` | First thing inside the footer. |
| `cartpops_drawer_footer_content` | In the footer, after `cartpops_drawer_footer_before` and before CartPops' add-ons, coupon field, totals and checkout button. |
| `cartpops_drawer_coupon_wrapper_start` | Just before the coupon block. Only fires when **Show coupon code field** is on (Drawer screen). |
| `cartpops_drawer_coupon_form_before` | Inside the coupon block, before its title and form. Same condition. |
| `cartpops_drawer_coupon_form_after` | Inside the coupon block, after the form and before the applied-coupon tags. Same condition. |
| `cartpops_drawer_coupon_wrapper_end` | Just after the coupon block. Same condition. |
| `cartpops_drawer_before_checkout_button` | Just before the checkout link. |
| `cartpops_drawer_after_checkout_button` | Just after the checkout link. |
| `cartpops_drawer_before_secondary_checkout_button` | After the checkout link, before the optional Pro secondary action. Fires even when there is no secondary action. |
| `cartpops_drawer_after_secondary_checkout_button` | After the optional secondary action, before the small View Cart link. Fires even when there is no secondary action. |
| `cartpops_drawer_footer_after` | Last thing inside the footer, after the View Cart link and the Powered by link. |
| `cartpops_drawer_panel_wrapper_end` | Last child of the drawer dialog, after the footer. |
| `cartpops_drawer_wrapper_end` | Last child of the outer wrapper, after the dialog and the screen reader live region. Like the start position, it is outside the dialog and stays visible when the drawer is closed. |

### The footer and an empty cart

The footer is rendered even when the cart is empty, so the footer actions still fire on a page with an empty cart. CartPops hides the footer element while the cart is empty and shows it again as items are added. Your footer output is hidden and shown with it. The callback is not run again.

The coupon, checkout and secondary action positions all live inside the footer, so they behave the same way. Positions outside the footer (`cartpops_drawer_content`, the header positions and the wrapper positions) are not hidden when the cart is empty.

## Examples

Print a short note at the top of the drawer content.

```php functions.php
add_action( 'cartpops_drawer_content', static function (): void {
	printf(
		'<p class="my-drawer-note">%s</p>',
		esc_html__( 'Free returns within 30 days.', 'my-plugin' )
	);
} );
```

Add a link under the checkout button, and style it in Custom CSS.

```php functions.php
add_action( 'cartpops_drawer_after_checkout_button', static function (): void {
	printf(
		'<a class="my-drawer-link" href="%s">%s</a>',
		esc_url( home_url( '/shipping-and-returns/' ) ),
		esc_html__( 'Shipping and returns', 'my-plugin' )
	);
} );
```

```css custom.css
.cpops-drawer .my-drawer-link {
	display: block;
	margin-top: 8px;
	text-align: center;
	font-size: 14px;
	color: var(--cpops-text-secondary);
}
```

## Not a CartPops hook

`fs_cartpops_loaded` also exists. It belongs to the Freemius licensing layer and fires when that layer is ready. It is not part of the extension contract described here.

:::note[Coming from V1?]
The popup and bar hooks are gone, and CartPops' own header, totals and footer are no longer callbacks on these actions. See [Upgrading to V2: for developers](/upgrading-to-v2/developers).
:::
