Skip to content

Hooks: Actions and Filters ​

For developers who want to change Logo Carousel with code. You don't need any of this for normal use.

Most people never need this page. If the inspector, a module or a setting already does what you want, use that. It keeps working through updates and you have nothing to maintain.

If you do customize with code, WordPress hooks are named points where you can plug in your own function. A filter receives a value, lets you change it, and must return it. An action runs your code at a certain moment and returns nothing.

Where to put your code ​

Put snippets in a child theme's functions.php, a small custom plugin, or a must-use plugin (a PHP file in wp-content/mu-plugins/). Never edit the Logo Carousel files themselves, because every update overwrites them.

Test on staging first

A syntax error in functions.php can take the whole site down, admin included. Work on a staging copy, keep a backup, and make sure you have FTP or file-manager access before you start.

Filters must return a value

Every add_filter callback has to return something, usually the value it was handed, possibly changed. A missing return is the most common reason a snippet makes logos disappear.

Hook names use a few prefixes (sp_lcpro_, sp_logo_carousel_pro_, lcp_, sp_lc_). That's history, not meaning: copy each name exactly as written here.

Blocks ​

These affect the five blocks: Carousel, Grid, List, Marquee and Table.

Choose which blocks register ​

sp_lcpro_block_types receives the list of block folders the plugin loads: Carousel, Marquee, Grid, ListBlock and Table. Remove one and that block isn't registered at all.

php
// Hide the Table block from the inserter on this site.
add_filter( 'sp_lcpro_block_types', function ( $block_types ) {
	return array_values( array_diff( $block_types, array( 'Table' ) ) );
} );

For most sites the switch on Logo Carousel → Getting Started → Blocks in the dashboard is the easier way to do the same thing. Use the filter when you want the choice locked in code. Note the List block's folder is ListBlock, not List.

Removing a block that's already in use

If a page already contains that block, the editor shows it as an unsupported block and the front end prints nothing for it. The saved settings stay in the post, so adding the block back restores it.

Raise or lower the logo-count limits ​

The editor warns you as a block's logo collection grows, and stops you adding more at a hard limit. sp_lcpro_performance_limits controls the three thresholds:

KeyDefaultWhat happens at that count
warning_threshold100The sidebar starts showing logos in batches, with a See More button
soft_limit250A warning that large collections slow editing and rendering
hard_limit500Adding is blocked until you remove some logos
php
add_filter( 'sp_lcpro_performance_limits', function ( $limits ) {
	$limits['hard_limit'] = 300; // Keep editors from building huge blocks.
	return $limits;
} );

These limits are editor guardrails. They don't trim logos that are already saved in a block. See Performance for why big collections are worth avoiding.

When a block renders, each logo's image is picked according to the block's Resolution setting (Full, Large, Medium or Thumbnail). sp_logo_carousel_pro_logo_image_resolution lets you adjust the result per logo.

ParameterWhat it is
$resolvedArray with url, width, height and, when available, srcset
$itemThe logo as stored in the block (mediaId, title, url, categories, …)
$resolutionThe selected size: full, large, medium or thumbnail
php
// Drop srcset for SVG logos, which don't need resized copies.
add_filter( 'sp_logo_carousel_pro_logo_image_resolution', function ( $resolved, $item, $resolution ) {
	$path = isset( $resolved['url'] ) ? (string) wp_parse_url( $resolved['url'], PHP_URL_PATH ) : '';
	if ( '.svg' === strtolower( substr( $path, -4 ) ) ) {
		$resolved['srcset'] = '';
	}
	return $resolved;
}, 10, 3 );

Every block prints its own CSS, scoped to that block's uniqueId so two blocks on one page never clash (see uniqueId). sp_logo_carousel_pro_block_selectors receives the map of named selectors used to build that CSS.

ParameterWhat it is
$selectorsArray of selector strings, keyed by name (wrapper, search, marqueeTrack, …)
$unique_idThe block's uniqueId
$block_typeThe block slug, for example carousel or grid
php
add_filter( 'sp_logo_carousel_pro_block_selectors', function ( $selectors, $unique_id, $block_type ) {
	// Inspect what you're working with before changing anything.
	// error_log( print_r( array_keys( $selectors ), true ) );
	return $selectors;
}, 10, 3 );

This is an advanced hook. Rewriting a selector can stop the inspector's styles from applying. For visual tweaks, Custom CSS & JS is safer.

Ready Patterns and Build with AI ​

Turn off the pattern library ​

sp_lcpro_ready_patterns_enabled is checked before the Ready Patterns Library module switch. Return false and the library is off, whatever the dashboard says: the Use a Pattern button and the Patterns Library toolbar button disappear, and the pattern endpoints answer with an error.

php
add_filter( 'sp_lcpro_ready_patterns_enabled', '__return_false' );

Where patterns come from ​

Ready Patterns are fetched from a remote list. Two filters let you point that somewhere else, or switch remote fetching off.

