Use wp_enqueue_scripts, not wp_print_styles, to enqueue scripts and styles for the frontend

If you are enqueueing scripts and styles, you will want to use one of these three hooks:

  1. wp_enqueue_scripts (for the frontend)
  2. login_enqueue_scripts (for the login screen)
  3. admin_enqueue_scripts (for the admin dashboard)

Don’t let the names fool you — they are for both scripts and styles. We’ll probably add equivalent *_enqueue_styles hooks in 3.4 just to make it more obvious, but these hooks have all existed for some time now.

A possible incompatibility with WordPress 3.3 could arise if you are using the wp_print_styles hook to enqueue styles — your styles may end up in the admin.

The fix: Use wp_enqueue_scripts instead. Yes, it’s that easy.

Edit: Yes, the same goes for registering styles. Registering or enqueueing (styles or scripts) should occur on *_enqueue_scripts.

(Background: #19510)

#3-3, #dev-notes

Did you have at least one props on…

Did you have at least one props on a commit in 3.3? If so, you’re listed on the Credits screen in the WP dashboard. In the listing, your name links to your profile. Some people are shown as their real names, while others show as their trac/.org usernames. Now, if you’re all about the alias and you go by your trac/irc handle everywhere and want to keep it that way, that’s fine. But, if you would like people (curious users, colleagues, potential clients or employers, etc) to see your real name, all you have to do is add it to your profile.

Note 1: You may say, “But my username is my name, just without spaces and capital letters/a last name.” You’re still on the list, because it’s in username format.

Note 2: You may say, “Yes, I would really like people who google my real name to find my WP profile, but within the community, everyone knows me as my username. Quandary!” Not really. Take a page from some of the other people in your situation and put your username in parenthesis after your last name. In the coming year we’ll be making improvements to the profiles section, and having an optional way to display your username will hopefully be added.

Below is a list of everyone is the 3.3 credits that is listed by username rather than regular name. If you see your username on this list, click on it to go to your profile. Log in. Edit links will appear. Click the one in the top section that controls name and description, put your real name in the Name field, and save it. Voil , your real name will show up on the credits page.

AdamBackstrom, amereservant, ampt, andrewfrazier, arena, carlospaulino, Caspie, cebradesign, David, Da^MsT, deltafactory, demetris, designsimply, dgwyer, Digital Raindrops, dragoonis, DrewAPicture, eduplessis, Eightamrock, eko-fr, Elpie, elyobo, Empireoflight, evansolomon, fonglh, garyc40, GaryJ, goldenapples, goto10, hakre, Ipstenu, Jackson, Jayjdk, jeremyclarke, jgadbois, Jick, JohnONolan, jtclarke, kevinB, kitchin, Kuraishi, Latz, linuxologos, lukeschlather, Mako, MarcusPope, mark-k, masonjames, MattyRob, matveb, Maugly, mdawaffe, mitchoyoshitaka, Mr Papa, mrtorrent, natebedortha, olivM, olleicua, Otto, pagesimplify, paulhastings0, pavelevap, pete.mall, peterwilsoncc, ppaire, r-a-y, Rami Y, ruslany, ryanhellyer, saracannon, scottconnerly, sirzooro, tech163, TheDeadMedic, tmoorewp, vnsavage, wpweaver, WraithKenny

And to everyone who contributed to 3.3, thank you!

#3-3, #credits, #profiles

As of r19574 the new feature pointers are…

As of r19574, the new feature pointers are no longer shown for new installs, or users created after an update that added the pointer.

We left this alone through RC2 that way if you created a test installation or added new users during testing, you’d still see the pointers.


Plugin developers The new wp add script before…

Plugin developers: The new wp_add_script_before() function has been removed from WordPress 3.3. (In r19573.)

In an IRC discussion it was discovered that it was not architected in an ideal way, and some inconsistencies were discovered relating to how scripts are loaded. For more see the end of #11520.

If you want to echo data to be used in your script, you can continue to use wp_localize_script() as before. Since it now uses json_encode(), it is a bit more flexible (can do nested arrays, for example). In 3.4 we hope to introduce a few more enhancements in this area.

(For those just tuning in, please note that wp_add_script_before() was originally new to 3.3, so this will not affect existing plugins.)

#3-3, #dev-notes

Admin Bar API changes in 3.3

Howdy! The Admin Bar API has changed quite a bit in WordPress 3.3.

First up, what may break your plugin. The added items are no longer stored in a publicly accessible (but ideally privately used) menu property. So, if you were doing something like $wp_admin_bar->menu->..., you won’t get anything back.

The reason for this is that the internal structure has changed. Nodes are no longer internally stored in a tree. Now, they’re stored in a flat list, and the tree is bound together just before render. This makes the internal API much more stable, and allows us to provide plugin developers some nifty new tools. Even core only handles nodes using the same APIs developers use (mostly).

If you were to open the file you will notice there are a number of new methods and signatures. Here are the ones developers will want to know:

  • Add a node: add_node( $args ) or add_menu( $args ) (these are equivalent)
  • Remove a node: remove_node( $id ) or remove_menu( $id ) (these are equivalent)
  • Fetch a node’s properties: get_node( $id ).
  • Add a group, which wraps nodes (more on that in a bit): add_group( $args ).

Terminology notes

Before I continue: I’m using “item,” “node,” and “menu” synomyously throughout this post. They all are referring to a single link in the admin bar. Nodes can be parents for other nodes, which creates dropdown menus. The API previously emphasized add_menu(), but this can be confusing, so add_node() is now being promoted a bit more. Both methods do the same thing.

Since the optional admin bar has been merged into the admin header, we’re now calling it the toolbar in the UI.


An example of groups in the new toolbar.

Version 3.3 introduces the concept of groups. Groups share the same namespace (in terms of IDs) as nodes, and a node’s parent can be a group, rather than another node. Groups allow us to group nodes together into distinct sections of a menu. As an example, see the new W menu in the top left of the toolbar (screenshot at right). Here’s how it was constructed:
  • The W logo node is added. It has no parent.
  • The “About WordPress” node is added. To create a submenu, we set the parent to the W logo.
  • We add a group for external links. The group’s parent is also the W logo. It uses an additional CSS class to add the gray styling.
  • We then add the four external links, and set their parent to the external links group.

You can see these registrations in wp_admin_bar_wp_menu() and wp_admin_bar_add_secondary_groups().

Groups were added to provide us some styling flexibility and semantic divisions for a few different menus. (The right side of the top menu, “Howdy” and search, are their own group.) But the possibilities for plugins are pretty great. A plugin can bundle all of their nodes into a single group that then maintain visual separation from all other nodes in that menu. You can use the add_group() method to add groups.

Moving and modifying nodes

While this isn’t a change from WordPress 3.2, it’s worth pointing out that add_node() is able to take the ID of an existing node as an argument, and then replace the specified arguments. For example, if you want to make the Network Admin level a top-level item, try this:

add_action( 'admin_bar_menu', 'nacin_promote_network_admin_in_toolbar', 25 );
function nacin_promote_network_admin_in_toolbar( $wp_admin_bar ) {
    $wp_admin_bar->add_node( array(
        'id' => 'network-admin',
        'parent' => false,
    ) );

That was easy!

To make modifications easier, there’s now a get_node() to fetch a node’s properties, and even get_nodes() to fetch a flat list of all nodes, in case you want to loop through them.

#3-3, #dev-notes

What to watch for: Javascript and Editor changes in WordPress 3.3

Updated December 8.

There was an earlier post on JavaScript changes in 3.3, but a lot has changed, so here it is again (and updated).

If JavaScript or visual editing broke in your plugin, start here.

jQuery 1.7.1

This release had strong backwards compatibility over jQuery 1.6.1 (which was bundled in WordPress 3.2) but there is still a chance that plugin JavaScript has broken. We will always attempt to bundle the latest jQuery version with every major release of WordPress, so if you plan to use jQuery, you should follow that project as well.

jQuery UI 1.8.16

jQuery UI has been updated to the latest version, and all UI components are now included in core, including widgets and effects. This will make it a lot easier and simpler for plugins using UI components that are not used in core as they will be able to just enqueue whatever they need. (For reference, WordPress 3.2 included part of 1.8.12.)

The wp_editor() API

Since the last post there have been some bug fixes for wp_editor(). This is an updated API for both TinyMCE and Quicktags that outputs all parts of both editors in the same way as used on the Add / Edit Post screens. Plugins will be able to use the WordPress editor anywhere — including rendering the Visual/HTML tabs and the links to upload files and show the media library.

Example usage:

$content = 'The  content.';
$editor_id = 'foo';
$args = array(); // Optional arguments.
wp_editor( $content, $editor_id, $args );

Yeah, that’s it (though of course, you need to save it as well). Of note, there’s one pretty big gotcha: If you use wp_editor() to render the visual editor in a meta box, you risk problems. TinyMCE does not support being detached/moved in the DOM, which would occur when a meta box is dragged. (I’d look into the edit_form_advanced hook to render additional editors.)

For more, there’s a nascent Codex page on the API.


Since the previous post there have been a few improvements for Quicktags (the HTML editor toolbar), including better loading of the default buttons and “safe” close_all_tags() functionality. The major issue here is that we updated a JavaScript “API” that was almost as old as WordPress, so maintaining compatibility has been difficult.

Quicktags was refactored to make it fully multi-instance compatible (#16695). I think it still needs a Codex page, and I’ll ask @azaozz to post a tutorial here on how to use the new methods, and what before/after looks like in terms of converstion.

wp_localize_script() and wp_add_script_before()

When switching it to json_encode(), we opened up the possibility for encoding errors, so some changes were made to make this more backwards compatible. If you have previously used wp_localize_script() to pass arbitrary data (rather than localized strings), consider switching to wp_add_script_before().

Edit, December 8: wp_add_script_before() was removed.


The old SWFUpload has been replaced with Plupload. Nothing here has really changed since the previous post, other than strings and the presentation of the drop zone.

wp_enqueue_script() now works mid-page

Yes: wp_enqueue_script(), called mid-page, will now enqueue the script into the footer. This isn’t new from the previous update.

#3-3, #dev-notes

New API in 3.3: is_main_query()

There’s a nifty new method for WP_Query that is available in 3.3: is_main_query().

This enables someone to hook into pre_get_posts and modify only the main query. No more checking for suppress_filters (which was wrong), or comparing against $wp_the_query (complicated), or using query_posts() in a template.

is_main_query(), the function, will return true if the current $wp_query is also the main query. (As an example, this would be false after query_posts() is called but before wp_reset_query() is called.) This is consistent with existing conditional tags — for example, is_page() refers to the main query, while is_page() can also be called against any WP_Query object.

Quick example (YMMV) —

add_action( 'pre_get_posts', 'nacin_modify_query_exclude_category' );
function nacin_modify_query_exclude_category( $query ) {
    if ( $query->is_main_query() && ! $query->get( 'cat' ) )
        $query->set( 'cat', '-5' );

For more: #18677

#3-3, #dev-notes

Do not include wp-admin/includes/template.php to get add_meta_box()

Don’t include wp-admin/includes/template.php to get add_meta_box() defined. This is a very wrong way to go about adding meta boxes.

The proper way to call add_meta_box() is to consider it to be admin-only (because it is). What you do is call add_meta_box() on the admin_init hook, or even better, the add_meta_boxes or add_meta_boxes_{$page} hooks. (Where $page is either a post type or ‘link’ or ‘comments’.)

Then there’s no need to include an admin file — the function will always exist when it is called.

During 3.3’s development, we noticed that some plugins were doing this, so we wanted to make the PSA.

#3-3, #dev-notes

The admin_user_info_links filter is gone in 3.3

Version 3.3 combines the admin header and the admin bar into one toolbar in the dashboard.

As the “Howdy” dropdown menu in the admin is now part of the admin bar, we’ve dropped the admin_user_info_links filter. This filter was previously used by plugins to insert links near “Howdy, name | Logout” and eventually as items in the dropdown added in 3.2.

Now what you can do is add links to the account menu, like so:

add_action( 'admin_bar_menu', 'nacin_add_account_menu_item' );
function nacin_add_account_menu_item( $wp_admin_bar ) {
    $wp_admin_bar->add_node( array(
        'id'     => 'secondary-profile-page',
        'parent' => 'user-actions',
        'title'  => 'Personal Settings',
        'href'   => menu_page_url( ... ),
    ) );

This follows us dropping favorite actions (and specifically the favorite_actions filter) in 3.2 as the UI continues to be refined.

#3-3, #dev-notes

Help and screen API changes in 3.3

WordPress 3.3 introduces a new API for working with administration screen help content. add_contextual_help( $screen, $content ) is deprecated.

There are now tabs inside the help tab:

The way to add these are to attach a function to an existing hook such as the load-{$pagenow} hook, then using the add_help_tab() method of the current screen object.

For example:

add_action( 'admin_menu', 'nacin_add_special_theme_page' );
function nacin_add_special_theme_page() {
    $theme_page = add_theme_page( ... );
    if ( $theme_page )
        add_action( 'load-' . $theme_page, 'nacin_add_help_tabs_to_theme_page' );
function nacin_add_help_tabs_to_theme_page() {
    $screen = get_current_screen();
    $screen->add_help_tab( array(
        'id'      => 'additional-plugin-help', // This should be unique for the screen.
        'title'   => 'Special Instructions',
        'content' => '<p>This is the content for the tab.</p>',
        // Use 'callback' instead of 'content' for a function callback that renders the tab content.
    ) );

If you want to add a tab to an existing core screen, you’ll probably want to use the admin_head-{$pagenow} instead (to add them to the bottom), since core help tabs are not added until after load-{$pagenow}.

Help Sidebars

You can set the content of the right sidebar using $screen->set_help_sidebar( $content ) on the same hook where you used $screen->add_help_tab().

Removing Help Tabs

You can use $screen->remove_help_tab( $id ). Additionally, $screen->remove_help_tabs() will remove all tabs for a page.

Using the new WP_Screen object to adapt to screen contexts

The get_current_screen() function returns an object that includes the screen’s ID, base, post type, and taxonomy, among other data points. While get_current_screen() (and $current_screen, though the use of the global isn’t necessary) existed since 3.0, it now contains more accurate screen context (and the methods we covered above).

You can use $screen->id, $screen->base, etc., to ascertain which page you are on. This is helpful as seen above, to make sure that the help tab is only added to edit-tags.php if the taxonomy is post_tag. Of course, this is also helpful on the admin_enqueue_scripts hook (to figure out which scripts or styles to enqueue on a page) as it provides more context than a simple $pagenow or $hook_suffix.

More about what IDs and bases look like:

  • For edit.php (list for posts, pages, and post types), the ID is ‘edit-{$post_type}’ and the base is ‘edit’. The $post_type value is additionally set.
  • For post.php and post-new.php, the ID is {$post_type} and the base is ‘post’. For post-new.php, $screen->action is ‘add’.
  • For edit-tags.php, the ID is ‘edit-{$taxonomy}’ and the base is ‘edit-tags’. The $taxonomy and $post_type values are additionally set.
  • For all other core pages, the ID and base are generally the same and equivalent to the $pagenow value (minus ‘.php’).
  • For plugin pages, the ID is the value returned by add_menu_page(), add_submenu_page(), or the like, or get_plugin_page_hookname().

Note on add_contextual_help() and the contextual_help and contextual_help_list filters

The function and these filters continue to work, but their use is deprecated, and they are not ideal for use. They only work on a single chunk of text, rather than multiple tabs. So if the function is called or if the filters return any data, a separate ‘Overview’ tab will be added for compatibility sake.

#3-3, #dev-notes