Skip to content

Instantly share code, notes, and snippets.

@gbyat
Created May 28, 2026 08:52
Show Gist options
  • Select an option

  • Save gbyat/5a7346e0eb62c8f8b7378e2bd71c9ce2 to your computer and use it in GitHub Desktop.

Select an option

Save gbyat/5a7346e0eb62c8f8b7378e2bd71c9ce2 to your computer and use it in GitHub Desktop.
WordPress Plugin Example Sticky First, Fixed Count
<?php
/**
* Plugin Name: Sticky First, Fixed Count
* Description: Keeps sticky posts first on blog home while preserving posts-per-page and pagination.
* Version: 1.0.0
* Author: webentwicklerin, Gabriele Laesser
* Author URI: https://webentwicklerin.at
*/
defined( 'ABSPATH' ) || exit;
/**
* Build sticky adjustments for the main home query.
*
* @param int $per_page Posts per page.
* @param int $page Current page.
* @param int[] $existing_not_in Existing excluded post IDs.
* @return array|null
*/
function sffc_get_sticky_adjustment( $per_page, $page, $existing_not_in = array() ) {
$sticky_posts = sffc_get_published_sticky_posts();
$sticky_count = count( $sticky_posts );
if ( 0 === $sticky_count || $per_page < 1 ) {
return null;
}
$page = max( 1, (int) $page );
$not_in = array_merge( (array) $existing_not_in, wp_list_pluck( $sticky_posts, 'ID' ) );
$not_in = array_values( array_unique( array_filter( array_map( 'intval', $not_in ) ) ) );
$adjustment = array(
'ignore_sticky_posts' => true,
'post__not_in' => $not_in,
'sffc_sticky_loop' => 1,
'sffc_sticky_per_page'=> $per_page,
'sffc_sticky_page' => $page,
);
// Keep configured page size unchanged.
$adjustment['posts_per_page'] = $per_page;
// From page 2 onward, compensate for sticky posts shown on page 1.
if ( 1 !== $page ) {
$adjustment['offset'] = ( ( $page - 1 ) * $per_page ) - $sticky_count;
if ( $adjustment['offset'] < 0 ) {
$adjustment['offset'] = 0;
}
}
return $adjustment;
}
/**
* Apply adjustments to main blog query.
*
* @param WP_Query $query Query object.
* @return void
*/
function sffc_pre_get_posts_main_home( $query ) {
if ( is_admin() || ! $query->is_main_query() || ! $query->is_home() ) {
return;
}
$per_page = (int) $query->get( 'posts_per_page' );
if ( $per_page < 1 ) {
$per_page = (int) get_option( 'posts_per_page' );
}
$page = sffc_get_current_page( $query );
$adjustment = sffc_get_sticky_adjustment( $per_page, $page, (array) $query->get( 'post__not_in' ) );
if ( null === $adjustment ) {
return;
}
foreach ( $adjustment as $key => $value ) {
$query->set( $key, $value );
}
}
/**
* Get published sticky posts in sticky order.
*
* @return WP_Post[]
*/
function sffc_get_published_sticky_posts() {
$sticky_ids = array_filter( array_map( 'intval', (array) get_option( 'sticky_posts', array() ) ) );
if ( empty( $sticky_ids ) ) {
return array();
}
$sticky_posts = get_posts(
array(
'post__in' => $sticky_ids,
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'post__in',
'ignore_sticky_posts' => true,
)
);
return is_array( $sticky_posts ) ? $sticky_posts : array();
}
/**
* Should sticky post-processing run for this query?
*
* @param WP_Query $query Query object.
* @return bool
*/
function sffc_should_apply_results( $query ) {
if ( is_admin() || ! is_a( $query, 'WP_Query' ) ) {
return false;
}
if ( $query->get( 'sffc_sticky_loop' ) ) {
return true;
}
return $query->is_main_query() && $query->is_home();
}
/**
* Resolve effective posts per page.
*
* @param WP_Query $query Query object.
* @return int
*/
function sffc_get_per_page( $query ) {
$per_page = (int) $query->get( 'sffc_sticky_per_page' );
if ( $per_page > 0 ) {
return $per_page;
}
$reading = (int) get_option( 'posts_per_page' );
if ( $reading > 0 ) {
return $reading;
}
$per_page = (int) $query->get( 'posts_per_page' );
if ( $per_page > 0 ) {
return $per_page;
}
return (int) get_option( 'posts_per_page' );
}
/**
* Resolve current page from query.
*
* @param WP_Query $query Query object.
* @return int
*/
function sffc_get_current_page( $query ) {
$page = (int) $query->get( 'sffc_sticky_page' );
if ( $page > 0 ) {
return $page;
}
$page = (int) $query->get( 'paged' );
if ( $page < 1 ) {
$page = (int) $query->get( 'page' );
}
if ( $page < 1 && isset( $_GET['query-page'] ) ) {
$page = (int) $_GET['query-page'];
}
if ( $page < 1 && ! empty( $_GET ) ) {
foreach ( array_keys( $_GET ) as $key ) {
if ( preg_match( '/^query-\d+-page$/', (string) $key ) ) {
$page = (int) $_GET[ $key ];
break;
}
}
}
return max( 1, $page );
}
/**
* Reorder result posts:
* - page 1: sticky first, total capped to per_page
* - page 2+: remove sticky and cap to per_page
*
* @param WP_Post[] $posts Query result posts.
* @param WP_Query $query Query object.
* @return WP_Post[]
*/
function sffc_apply_sticky_results( $posts, $query ) {
if ( ! sffc_should_apply_results( $query ) ) {
return $posts;
}
$per_page = sffc_get_per_page( $query );
$page = sffc_get_current_page( $query );
if ( $per_page < 1 ) {
return $posts;
}
if ( 1 === $page ) {
$sticky_posts = sffc_get_published_sticky_posts();
$sticky_ids = wp_list_pluck( $sticky_posts, 'ID' );
$sticky_count = count( $sticky_posts );
if ( $sticky_count >= $per_page ) {
$posts = array_slice( $sticky_posts, 0, $per_page );
} elseif ( $sticky_count > 0 ) {
$regular = array_values(
array_filter(
$posts,
static function ( $post ) use ( $sticky_ids ) {
return is_a( $post, 'WP_Post' ) && ! in_array( (int) $post->ID, $sticky_ids, true );
}
)
);
$regular = array_slice( $regular, 0, $per_page - $sticky_count );
$posts = array_merge( $sticky_posts, $regular );
$posts = array_slice( $posts, 0, $per_page );
}
} else {
$sticky_ids = wp_list_pluck( sffc_get_published_sticky_posts(), 'ID' );
if ( ! empty( $sticky_ids ) ) {
$posts = array_values(
array_filter(
$posts,
static function ( $post ) use ( $sticky_ids ) {
return is_a( $post, 'WP_Post' ) && ! in_array( (int) $post->ID, $sticky_ids, true );
}
)
);
}
$posts = array_slice( $posts, 0, $per_page );
}
return $posts;
}
/**
* Final safeguard after WP_Query sticky handling.
*
* @param WP_Post[] $posts Query result posts.
* @param WP_Query $query Query object.
* @return WP_Post[]
*/
function sffc_filter_the_posts( $posts, $query ) {
return sffc_apply_sticky_results( $posts, $query );
}
add_action( 'pre_get_posts', 'sffc_pre_get_posts_main_home' );
add_filter( 'the_posts', 'sffc_filter_the_posts', 999, 2 );
@Zodiac1978

Copy link
Copy Markdown

If you change the filename from gistfile1.txt to something with the suffix .php you get syntax highlighting in the gist!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment