Creating a Custom Template

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:

  1. Header block that defines Template Name, Template ID, Version, Slots, and Settings
  2. Rendering logic that reads $items and $settings and 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, and repeater

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 excerpts
  • esc_attr() for attributes and CSS variables
  • esc_url() for href and src
  • sanitize_key() for select values like hover and image_fit
  • sanitize_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.php to 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

  1. Copy cards.php to your theme template folder and rename the file
  2. Edit the header: new Template Name, ID, Version, Slots, and Settings
  3. Adjust the settings normalization block and wrapper classes
  4. Render your items with proper escaping and accessible markup
  5. Add small, scoped CSS or rely on your theme styles
  6. 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