This guide explains how to build your own API Press Pro template using cards.php as the working example.
You will learn how to define Slots and Settings in the header, read and sanitize values, output the markup, and keep your work update safe.
Prerequisites
- API Press Pro is installed and active
- You have at least one API setup
- Basic PHP and WordPress coding knowledge
Where templates live
Default Pro templates are located at:
/wp-content/plugins/api-press-pro/templates/
To add a custom template or override an existing template with your own modifications, you should copy the template file from the plugin location and add it to your theme folder below:
/wp-content/themes/your-theme/apipress/templates/
If a file with the same Template ID exists in your theme path, API Press will use that version instead of the plugin copy.
Templates available in Pro
- Table – structured data with sorting, filters, and pagination
- Gallery – image grid for photo or product feeds
- Cards – flexible card grid for listings, profiles, and products
- List – similar to Cards but rendered in a list format
- Carousel – slide-based layout for featured or rotating content, ideal for small logo carousels
- Slider – similar to Carousel but for larger images and or text
How a template works
Every template file is a single PHP file that contains two main parts:
- Header block that defines Template Name, Template ID, Version, Slots, and Settings
- Rendering logic that reads
$itemsand$settingsand outputs HTML
API Press parses the header block to automatically build the Slots mapping UI and the Template Settings UI in the admin.
Header format based on cards.php
Below is the actual header from cards.php. Use this as the pattern for your own custom template. Change the Template Name and Template ID to new values.
/**
* Template Name: Cards
* Template ID: cards
* Version: 1.1.1
* Slots:
* - heading:text|required|label=Heading
* - subheading:text|label=Subheading
* - excerpt:text|label=Excerpt
* - image:url|label=Image URL
* - href:url|required|label=Link URL
* - badge:text|label=Badge
*
* Settings:
* class:text|label=Extra CSS class|help=Add custom class names to the wrapper.
* columns:number|label=Columns (desktop)|default=3|min=1|max=6|step=1|help=Cards per row on desktop screens.
* gap:number|label=Gap (px)|default=16|min=0|max=48|step=1|help=Space between cards.
* hover:select|label=Hover effect|options=none,raise,shadow|default=raise|help=Card interaction on hover.
* image_position:select|label=Image position|options=top,left|default=top|help=Place image above text or to the left.
* image_ratio:select|label=Image ratio|options=16/9,4/3,1/1,3/2,auto|default=16/9|help=Aspect ratio (top position only).
* image_fit:select|label=Image fit|options=cover,contain|default=cover|help=How the image fits inside its box.
*/
- Slots are the keys you will map to fields from your API response. You can name the slots anything you like but they should be all lowercase with no spaces.
- Settings define controls that appear in the Template Settings panel
- Supported setting types include
text,number,toggle,select,url, andrepeater
Reading items and settings
The Cards template normalizes and sanitizes settings, then uses them to build classes and scoped CSS variables. It then loops through $items and renders each card. Start your file with the standard guard and input normalization:
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
// Normalize settings.
$settings = is_array( $settings ) ? $settings : array();
$css_class = isset( $settings['class'] ) ? sanitize_html_class( $settings['class'] ) : '';
$columns = isset( $settings['columns'] ) ? max( 1, (int) $settings['columns'] ) : 3;
$gap = isset( $settings['gap'] ) ? max( 0, (int) $settings['gap'] ) : 16;
$hover = isset( $settings['hover'] ) ? sanitize_key( $settings['hover'] ) : 'raise'; // none|raise|shadow
$image_pos = isset( $settings['image_position'] ) ? sanitize_key( $settings['image_position'] ) : 'top'; // top|left
$image_ratio= isset( $settings['image_ratio'] ) ? trim( (string) $settings['image_ratio'] ) : '16/9';
$image_fit = isset( $settings['image_fit'] ) ? sanitize_key( $settings['image_fit'] ) : 'cover'; // cover|contain
Wrapper classes and CSS variables
Cards demonstrates a clean way to push layout values to CSS using scoped variables. This avoids inline styles on each child element and keeps your HTML small.
<?php
// Build wrapper classes.
$wrap_classes = array(
'apipress-cards',
$css_class,
'apipress-cards--hover-' . $hover,
'apipress-cards--img-' . ( 'left' === $image_pos ? 'left' : 'top' ),
);
$wrap_classes = array_filter( array_map( 'sanitize_html_class', $wrap_classes ) );
// CSS variables (scoped).
$style_vars = sprintf(
'--apipress-cards-gap:%dpx;--apipress-cards-columns:%d;',
$gap,
$columns
);
// Image ratio variable only matters when image is on top.
if ( 'top' === $image_pos && 'auto' !== strtolower( $image_ratio ) ) {
$ratio_css = str_replace('/', ' / ', preg_replace('~\s+~', '', $image_ratio));
$style_vars .= ' --apipress-image-aspect-ratio:' . esc_attr( $ratio_css ) . ';';
} else {
$style_vars .= ' --apipress-image-aspect-ratio:auto;';
}
?>
<div class="<?php echo esc_attr( implode( ' ', $wrap_classes ) ); ?>"
style="<?php echo esc_attr( $style_vars ); ?>"
data-columns="<?php echo esc_attr( $columns ); ?>">
...
</div>
Rendering each item
In Cards, each row is a simple associative array whose keys match your Slot names. You’ll notice that ‘heading’, ‘subheading’, ‘excerpt’ etc all match the Slot names defined earlier.
<?php if ( ! empty( $items ) && is_array( $items ) ) : foreach ( $items as $card ) :
$heading = (string) ( $card['heading'] ?? '' );
$subheading = (string) ( $card['subheading'] ?? '' );
$excerpt = (string) ( $card['excerpt'] ?? '' );
$image = (string) ( $card['image'] ?? '' );
$href = (string) ( $card['href'] ?? '' );
$badge = (string) ( $card['badge'] ?? '' );
$has_image = ! empty( $image );
$card_classes = array(
'apipress-card',
$has_image ? 'apipress-card--with-image' : 'apipress-card--no-image',
);
$card_classes = array_map( 'sanitize_html_class', $card_classes );
?>
<article class="<?php echo esc_attr( implode( ' ', $card_classes ) ); ?>" aria-label="<?php echo esc_attr( $heading ); ?>">
<?php if ( $has_image ) : ?>
<div class="apipress-card-media" data-fit="<?php echo esc_attr( $image_fit ); ?>">
<?php if ( $href ) : ?><a class="apipress-card-link" href="<?php echo esc_url( $href ); ?>"><?php endif; ?>
<?php if ( 'top' === $image_pos ) : ?>
<div class="apipress-card-media-outer" style="aspect-ratio: var(--apipress-image-aspect-ratio);">
<img class="apipress-card-image" src="<?php echo esc_url( $image ); ?>" alt="<?php echo esc_attr( $heading ); ?>">
</div>
<?php else : ?>
<img class="apipress-card-image apipress-card-image--left" src="<?php echo esc_url( $image ); ?>" alt="<?php echo esc_attr( $heading ); ?>">
<?php endif; ?>
<?php if ( $href ) : ?></a><?php endif; ?>
<?php if ( $badge ) : ?><span class="apipress-badge apipress-card-badge apipress-badge--over"><?php echo esc_html( $badge ); ?></span><?php endif; ?>
</div>
<?php endif; ?>
<div class="apipress-card-body">
<?php if ( $heading ) : ?>
<h3 class="apipress-card-title">
<?php if ( $href ) : ?><a class="apipress-card-link" href="<?php echo esc_url( $href ); ?>"><?php endif; ?>
<?php echo esc_html( $heading ); ?>
<?php if ( $href ) : ?></a><?php endif; ?>
</h3>
<?php endif; ?>
<?php if ( $subheading ) : ?><p class="apipress-card-sub"><?php echo esc_html( $subheading ); ?></p><?php endif; ?>
<?php if ( $excerpt ) : ?><p class="apipress-card-excerpt"><?php echo esc_html( $excerpt ); ?></p><?php endif; ?>
<?php if ( ! $has_image && $badge ) : ?>
<span class="apipress-badge apipress-card-badge"><?php echo esc_html( $badge ); ?></span>
<?php endif; ?>
</div>
</article>
<?php endforeach; else : ?>
<p class="apipress-empty"><?php esc_html_e( 'No results found.', 'apipress' ); ?></p>
<?php endif; ?>
Sanitization checklist
esc_html()for text content like headings and excerptsesc_attr()for attributes and CSS variablesesc_url()for href and srcsanitize_key()for select values likehoverandimage_fitsanitize_html_class()for class name inputs
Styling patterns
Cards uses scoped CSS variables so themes can style the layout without touching the markup.
.apipress-cards {
--apipress-cards-gap: 16px;
--apipress-cards-columns: 3;
display: grid;
gap: var(--apipress-cards-gap);
grid-template-columns: repeat(var(--apipress-cards-columns), minmax(0, 1fr));
}
.apipress-card { background: #fff; border: 1px solid rgba(0,0,0,.06); border-radius: 8px; overflow: hidden; }
.apipress-cards--hover-raise .apipress-card:hover { transform: translateY(-2px); }
.apipress-cards--hover-shadow .apipress-card:hover { box-shadow: 0 6px 18px rgba(0,0,0,.12); }
.apipress-card-media-outer { width: 100%; aspect-ratio: var(--apipress-image-aspect-ratio, auto); }
.apipress-card-image { width: 100%; height: auto; object-fit: cover; display: block; }
.apipress-card-image--left { width: 128px; height: 100%; object-fit: cover; float: left; margin-right: 12px; }
.apipress-card-body { padding: 12px 14px; }
.apipress-card-title { margin: 0 0 6px; font-size: 18px; }
.apipress-card-sub { margin: 0 0 6px; color: #555; }
.apipress-card-excerpt { margin: 0; color: #444; }
.apipress-badge { display: inline-block; padding: 2px 8px; background: #eef2ff; color: #3730a3; border-radius: 999px; font-size: 12px; }
.apipress-badge--over { position: absolute; top: 8px; left: 8px; }
Adding or changing settings
To add a new setting, declare it in the header under Settings, then read and sanitize it where settings are normalized. For example, to add a rounded toggle that controls border radius:
/* In header:
* - rounded:toggle|label=Rounded corners|default=1
*/
// In PHP:
$rounded = ! empty( $settings['rounded'] );
$wrap_classes[] = $rounded ? 'apipress-cards--rounded' : 'apipress-cards--sharp';
You can then style it using the class of apipress-cards--rounded.
Using repeater settings
When you need a flexible group of controls, use a repeater. For example, an Actions repeater to output buttons under each card:
/* In header:
* - actions:repeater|label=Actions
* - label:text|label=Label
* - href:url|label=URL
* - target:select|options=_self,_blank|default=_self|label=Target
*/
// In PHP:
$actions = array();
if ( ! empty( $settings['actions'] ) && is_array( $settings['actions'] ) ) {
foreach ( $settings['actions'] as $row ) {
$label = trim( (string) ( $row['label'] ?? '' ) );
$href = trim( (string) ( $row['href'] ?? '' ) );
$target = sanitize_key( $row['target'] ?? '_self' );
if ( '' === $label || '' === $href ) continue;
$actions[] = compact( 'label', 'href', 'target' );
}
}
Overriding and versioning
- Duplicate an existing template like
cards.phpto your theme path and rename it - Change Template Name and Template ID in the header
- Increment the Version string when you ship changes
- Keep Template ID stable to preserve saved settings and mappings
Quick start checklist
- Copy
cards.phpto your theme template folder and rename the file - Edit the header: new Template Name, ID, Version, Slots, and Settings
- Adjust the settings normalization block and wrapper classes
- Render your items with proper escaping and accessible markup
- Add small, scoped CSS or rely on your theme styles
- Preview on an API post and iterate
When to choose each built in template
- Cards or List when you need simple grids or lists with text and images
- Gallery for image-first layouts with minimal text
- Carousel for compact logo or feature rotations
- Slider for larger hero slides or text-forward slides
- Table for data heavy views that benefit from sorting and filters
