Customize Cards

Inject badges, buttons, counts, and metadata into post cards, reply cards, space cards, and member rows using purpose-built hooks - no template override required for most use cases.


Post Cards

Post cards appear in the space topic listing, the home grid, and in search results. The single-post header reuses the same jetonomy_post_card_after_badges hook so a badge added here appears consistently in both surfaces.

jetonomy_post_card_after_badges

Fires right after the built-in status badges (Sticky, Private, Pinned) inside the card. Use this to append your own badge or marker. This hook fires in both the listing card and the single-post header.

do_action( 'jetonomy_post_card_after_badges', $post, $space )

Parameters

Parameter Type Description
$post object The post row being rendered.
$space object|null The post's space, if loaded.

Source: templates/partials/post-card.php, templates/views/single-post.php

Example: Featured badge

add_action( 'jetonomy_post_card_after_badges', function( object $post, $space ) {
    if ( ! get_post_meta( $post->id, '_my_featured', true ) ) {
        return;
    }
    echo '<span class="my-badge my-badge--featured">' . esc_html__( 'Featured', 'my-plugin' ) . '</span>';
}, 10, 2 );

Example: Space-type label (only on the home grid where $space may vary)

add_action( 'jetonomy_post_card_after_badges', function( object $post, $space ) {
    if ( ! $space || 'qa' !== ( $space->type ?? '' ) ) {
        return;
    }
    echo '<span class="my-badge my-badge--qa">' . esc_html__( 'Q&A', 'my-plugin' ) . '</span>';
}, 10, 2 );

jetonomy_post_actions

Fires inside the single-post action toolbar, alongside the built-in actions (vote, bookmark, share). Use this to inject a custom action button visible on a single post view.

do_action( 'jetonomy_post_actions', $post )

Parameters

Parameter Type Description
$post object The current post being viewed.

Source: templates/views/single-post.php

Example: "Translate" button

add_action( 'jetonomy_post_actions', function( object $post ) {
    if ( ! is_user_logged_in() ) {
        return;
    }
    $nonce = wp_create_nonce( 'my_translate_' . $post->id );
    printf(
        '<button class="jt-action-btn my-translate-btn" data-post-id="%d" data-nonce="%s" aria-label="%s">%s</button>',
        (int) $post->id,
        esc_attr( $nonce ),
        esc_attr__( 'Translate this post', 'my-plugin' ),
        esc_html__( 'Translate', 'my-plugin' )
    );
} );

jetonomy_after_post_content (filter)

Filters the HTML rendered directly after the post body in the single-post view. Return a non-empty string to inject content between the post body and the action row.

apply_filters( 'jetonomy_after_post_content', $html, $post )

Parameters

Parameter Type Description
$html string HTML to render after the post content (empty by default).
$post \Jetonomy\Models\Post The current post object.

Return: string

Source: templates/views/single-post.php

Example: Related posts block

add_filter( 'jetonomy_after_post_content', function( string $html, $post ): string {
    $related = my_plugin_get_related( (int) $post->id );
    if ( empty( $related ) ) {
        return $html;
    }

    ob_start();
    echo '<div class="my-related-posts">';
    echo '<strong>' . esc_html__( 'Related discussions', 'my-plugin' ) . '</strong>';
    echo '<ul>';
    foreach ( $related as $r ) {
        printf(
            '<li><a href="%s">%s</a></li>',
            esc_url( $r['url'] ),
            esc_html( $r['title'] )
        );
    }
    echo '</ul></div>';
    $html .= ob_get_clean();

    return $html;
}, 10, 2 );

jetonomy_after_post_article

Fires after the main post <article> element and before the replies section. Ideal for ads, CTAs, or related-content blocks placed between the topic body and the reply list.

do_action( 'jetonomy_after_post_article', $post )

Parameters

Parameter Type Description
$post object The current post object.

Source: templates/views/single-post.php

Example: Ad placement

add_action( 'jetonomy_after_post_article', function( object $post ) {
    echo do_shortcode( '[my_ad zone="community_post_bottom"]' );
} );

Reply Cards

Reply cards appear in the single-post reply list. The hook fires for every top-level reply in both the "opening" and "latest" batches.

jetonomy_reply_actions

Fires inside the reply card, within the reply action row, alongside the built-in reply actions (upvote, accept, flag).

do_action( 'jetonomy_reply_actions', $reply )

Parameters

Parameter Type Description
$reply object The reply being rendered.

Source: templates/partials/reply-card.php

Example: "Mark as helpful" button

add_action( 'jetonomy_reply_actions', function( object $reply ) {
    if ( ! is_user_logged_in() ) {
        return;
    }
    $count   = (int) get_user_meta( $reply->id, '_helpful_count', true );
    $nonce   = wp_create_nonce( 'my_helpful_' . $reply->id );
    printf(
        '<button class="jt-action-btn my-helpful-btn" data-reply-id="%d" data-nonce="%s" aria-pressed="false">%s <span class="my-helpful-count">%d</span></button>',
        (int) $reply->id,
        esc_attr( $nonce ),
        esc_html__( 'Helpful', 'my-plugin' ),
        $count
    );
} );

