Skip to content
ThemesIonic — home
WordPress Tutorials

Loading Scripts and Styles in the WordPress Admin

The hook fires on every admin page and passes the current screen's hook suffix, which is the only thing standing between a plugin that loads one file on one screen and a plugin that slows the whole dashboard.

Updated 4 min read intermediate

The hook passes the screen's hook suffix, and the whole discipline of admin assets is checking it. admin_enqueue_scripts fires on every admin page, once, with one argument: a string identifying the page. A callback that enqueues without looking at it loads its script on the dashboard, every post list, every settings screen, and inside the block editor's surrounding page. Nothing breaks; everything gets slightly slower, and after a dozen such plugins the admin is a known slow admin case.

The working shape

add_action( 'admin_enqueue_scripts', 'ti_admin_assets' );

function ti_admin_assets( $hook_suffix ) {
	if ( ! in_array( $hook_suffix, array( 'post.php', 'post-new.php' ), true ) ) {
		return;
	}

	$screen = get_current_screen();

	if ( ! $screen || 'product' !== $screen->post_type ) {
		return;
	}

	wp_enqueue_style( 'ti-product-admin', plugins_url( 'css/admin.css', __FILE__ ), array(), '1.4.0' );
	wp_enqueue_script( 'ti-product-admin', plugins_url( 'js/admin.js', __FILE__ ), array( 'jquery' ), '1.4.0', true );

	wp_add_inline_script( 'ti-product-admin', 'window.tiAdmin = ' . wp_json_encode( array(
		'nonce' => wp_create_nonce( 'ti_admin' ),
		'ajax'  => admin_url( 'admin-ajax.php' ),
	) ) . ';', 'before' );
}

The suffix narrows to the post edit screens; the screen object narrows to one post type. The version string on each asset becomes the cache-busting query string, so a release that changes admin.js must change the version too, or editors keep running the old file until their cache expires — the admin version of changes not showing.

Finding the hook suffix

Screen Suffix
Dashboard index.php
Post list edit.php (check $screen->post_type for which type)
Edit or add post post.php, post-new.php
Settings › General options-general.php
Plugin page under a top-level menu Return value of add_menu_page()
Plugin page under Settings Return value of add_submenu_page(), e.g. settings_page_ti-settings
Term edit term.php, edit-tags.php
User profile profile.php, user-edit.php

For your own pages, keep the return value rather than guessing the string: the generated suffix includes a sanitised menu title that changes with translation.

$ti_page_hook = add_options_page( 'TI Settings', 'TI Settings', 'manage_options', 'ti-settings', 'ti_render_settings' );

Compare $hook_suffix with $ti_page_hook in the enqueue callback. Alternatively, the page-specific actions fire only on that page and remove the check altogether: admin_print_styles-{$hook_suffix} for stylesheets and admin_print_scripts-{$hook_suffix} for scripts. The styles one matters — a stylesheet enqueued on the scripts action has already missed the point where admin styles are printed and ends up in the footer.

Contexts that use a different hook

  • Block editor extensions. enqueue_block_editor_assets is where scripts that register blocks, plugins or sidebars belong. The editor is an isolated application, and a script loaded on the surrounding page cannot reach its APIs reliably. The companion enqueue_block_assets loads in both the editor canvas and the front end.
  • Login page. login_enqueue_scripts. There is no admin on wp-login.php, and admin_enqueue_scripts does not fire.
  • Front end. wp_enqueue_scripts, covered in wp_enqueue_scripts. It is fired from wp_head, which the login page never calls, so front-end styles that reach the login screen were enqueued directly on init or on login_head, not through this hook.
  • Customizer preview and controls. customize_preview_init and customize_controls_enqueue_scripts.

A plugin that enqueues the same file on both wp_enqueue_scripts and admin_enqueue_scripts without a condition is loading it on every front-end page and every admin screen — though not inside the editor's iframed canvas, which only receives styles from enqueue_block_assets.

Dependencies and ordering

Admin screens already load jQuery, the media library scripts, and on edit screens the block editor's packages. Declaring dependencies rather than enqueuing your own copies keeps the load order correct and avoids double-loading jQuery, which breaks plugins that attached handlers to the first copy. Use wp_enqueue_media() when a settings page needs the media picker; it enqueues the whole set in the right order and exposes wp.media.

Inline configuration belongs with wp_add_inline_script() positioned before the handle, as above, or with wp_localize_script() for the older pattern. Printing a <script> from the callback bypasses the dependency system and runs before the file it configures has loaded on screens where the file is deferred.

Verify

Open the browser's network panel on three screens — the dashboard, an unrelated settings page, and the screen you targeted. The asset should appear on the third only. If it is on all three, the suffix check is not matching, and error_log( $hook_suffix ) for one page load tells you the actual string. If the script loads but window.tiAdmin is undefined, the inline script was attached to a handle that is not the one being printed, usually a typo between the enqueue handle and the inline-script handle.

Frequently asked

It is the return value of add_menu_page() or add_submenu_page(). Store it when you register the page and compare against it in the enqueue callback. For a top-level page it looks like toplevel_page_my-slug; for a submenu under Settings, settings_page_my-slug.
It probably does on the post editor, but not in the Site Editor or the widgets editor, which have different hook suffixes. enqueue_block_editor_assets fires in every editor context and only there, which is why it is the hook for anything that extends the editor; keep admin_enqueue_scripts for the surrounding screen.
It does not; the login page has no admin and runs login_enqueue_scripts instead. If styles appear there, something else is loading them — a wp_enqueue_style() call made directly on init or on login_head, since neither admin_enqueue_scripts nor wp_enqueue_scripts fires on wp-login.php.

Related guides