87

What is the idea way to register/enqueue scripts and/or styles for use in plugins?

I recently made a plugin simple plugin to add the user avatar/gravatar with a shortcode. I have different style options for displaying the avatar (square, round, etc.) and decided to put the css directly in the shortcode itself.

However, I realize now this isn't a good approach since it will repeat the css every time the shortcode is used on a page. I've seen several other approaches on this site and the wp codex even has two examples of their own so it's hard to know what approach is most consistent and fastest.

Here are the methods I currently am aware of:

Method 1: Include directly in shortcode - This is what I'm currently doing in the plugin, but doesn't seem good since it repeats code.

class My_Shortcode {
function handle_shortcode( $atts, $content="" ) {
/* simply enqueue or print the scripts/styles in the shortcode itself */
?>
<style type="text/css">

</style>
<?php
    return "$content";
     }
}
add_shortcode( 'myshortcode', array( 'My_Shortcode', 'handle_shortcode' ) );

Method 2: Use class for enqueueing scripts or styles conditionally

class My_Shortcode {
    static $add_script;
    static function init() {
        add_shortcode('myshortcode', array(__CLASS__, 'handle_shortcode'));
        add_action('init', array(__CLASS__, 'register_script'));
        add_action('wp_footer', array(__CLASS__, 'print_script'));
    }
    static function handle_shortcode($atts) {
        self::$add_script = true;
        // shortcode handling here
    }
    static function register_script() {
        wp_register_script('my-script', plugins_url('my-script.js', __FILE__), array('jquery'), '1.0', true);
    }
    static function print_script() {
        if ( ! self::$add_script )
            return;
        wp_print_scripts('my-script');
    }
}
My_Shortcode::init();

Method 3: Using get_shortcode_regex();

function your_prefix_detect_shortcode() {

    global $wp_query;   
    $posts = $wp_query->posts;
    $pattern = get_shortcode_regex();

    foreach ($posts as $post){
        if (   preg_match_all( '/'. $pattern .'/s', $post->post_content, $matches )
            && array_key_exists( 2, $matches )
            && in_array( 'myshortcode', $matches[2] ) )
        {
            // css/js 
            break;  
        }    
    }
}
add_action( 'wp', 'your_prefix_detect_shortcode' );

Method 4: Using has_shortcode();

function custom_shortcode_scripts() {
    global $post;
    if( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'myshortcode') ) {
        wp_enqueue_script( 'my-script');
    }
}
add_action( 'wp_enqueue_scripts', 'custom_shortcode_scripts');
4
  • 1
    I think Method 4: Using has_shortcode(); is best because it will ensure that scripts and styles will load once if post content has shortcode regardless of multiple uses of shortcode. Although it might not work for shortcode uses in widgets or in sidebar, not sure though. If it's for a plugin then I would not recommend you to tie scripts with shortcode because some might call your function instead of shortcode to get desired output.
    – Robert hue
    Commented Oct 18, 2014 at 7:46
  • 1
    Possible duplicate of How to add stylesheets only to pages with specific shortcode?
    – kontur
    Commented Mar 30, 2018 at 13:35
  • Method 4 is good but there is one problem it can create a problem if the shortcode is not directly added to the WP content area but some other third-party plugin options. It can be helpful to add a check in the shortcode function ! wp_style_is along with method 4 and enqueue the style there incase it's not added in the earlier wp_enqueue_scripts callback
    – Adeel Raza
    Commented Oct 14, 2020 at 10:23
  • Method 4 will not work if the requested page is something like home, index page and very first post has no shortcode in it. All the rest posts will not display shortcode CSS, JS properly.
    – vee
    Commented Jan 1, 2023 at 19:17

10 Answers 10

88

I found an other way that works well for me:

Here is an example plugin using this method that lets you use get_avatar in a shortcode. The stylesheet is only enqueued when the shortcode is present.

Usage (id defaults to current user):

[get_avatar id="" size="32" default="mystery" alt="Profile Photo" class="round"]