Space Cards

Space cards appear on the community home grid (/community/) and the category page (/community/category/{slug}/). The hook fires outside the card's <a> wrapper, so interactive elements (buttons, forms) are valid HTML here.

jetonomy_space_card_after

Fires after each space card, outside its <a> wrapper.

do_action( 'jetonomy_space_card_after', $space )

Parameters

Parameter Type Description
$space object The space being rendered.

Source: templates/views/home.php, templates/views/category.php

Example: "New" badge on recently created spaces

add_action( 'jetonomy_space_card_after', function( object $space ) {
    $created = strtotime( $space->created_at ?? '' );
    if ( ! $created || ( time() - $created ) > WEEK_IN_SECONDS ) {
        return;
    }
    echo '<span class="my-space-badge my-space-badge--new">' . esc_html__( 'New', 'my-plugin' ) . '</span>';
} );

Example: Join button outside the card link

Since this hook fires outside the <a> wrapper, you can safely render a button here without nesting interactive elements inside the link.

add_action( 'jetonomy_space_card_after', function( object $space ) {
    if ( ! is_user_logged_in() ) {
        return;
    }
    // Only show the button if the user is not already a member.
    if ( \Jetonomy\Models\SpaceMember::is_member( get_current_user_id(), (int) $space->id ) ) {
        return;
    }
    printf(
        '<button class="my-space-join-btn" data-space-id="%d">%s</button>',
        (int) $space->id,
        esc_html__( 'Join', 'my-plugin' )
    );
} );

Member Cards (Space Members List)

Member rows appear on the space members page (/community/s/{slug}/members/). The hook fires after each row's closing element, giving you a slot for per-member extras like badges, direct-message links, or moderator actions.

jetonomy_member_card_after

Fires after each member row in the space members list.

do_action( 'jetonomy_member_card_after', $member, $space )

Parameters

Parameter Type Description
$member object The space membership row. Properties: user_id (int), role (string), joined_at (string datetime).
$space object The space being viewed.

Source: templates/views/space-members.php

Example: Badge count for each member

add_action( 'jetonomy_member_card_after', function( object $member, object $space ) {
    $count = my_badges_count_for_user( (int) $member->user_id );
    if ( $count < 1 ) {
        return;
    }
    printf(
        '<span class="my-member-badge-count" title="%s">%d %s</span>',
        esc_attr__( 'Badges earned', 'my-plugin' ),
        (int) $count,
        esc_html( _n( 'badge', 'badges', $count, 'my-plugin' ) )
    );
}, 10, 2 );

Example: Message button (only for Pro users with messaging)

add_action( 'jetonomy_member_card_after', function( object $member, object $space ) {
    $current_user_id = get_current_user_id();
    // Do not show the button to guests or to the member themselves.
    if ( ! $current_user_id || (int) $member->user_id === $current_user_id ) {
        return;
    }
    $settings = get_option( 'jetonomy_settings', [] );
    $base     = $settings['base_slug'] ?? 'community';
    printf(
        '<a href="%s" class="jt-btn jt-btn--ghost jt-btn--sm my-dm-btn">%s</a>',
        esc_url( home_url( "/{$base}/messages/?to={$member->user_id}" ) ),
        esc_html__( 'Message', 'my-plugin' )
    );
}, 10, 2 );

Styling Custom Injected Content

Use Jetonomy's CSS tokens so your injected elements adapt to the active theme, dark mode, and RTL layout automatically.

/* your-plugin/assets/css/my-plugin.css */

/* Badge appended via jetonomy_post_card_after_badges */
.my-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.7rem;
    font-weight: 600;
    padding: 2px 6px;
    border-radius: var(--jt-radius-sm);
    background: var(--jt-accent-light);
    color: var(--jt-accent);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.my-badge--featured {
    background: var(--jt-warn-light);
    color: var(--jt-warn);
}

/* Button appended via jetonomy_member_card_after */
.my-dm-btn {
    margin-inline-start: auto; /* RTL-safe push to the trailing edge */
}

Available tokens are listed in the plugin's CLAUDE.md CSS Token Rules section. The key categories are --jt-accent-*, --jt-text-*, --jt-bg-*, --jt-radius-*, and --jt-border. Never use hardcoded hex or px values in styles that ship alongside these hooks.


Notes

  • jetonomy_post_card_after_badges fires in both the listing card and the single-post header. Test both surfaces when you add a badge.
  • jetonomy_space_card_after fires outside the <a> wrapper - this is intentional so buttons and forms are valid HTML. Don't wrap the emitted content in another <a>.
  • jetonomy_member_card_after fires inside the members list grid, not inside any wrapper link, so interactive elements are valid.
  • For heavier customizations that require restructuring the card layout, use Template Overrides instead.
  • See Hooks Reference for the full list of hooks, including sidebar, reply, and between-replies slots.