php
// Return '' to stop fetching the remote pattern list.
add_filter( 'sp_lcpro_patterns_remote_url', function ( $url ) {
	return 'https://patterns.example.com/wp-json/logo-carousel/v1/pattern-list/';
} );

// URL for one pattern's full markup. The default is '' (empty), which means
// "replace /pattern-list/ in the list URL with /single-pattern/<id>".
add_filter( 'sp_lcpro_patterns_single_remote_url', function ( $url, $pattern_id, $remote_manifest ) {
	return $url;
}, 10, 3 );

Turn off Build with AI ​

lcpro_ai_builder_enabled is checked before the Build with AI module switch. Return false to remove Build with AI and Refine with AI from the editor and stop the generation endpoint answering.

php
add_filter( 'lcpro_ai_builder_enabled', '__return_false' );

The AI Builder also stays off when WordPress itself reports that AI features aren't supported on the site.

SVG uploads ​

Logo Carousel cleans every uploaded SVG before saving it, keeping only an allow-list of elements and attributes. Any on… event attribute is always removed, as are javascript:, data: and similar URLs. Three filters let you adjust the cleaner.

php
// Allow an element the default list leaves out.
add_filter( 'sp_lcpro_allowed_svg_elements', function ( $elements ) {
	$elements[] = 'feturbulence'; // For logos with a noise texture.
	return $elements;
} );

// Allow an extra attribute. Event handlers (on*) stay blocked regardless.
add_filter( 'sp_lcpro_allowed_svg_attributes', function ( $attributes ) {
	$attributes[] = 'word-spacing';
	return $attributes;
} );

// See (or change) the final cleaned markup before it's written to disk.
add_filter( 'sp_lcpro_sanitized_svg_markup', function ( $sanitized, $original ) {
	return $sanitized;
}, 10, 2 );

Widen the lists carefully

SVG files can carry scripts. Add only elements and attributes you understand, and never re-add anything script-related. Only administrators can upload SVG logos in the first place.

Access and permissions ​

Let other roles manage the plugin ​

By default only Administrators (manage_options) see the Logo Carousel admin screens. sp_lc_ui_permission changes the capability for the dashboard, the classic logo and view screens, the Saved Templates list and the dashboard's own requests.

php
add_filter( 'sp_lc_ui_permission', function () {
	return 'edit_others_posts'; // Editors and above.
} );

The blocks themselves don't use this filter. Anyone who can edit a post can add and configure a block.

Who sees the license notice ​

When the license isn't active, the front end shows an "activate the license" notice instead of the output, but only to users with this capability. Everyone else sees nothing. logo_carousel_pro_user_role_permission sets that capability for both blocks and the classic shortcode.

php
add_filter( 'logo_carousel_pro_user_role_permission', function () {
	return 'edit_pages';
} );

See Installation → Activate your license for the fix itself.

License activation URL ​

sp_site_home_url changes the site URL sent when you activate the license (single-site installs only; multisite always sends the network URL).

php
add_filter( 'sp_site_home_url', function ( $url ) {
	return 'https://www.example.com';
} );

Classic version ​

These hooks affect the Classic version: the logo post type, views and the [logocarousel] shortcode. They don't affect blocks.

Rename or reconfigure the logo post type ​

The logo post type (sp_logo_carousel) and its category taxonomy (sp_logo_carousel_cat) are registered with filterable labels and arguments. The logo post type's menu_name label is also the name of the top-level Logo Carousel admin menu.

php
add_filter( 'sp_logo_post_labels', function ( $labels ) {
	$labels['menu_name'] = __( 'Client Logos', 'my-theme' );
	return $labels;
} );

add_filter( 'sp_logo_post_args', function ( $args ) {
	$args['menu_position'] = 25; // Default 80.
	return $args;
} );

add_filter( 'sp_logo_carousel_cat_labels', function ( $labels ) {
	$labels['menu_name'] = __( 'Logo Groups', 'my-theme' );
	return $labels;
} );

add_filter( 'sp_logo_carousel_cat_args', function ( $args ) {
	return $args;
} );

These are the raw register_post_type() and register_taxonomy() arrays. Change one key and return the array; don't rebuild it.

Classic text strings ​

php
// Placeholder in the classic Ajax search box (default "Search Logo").
add_filter( 'lcp_search_placeholder_text', function ( $text ) {
	return __( 'Find a partner', 'my-theme' );
} );

// Message when a classic search finds nothing.
add_filter( 'lcp_edit_no_result_found_text', function ( $text ) {
	return __( 'No partners match that search.', 'my-theme' );
} );

// Message after the last page of classic pagination (default "No more logos available").
add_filter( 'sp_lcp_pagination_end_content_text', function ( $text ) {
	return __( "That's everyone!", 'my-theme' );
} );

Classic filter buttons ​

php
// Letters offered by the A–Z live filter (default A to Z).
add_filter( 'lcp_atoz_rang', function ( $letters ) {
	return array_merge( array( '0-9' ), $letters );
} );