function wpse_165754_avatar_shortcode_wp_enqueue_scripts() {
    wp_register_style( 'get-avatar-style', plugins_url( '/css/style.css', __FILE__ ), array(), '1.0.0', 'all' );
}

add_action( 'wp_enqueue_scripts', 'wpse_165754_avatar_shortcode_wp_enqueue_scripts' );
if ( function_exists( 'get_avatar' ) ) {
    function wpse_165754_user_avatar_shortcode( $attributes ) {

        global $current_user;
        get_currentuserinfo();

        extract( shortcode_atts(
                     array(
                         "id"      => $current_user->ID,
                         "size"    => 32,
                         "default" => 'mystery',
                         "alt"     => '',
                         "class"   => '',
                     ), $attributes, 'get_avatar' ) );
        
        $get_avatar = get_avatar( $id, $size, $default, $alt );

        wp_enqueue_style( 'get-avatar-style' );

        return '<span class="get_avatar ' . $class . '">' . $get_avatar . '</span>';
    }

    add_shortcode( 'get_avatar', wpse_165754_user_avatar_shortcode' );
}
4
  • Forgot I asked this question last year, but I had actually started doing it this way as well in a lot of cases. It's kind of like combining method 1 and 2. Commented Jul 8, 2015 at 17:36
  • 7
    This method loads the CSS in the footer, which surely has limitations? Commented Apr 21, 2017 at 13:26
  • 2
    This is absolutely the right way to do this. Use this method any time you need to conditionally load a script or stylesheet when the wp_enqueue_scripts hook has already occurred. (Shortcodes are parsed during the_content which is a part of the the_post hook, meaning enqueueing when it is parsed is too late unless you've already registered the script) Commented Apr 20, 2019 at 21:56
  • 1
    This is not helpful as it creates FOUT when the style is loaded in the shortcode function. It's better to use the has_shortcode method.
    – Adeel Raza
    Commented Oct 14, 2020 at 10:27
35

Before starting answer I have to say that regarding this topic css and js are not the same.

The reason is simple: while adding js to the body of the page (in footer) is a common and valid way to go, css need to be placed in the <head> section of the page: even if majority of browsers can proper render css in page body, that's not valid HTML code.

When a shortcode is rendered, <head> section was already printed, it means that js can be added without any problem on footer, but css must be added before the shortcode is rendered.

Scripts

If your shortcode needs only js you are lucky and can just use wp_enqueue_script in the body of shortcode callback:

add_shortcode( 'myshortcode', 'my_handle_shortcode' );

function my_handle_shortcode() {
  wp_enqueue_script( 'myshortcodejs', '/path/to/js/file.js' );
  // rest of code here...
}

Doing so your script is added to the footer and only once, even if the shortcode is used more than once in the page.

Styles

If you code needs styles, then you need to act before shortcode is actually rendered.

There are different ways to do this:

  1. look at all the posts in current query and add the shortcode styles if needed. This is what you do in both method #3 and #4 in OP. In fact, both two methods do same thing, but has_shortcode was added in WP 3.6, while get_shortcode_regex is available since version 2.5, so use get_shortcode_regex only if you want make your plugin compatible with older versions.

  2. always add shortcode style, in all pages

Issues

Issue with #1 is performance. Regex are pretty slow operations and launch regex in a loop of all posts may slow down page consistently. Moreover, it's a pretty common task in themes to show only post excerpt in archives and show full content with shortcodes only in singular views. If that happen, when an archive is shown, your plugin will launch a regex matching in a loop with the aim to add a style that will never be used: an unnecessary double performance impact: slow down page generation + additional unnecessary HTTP request

Issue with #2 is performance, again. Adding style to all pages means add an additional HTTP request for all pages, even when not needed. Even if server side page generation time is not affected, total page rendering time will, and for all site pages.

So, what does a plugin developer should do?

I think that best thing to do is adding an option page to plugin, where users can choose if the shortcode should be handled in singular view only or even in archives. In both cases is better to provide another option to choose for which post types enable shortcode.

In that way is possible to hook "template_redirect" to check if current query satisfy requirements and in that case add the style.

If user choose to use shortcode only in singular post views, is a good idea check if post has shortcode or not: once only one regex is required it should not slow down page so much.

If user choose to use shortcode even in archives, then I would avoid to run regex for all posts if number of posts is high and just enqueue the style if query fit requirements.

What to consider "high" on this regard should be get using some performance tests or, as alternative, add another option and give choice to users.

3
  • 7
    It's worth noting here that <style> tags in the document body are now valid as per W3C spec. w3.org/TR/html52/document-metadata.html#the-style-element Commented Nov 16, 2018 at 22:30
  • @IsaacLubow Currently (2023): Contexts in which this element can be used: In a noscript element that is a child of a head element. Validated <style> inside <body> on validator.w3.org/nu/#textarea but result is Error: Element style not allowed as child of element body in this context.
    – vee
    Commented Jan 1, 2023 at 18:53
  • But <link> inside <body> is pass validation.
    – vee
    Commented Jan 1, 2023 at 18:56
11

For my plugin I found that sometimes users have a theme builder that has shortcode stored in post meta data. Here is what I am using to detect whether my plugin shortcode is present in current post or post meta data:

function abcd_load_my_shorcode_resources() {
       global $post, $wpdb;

       // determine whether this page contains "my_shortcode" shortcode
       $shortcode_found = false;
       if ( has_shortcode($post->post_content, 'my_shortcode') ) {
          $shortcode_found = true;
       } else if ( isset($post->ID) ) {
          $result = $wpdb->get_var( $wpdb->prepare(
            "SELECT count(*) FROM $wpdb->postmeta " .
            "WHERE post_id = %d and meta_value LIKE '%%my_shortcode%%'", $post->ID ) );
          $shortcode_found = ! empty( $result );
       }

       if ( $shortcode_found ) {
          wp_enqueue_script(...);
          wp_enqueue_style(...);
       }
}
add_action( 'wp_enqueue_scripts', 'abcd_load_my_shorcode_resources' );
7

WordPress has a built-in function to do something based on a specific Shortcode presenting status. The function name is has_shortcode(). You can use the following code to enqueue your style and scripts.

Here I used the is_a( $post, 'WP_Post' ) to check if the $post object is of the WP_Post class and I used $post->post_content to check the post content.

if ( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'shortcode_tag') ) {
    wp_enqueue_style( 'handle', get_template_directory_uri() . '/your_file_filename.css' );
}
4

I do so:

class My_Shortcode {

    function __construct() {
        do_action('my_start_shortcode'); // call
....

and catch hook in other functions (or other plugins):

function wpse_3232_load_script(){
    wp_enqueue_script('js-myjs');
}
add_action('my_start_shortcode','wpse_3232_load_script',10);
4

As someone mentioned using 'has_shortcode' will do it. Here's the example I found very helpful (Source: Wordpress.org)

function wpdocs_shortcode_scripts() {
    global $post;
    if ( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'wpdocs-shortcode') ) {
        wp_enqueue_script( 'wpdocs-script');
    }
}
add_action( 'wp_enqueue_scripts', 'wpdocs_shortcode_scripts');
2

We can indeed conditionally load CSS and JS files via shortcode without using any overly elaborate functions:

First, let's assume we registered all of our CSS/JS files previously (perhaps when wp_enqueue_scripts ran)

function prep_css_and_js() {
     wp_register_style('my-css', $_css_url);
     wp_register_script('my-js', $_js_url);
}
add_action( 'wp_enqueue_scripts', 'prep_css_and_js', 5 );

Next, lets examine our shortcode function:

function my_shortcode_func( $atts, $content = null ) {
  if( ! wp_style_is( "my-css", $list = 'enqueued' ) ) { wp_enqueue_style('my-css'); }
  if( ! wp_script_is( "my-js", $list = 'enqueued' ) ) { wp_enqueue_script('my-js'); }
  ...
}

Simple. Done. Ergo: we can "conditionally load" css/js files, (only when [shortcode] runs).

Let's consider the following ideology:

  • We want to load certain CSS/JS files (but only when shortcode is triggered)
  • Maybe we do not want those files to load on every page, or at all if the shortcode is not used on a page/post. (light weight)
  • We can accomplish this by making sure WP registers ALL of the css/js files prior to calling the shortcode and prior to the enqueue.
  • IE: Maybe during my prep_css_and_js() function I load ALL .css/.js files that are in certain folders...

Now that WP sees them as registered scripts, we can indeed call them, conditionally, based on a variety of situations, including but not limited to my_shortcode_func()

Benefits: (no MySQL, no advanced functions and no filters needed)

  • this is "WP orthodox", elegant, fast loading, easy and simple
  • allows compilers/minifiers to detect such scripts
  • uses the WP functions (wp_register_style/wp_register_script)
  • compatible with more themes/plugins that might want to de-register those scripts for a variety of reasons.
  • will not trigger multiple times
  • very little processing or code is involved

References:

1
  • This method loads styles below the footer. It's usually best to put these in the head.
    – leclaeli
    Commented May 8 at 20:24
1

I did find out for my own plugin, if the shortcode is present in the text widget.

function check_shortcode($text) {

  $pattern = get_shortcode_regex();

   if (   preg_match_all( '/'. $pattern .'/s', $text, $matches )
        && array_key_exists( 2, $matches )
        && in_array( 'myshortcode', $matches[2] ) )
    {
        // myshortcode is being used

        // enque my css and js
        /****** Enqueu RFNB  ******/
        wp_enqueue_style('css-mycss');
        wp_enqueue_script('js-myjs');

        // OPTIONAL ALLOW SHORTCODE TO WORK IN TEXT WIDGET
        add_filter( 'widget_text', 'shortcode_unautop');
        add_filter( 'widget_text', 'do_shortcode'); 

    }

  return $text;

}
add_filter('widget_text', 'check_shortcode');
0

I made a combination of the example code from the Wordpress page for has_shortcode() and the answer zndencka gave. I did notice that the has_shortcode function is added in Wordpress 3.6, so that's why I first check if the function exists. Maybe that check is a bit obsolete though, since there aren't many users from below wordpress 3.6 anymore according to wordpress their own stats.

// This makes sure the styling is already enqueued in the header, so before the shortcode loads in the page/post
function enqueue_shortcode_header_script() {
    global $post;
    if ( function_exists( 'has_shortcode' ) ){  // is added in wordpress 3.6
        // Source: https://codex.wordpress.org/Function_Reference/has_shortcode
        if( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'my_shortcode') ) {
            wp_enqueue_style( 'shortcode-css' );
        }
    }
    else if ( isset($post->ID) ) { // Small percentage wordpress users are below 3.6 https://wordpress.org/about/stats/
        global $wpdb;
        $result = $wpdb->get_var(
          $wpdb->prepare(
              "SELECT count(*) FROM $wpdb->postmeta " .
              "WHERE post_id = %d and meta_value LIKE '%%my_shortcode%%'",
               $post->ID )
        );
        if (!empty( $result )) { wp_enqueue_style( 'shortcode-css' ); }
    }
}
add_action( 'wp_enqueue_scripts', 'enqueue_shortcode_header_script');
0

I use WordPress Version 5.4 with OOP style of code i dont know if this affect why none of the above solutions didn't work for me so i come up with this solution:

public function enqueue_scripts() {
 global $post;
 //error_log( print_r( $post, true ) );
 //error_log( print_r(  $post->post_content, true ) );
 //error_log( print_r(  strpos($post->post_content, '[YOUR_SHORTCODE]'),true));

 if ( is_a( $post, 'WP_Post' ) && strpos($post->post_content, '[YOUR_SHORTCODE]') ) 
 {
     wp_register_style('my-css', $_css_url);
     wp_register_script('my-js', $_js_url);
 }
}

Hope this help someone.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.