How to Create a Responsive JetEngine Trim Text Callback with Links

To trim text differently on desktop, tablet and mobile in JetEngine, register a custom Dynamic Field callback with three filters: jet-engine/listings/allowed-callbacks, jet-engine/listings/allowed-callbacks-args and jet-engine/listing/dynamic-field/callback-args. The snippet on this page adds a Trim text (responsive) option to the Callback dropdown. It has its own word limit for each device and an optional toggle that links the trimmed text to the post, with or without a new tab. In your listing, turn on Filter field output, pick the callback, set the three lengths, and you are done.
- What this callback does
- Do you need custom code at all?
- How the three JetEngine hooks fit together
- Add the snippet to WordPress
- Use the callback in a listing
- Test that it works
- Troubleshooting
- Security notes
What this callback does
The snippet extends the JetEngine Dynamic Field (Elementor widget, Gutenberg block or Bricks element) so you can:
- Trim text by device: show fewer words on tablet and mobile than on desktop.
- Set the length per device in the editor: three number fields, no code changes per listing.
- Make the trimmed text a link to the current post, which is handy for trimmed titles in cards.
- Open the link in a new tab if you want, with
rel="noopener"added automatically. - Reuse it everywhere: it is one snippet that works in every listing on the site.
It outputs three short versions of the text, each in its own span, and a small CSS block shows only one of them per screen size. We use CSS instead of detecting the device in PHP because server-side detection breaks as soon as a page cache serves the same HTML to every visitor.
Do you need custom code at all?
Maybe not. Crocoblock ships an official Trim String Callback as a JetEngine External Module. Install it from WordPress Dashboard > JetEngine > JetEngine, Modules tab, External modules section. It adds a callback called "Trim string by chars or words" with two settings: Trimmed Type (Chars or Words) and String length. See the JetEngine dashboard settings overview for the module list.
That module is the right choice when one length for all screens is enough. It also exists for a reason: JetEngine's built-in trimming only covers the post excerpt, not meta fields. Use the custom snippet below when you need:
- different lengths on desktop, tablet and mobile, or
- the trimmed text wrapped in a link to the post.
How the three JetEngine hooks fit together
Crocoblock documents this pattern in its custom callback example, and its own trim plugin is built the same way. Knowing what each hook does makes the snippet easy to adapt.
jet-engine/listings/allowed-callbacksreceives an array offunction_name => label. You add your entry and return the array. The key must be the name of a real PHP function, because that is what JetEngine calls.jet-engine/listings/allowed-callbacks-argsreceives the control definitions, keyed by setting name and written like Elementor controls. Each control gets aconditionwithdynamic_field_filter => 'yes'andfilter_callback => array( 'your_callback' ), so it shows only when your callback is selected.jet-engine/listing/dynamic-field/callback-argsreceives four arguments: the argument array, the selected callback name, the widget settings and the widget instance. The snippet uses the first three. The first item in the array is the value the Dynamic Field received (a title, a meta value, an ID). You append your settings to it, and JetEngine passes the whole array to your function.
The controls use only plain types (number and switcher) and no Elementor constants. Crocoblock recommends this so the callback settings are not tied to Elementor.
Add the snippet to WordPress
You can paste the code into a child theme's functions.php file, but a snippets plugin such as Code Snippets or WPCode is easier to manage and switch off. Either way:
- Create a new PHP snippet and paste the code below. It starts without an opening
<?phptag; add one only if you put it in a file that does not already have it. - Set the snippet to run everywhere, not on the front end only. The controls are registered while the editor loads, so admin-only or front-end-only snippets will hide the callback or its output.
- Save and activate it.
If JetEngine is deactivated, its filters never fire, so the callback does nothing and causes no errors. Only the few CSS rules from part 5 are still printed. All function names use the bmc_ prefix to avoid clashes with other plugins.
Here is the complete snippet:
Code
/**
* BoostMyCroco: responsive trim text callback for the JetEngine Dynamic Field.
* Adds "Trim text (responsive)" to the Callback dropdown with per-device
* word limits and an optional link to the current post.
* If JetEngine is inactive, the filters never fire and the callback does nothing;
* only the CSS rules from part 5 are still printed.
*/
// 1. Add the callback to the Dynamic Field "Callback" dropdown.
add_filter( 'jet-engine/listings/allowed-callbacks', 'bmc_trim_register_callback' );
function bmc_trim_register_callback( $callbacks ) {
$callbacks['bmc_responsive_trim'] = 'Trim text (responsive)';
return $callbacks;
}
// 2. Add the settings that appear when the callback is selected.
add_filter( 'jet-engine/listings/allowed-callbacks-args', 'bmc_trim_register_controls' );
function bmc_trim_register_controls( $controls ) {
$condition = array(
'dynamic_field_filter' => 'yes',
'filter_callback' => array( 'bmc_responsive_trim' ),
);
$controls['bmc_trim_desktop'] = array(
'label' => 'Words on desktop',
'type' => 'number',
'default' => 20,
'min' => 1,
'condition' => $condition,
);
$controls['bmc_trim_tablet'] = array(
'label' => 'Words on tablet',
'type' => 'number',
'default' => 15,
'min' => 1,
'condition' => $condition,
);
$controls['bmc_trim_mobile'] = array(
'label' => 'Words on mobile',
'type' => 'number',
'default' => 10,
'min' => 1,
'condition' => $condition,
);
$controls['bmc_trim_link'] = array(
'label' => 'Link to the post',
'type' => 'switcher',
'default' => '',
'condition' => $condition,
);
$controls['bmc_trim_new_tab'] = array(
'label' => 'Open link in a new tab',
'type' => 'switcher',
'default' => '',
'condition' => array_merge( $condition, array( 'bmc_trim_link' => 'yes' ) ),
);
return $controls;
}
// 3. Pass the saved settings to the callback as extra arguments.
add_filter( 'jet-engine/listing/dynamic-field/callback-args', 'bmc_trim_callback_args', 10, 3 );
function bmc_trim_callback_args( $args, $callback, $settings = array() ) {
if ( 'bmc_responsive_trim' !== $callback ) {
return $args;
}
$args[] = isset( $settings['bmc_trim_desktop'] ) ? $settings['bmc_trim_desktop'] : 20;
$args[] = isset( $settings['bmc_trim_tablet'] ) ? $settings['bmc_trim_tablet'] : 15;
$args[] = isset( $settings['bmc_trim_mobile'] ) ? $settings['bmc_trim_mobile'] : 10;
$args[] = isset( $settings['bmc_trim_link'] ) ? $settings['bmc_trim_link'] : '';
$args[] = isset( $settings['bmc_trim_new_tab'] ) ? $settings['bmc_trim_new_tab'] : '';
return $args;
}
// 4. The callback: three trimmed versions (one per device), optionally linked.
function bmc_responsive_trim( $value = '', $desktop = 20, $tablet = 15, $mobile = 10, $link = '', $new_tab = '' ) {
if ( is_array( $value ) || is_object( $value ) ) {
return '';
}
$value = (string) $value;
if ( '' === trim( $value ) ) {
return '';
}
$lengths = array(
'desktop' => $desktop,
'tablet' => $tablet,
'mobile' => $mobile,
);
$html = '';
foreach ( $lengths as $device => $length ) {
$length = absint( $length );
if ( $length < 1 ) {
$length = 20;
}
// wp_trim_words() strips HTML tags before counting words.
$trimmed = wp_trim_words( $value, $length, '...' );
$html .= '<span class="bmc-trim bmc-trim--' . $device . '">' . esc_html( $trimmed ) . '</span>';
}
// Elementor saves switchers as 'yes'; the block editor and Bricks save true.
$link = filter_var( $link, FILTER_VALIDATE_BOOLEAN );
$new_tab = filter_var( $new_tab, FILTER_VALIDATE_BOOLEAN );
if ( $link ) {
$url = get_permalink();
if ( $url ) {
$target = $new_tab ? ' target="_blank" rel="noopener"' : '';
$html = '<a class="bmc-trim-link" href="' . esc_url( $url ) . '"' . $target . '>' . $html . '</a>';
}
}
return $html;
}
// 5. Show one version per screen size.
// Change the max-width values to match your theme or builder breakpoints.
add_action( 'wp_head', 'bmc_trim_print_css' );
function bmc_trim_print_css() {
echo '<style id="bmc-trim-css">'
. '.bmc-trim--tablet,.bmc-trim--mobile{display:none;}'
. '@media (max-width:1024px){.bmc-trim--desktop{display:none;}.bmc-trim--tablet{display:inline;}}'
. '@media (max-width:767px){.bmc-trim--tablet{display:none;}.bmc-trim--mobile{display:inline;}}'
. '</style>';
}Part 1 puts the callback in the dropdown, part 2 adds the five settings, and part 3 hands those settings to bmc_responsive_trim() in the same order as its parameters. The callback trims the value three times with wp_trim_words(), escapes each result with esc_html() and wraps them in spans. If the link toggle is on, it wraps the spans in an anchor built with esc_url( get_permalink() ). Part 5 prints a few CSS rules in the page head so only one span is visible at a time.
Use the callback in a listing
- Open your listing item template (JetEngine > Listings) in Elementor, the block editor or Bricks.
- Add a Dynamic Field, or select an existing one, and choose the source, for example the post title or a text meta field.
- Turn on Filter field output. Callbacks only appear in the Callback dropdown when this toggle is on.
- In Callback, choose Trim text (responsive).
- Set Words on desktop, Words on tablet and Words on mobile.
- To make the text clickable, turn on Link to the post. A second toggle, Open link in a new tab, appears below it.
- Save the listing and place it on a page with a Listing Grid.
Match your breakpoints
The CSS uses 1024px and 767px as example breakpoints. Change both max-width values in part 5 so they match the tablet and mobile breakpoints in your theme or page builder settings. If you prefer to keep CSS in one place, delete part 5 and paste the same rules into your theme's custom CSS instead.
Test that it works
- Open the page on the front end and view the source. Each listing item should contain three spans with the classes
bmc-trim--desktop,bmc-trim--tabletandbmc-trim--mobile. - Open your browser's device toolbar and switch between widths. You should see the text get shorter at each breakpoint, never two versions at once.
- If you turned on the link, click a few items. Each one should open its own post, and in a new tab only when that toggle is on.
The CSS is printed on the front end. If an editor preview shows all three versions next to each other, check the front end before changing anything.
Troubleshooting
- The callback is missing from the dropdown. Check that Filter field output is on, that the snippet is active and set to run everywhere, and that the array key in part 1 matches the function name exactly (
bmc_responsive_trim). - The callback is there but the settings are not. The
conditionin part 2 must use the same callback name as part 1. A typo in either one hides the controls. - All three versions show at once. The CSS is not loading. A CSS optimization or minify plugin may be stripping or delaying it, or a cached page was built before you added the snippet. Clear all caches and check the source for
bmc-trim-css. - Bold, italics or links inside the text disappear. That is expected.
wp_trim_words()strips HTML before counting words, so the output is always plain text. - The link goes to the wrong page.
get_permalink()points to the current post, which is correct in post listings. In term, user or custom content type listings it can point somewhere unexpected, so turn the link off there or adapt the URL logic and test it inside that listing.
Trim by characters instead of words
If you need characters rather than words, replace the wp_trim_words() line inside the loop with this (requires the PHP mbstring extension, which most hosts enable):
$trimmed = mb_strimwidth( wp_strip_all_tags( $value ), 0, $length, '...' );Rename the labels in part 2 to "Characters on desktop" and so on, and raise the defaults, since 20 characters is much shorter than 20 words.
Security notes
Any callback that returns HTML must escape what it outputs. This snippet runs every text value through esc_html() and the URL through esc_url(), so a meta field containing script tags cannot inject markup into your listing. Keep that escaping if you change the code. JetEngine itself has had several security fixes, so keep the plugin updated and review your snippets after major updates. For other ways listings break, our guide to common JetEngine Query Builder mistakes covers the query side.
With one snippet, every Dynamic Field on your site can show shorter text on small screens and link it to the post, and you control both from the editor. If you would rather have a developer build or adapt this for your listings, post a request on BoostMyCroco.
Frequently Asked Questions
Why is my custom callback not showing in the JetEngine Dynamic Field?
Can JetEngine trim text by characters instead of words?
How do I change the ellipsis after the trimmed text?
How do I add a Read more link after the trimmed text?
Does this trim callback work in term or user listings?
Sources
- jet-engine-trim-callback/jet-engine-trim-callback.php at master · Crocoblock/jet-engine-trim-callback · GitHub (github.com)
- Dashboard Settings Overview (crocoblock.com)
- JetEngine Dynamic Field add custom callback (for example, add callback that partially masks an email with * or other symbols) · GitHub (gist.github.com)
- JetEngine WordPress Plugin Security Vulnerabilities (wpscan.com)
- Dynamic Field Widget Overview — JetEngine (crocoblock.com)