// Category filter buttons. $groups is keyed by category name; each value has id, name, slug.
// $view_id is the view's post ID.
add_filter( 'sp_logo_categories_filter_sort', function ( $groups, $view_id ) {
	krsort( $groups ); // Z to A instead of A to Z.
	return $groups;
}, 10, 2 );

Classic queries ​

php
// suppress_filters on classic logo queries. The plugin sets this to true during
// Ajax requests itself; return false if a multilingual plugin needs to filter them.
add_filter( 'lcp_logo_suppress_filters', function ( $suppress, $view_id ) {
	return $suppress;
}, 20, 2 );

// Skip any item in a classic loop that isn't a logo post (default false).
add_filter( 'sp_logo_carousel_check_post_type', '__return_true' );

// suppress_filters for export queries (default 1), handy with WPML.
add_filter( 'lcp_wpml_export_suppress_filters', function () {
	return 0;
} );

After a view is imported ​

sp_logo_carousel_shortcode_imported fires once for every view created by Import & Export. It passes the new view's post ID.

php
add_action( 'sp_logo_carousel_shortcode_imported', function ( $new_view_id ) {
	update_post_meta( $new_view_id, '_imported_on', current_time( 'mysql' ) );
} );

Drag-and-drop ordering ​

The classic All Logos list supports drag-and-drop ordering. It uses the same hook names as the Post Types Order plugin, so code written for that plugin also works here: pto/posts_orderby, pto/posts_orderby/ignore and post-types-order_save-ajax-order.

Bundled options-framework hooks ​

The classic screens are built with a bundled copy of ShapedPlugin's options framework. It has its own hooks, all prefixed splogocarousel_, for example splogocarousel_init, splogocarousel_loaded, splogocarousel_enqueue, splogocarousel_color_palette, the typography font-list filters, and dynamic save hooks such as splogocarousel_{$unique}_save and splogocarousel_{$unique}_saved.

Treat these as private

They belong to the framework, not to Logo Carousel's feature set, and can change without notice. They're mentioned only so you recognise one if it turns up in a stack trace or an old snippet.

Hooks the plugin calls but doesn't own ​

HookOwner
widget_titleWordPress core, applied to the legacy widget's title
in_plugin_update_message-{$file}WordPress core, the update notice on the Plugins screen
edd_sl_api_request_verify_sslEasy Digital Downloads updater, SSL checks on license requests

JavaScript: events and re-initialising ​

There are no wp.hooks filters for the blocks. On the front end you get one DOM event and one global function.

sp-lcpro-swiper-ready ​

Fired when a Carousel block's slider (built on the Swiper library) has finished starting. It's dispatched on the carousel element itself (.sp-lcpro-logo-carousel.swiper) and doesn't bubble, so listen on that element. The Swiper instance is in event.detail.swiper.

Once ready, the element also has el.spLcproSwiper (the instance) and data-sp-lcpro-swiper-ready="1". With Carousel Starts on Screen on, the slider starts only when the block scrolls into view, so check both:

js
document.querySelectorAll( '.sp-lcpro-logo-carousel.swiper' ).forEach( ( el ) => {
	const onReady = ( swiper ) => {
		swiper.on( 'slideChange', () => {
			console.log( 'Now showing slide', swiper.realIndex );
		} );
	};

	if ( el.spLcproSwiper ) {
		onReady( el.spLcproSwiper ); // Already started.
	} else {
		el.addEventListener( 'sp-lcpro-swiper-ready', ( event ) => onReady( event.detail.swiper ) );
	}
} );

A carousel with one logo or none never starts a slider, so it never fires the event.

window.spLcproBlocksInit() ​

Starts every Logo Carousel block on the page that hasn't started yet (anything with data-sp-lcpro-block). Blocks that are already running are skipped, and each finished block gets data-sp-lcpro-runtime-ready="1", so calling it twice is safe.

Call it after you inject block markup with JavaScript, for example from an Ajax "load more posts" button or a modal that loads content:

js
fetch( '/my-endpoint/' )
	.then( ( response ) => response.text() )
	.then( ( html ) => {
		document.querySelector( '#results' ).insertAdjacentHTML( 'beforeend', html );
		if ( typeof window.spLcproBlocksInit === 'function' ) {
			window.spLcproBlocksInit();
		}
	} );

The page-builder integrations already call it for you.

Notes and caveats ​

  • Hooks aren't a public API contract. Re-test your snippets after a major update.
  • Blocks store their settings in the post. Most block behaviour is changed in the inspector, not with PHP. Check there first.
  • Keep callbacks cheap. Some of these run once per logo. A database query inside one multiplies across every logo on the page.
  • Use named functions if you'll ever need remove_filter. You can't remove an anonymous function.
  • Register early. Add hooks on plugins_loaded or init, or at the top level of your plugin or theme file, never from wp_footer.

Next steps ​