2026年11月12日
2026年11月12日
WordPress wp_head and wp_footer Hooks: Meta Tags, GTM, Inline Scripts
This article is a localized edition of the Japanese original. Read the Japanese original
wp_head, wp_body_open and wp_footer are action hooks run by functions (wp_head() and the others) that the theme calls at fixed positions in the HTML. WordPress core and plugins output CSS, JavaScript, meta tags and more there. When you add your own code, using these hooks instead of editing templates directly means your changes survive theme updates.
The priority values and functions in this article were checked against the source code of WordPress 6.9 as of November 2026 (wp-includes/default-filters.php and others).
Where the three hooks output
| Function (called by the theme) | Where the theme calls it | Main output |
|---|---|---|
wp_head() |
Just before </head> |
<title>, CSS, scripts placed in the head, canonical, site icon |
wp_body_open() |
Right after <body> (WordPress 5.2 and later) |
Admin bar (the Google Tag Manager noscript is also added here) |
wp_footer() |
Just before </body> |
Scripts placed in the footer |
In block themes (Twenty Twenty-Five and others), WordPress calls all three automatically, so the theme does not need to.
If you build your own classic theme, write these in header.php and footer.php. Without wp_head() or wp_footer(), plugin CSS and JavaScript and the admin bar stop appearing.
Example header.php:
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
Example footer.php:
<?php wp_footer(); ?>
</body>
</html>
If you do not write <title> yourself and instead specify add_theme_support( 'title-tag' ); in functions.php, WordPress outputs <title> in wp_head.
Load CSS and JavaScript files with wp_enqueue_scripts
Avoid writing CSS or JavaScript files directly into wp_head as <script src="…">. When you register them with the wp_enqueue_scripts hook, WordPress prevents duplicates, orders them by dependency and outputs them inside wp_head() or wp_footer().
<?php
add_action( 'wp_enqueue_scripts', function () {
// Load only on single posts
if ( is_singular( 'post' ) ) {
wp_enqueue_script(
'mytheme-share',
get_theme_file_uri( 'js/share.js' ),
array(),
'1.0.0',
array(
'in_footer' => true, // Output in wp_footer()
'strategy' => 'defer', // Add the defer attribute (WordPress 6.3 and later)
)
);
}
} );
Passing just true as the fifth argument means output in the footer. The array form with strategy was added in WordPress 6.3.
Things are easier to manage if you limit direct output in wp_head or wp_footer to what cannot be loaded as a file (meta tags, tags supplied by external services, small scripts whose values change per page).
Setting output order with priorities
The third argument of add_action() is the priority. Lower numbers run first (10 if omitted). The main things WordPress core registers on wp_head are:
| Priority | What runs |
|---|---|
| 1 | <title> output, running wp_enqueue_scripts, robots meta tag |
| 8 | CSS output (wp_print_styles) |
| 9 | Output of scripts placed in the head (wp_print_head_scripts) |
| 10 | canonical and others |
| 99 | Site icon |
| 101 | Output of "Additional CSS" |
On wp_footer, footer scripts are output at priority 20. So use a number below 20 for things that must come before the footer scripts, and above 20 for things that must come after.
Adding meta tags to the head (OGP example)
<?php
add_action( 'wp_head', function () {
if ( ! is_singular() ) {
return;
}
$post_id = get_queried_object_id();
$description = wp_strip_all_tags( get_the_excerpt( $post_id ) );
$image = get_the_post_thumbnail_url( $post_id, 'large' );
if ( ! $image ) {
$image = get_site_icon_url( 512 );
}
?>
<meta property="og:title" content="<?php echo esc_attr( get_the_title( $post_id ) ); ?>">
<meta property="og:description" content="<?php echo esc_attr( $description ); ?>">
<meta property="og:url" content="<?php echo esc_url( get_permalink( $post_id ) ); ?>">
<meta property="og:type" content="article">
<?php if ( $image ) : ?>
<meta property="og:image" content="<?php echo esc_url( $image ); ?>">
<?php endif; ?>
<?php
}, 5 );
- If no excerpt has been entered,
get_the_excerpt()generates one from the content. - SEO plugins such as Yoast SEO, All in One SEO and Rank Math output OGP, so if you use one, this code creates duplicates. Check the page source to make sure
og:titledoes not appear twice.
Adding Google Tag Manager
Google Tag Manager instructs you to place the code shown in its admin screen in two places: as high in the head as possible and right after <body>. Output them with wp_head and wp_body_open. Replace GTM-XXXXXXX with your container ID.
<?php
define( 'MYTHEME_GTM_ID', 'GTM-XXXXXXX' );
// Output as high in the head as possible
add_action( 'wp_head', function () {
?>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','<?php echo esc_js( MYTHEME_GTM_ID ); ?>');</script>
<!-- End Google Tag Manager -->
<?php
}, 1 );
// Output right after <body>
add_action( 'wp_body_open', function () {
?>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=<?php echo esc_attr( MYTHEME_GTM_ID ); ?>"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<?php
} );
- Other things also run at priority 1, so this is not guaranteed to be at the very top of the head. Within the same priority, callbacks run in the order they were registered, so theme code runs after core's. To output it earlier, use priority 0.
- If the theme does not call
wp_body_open(), the noscript part is not output. - To exclude your own visits from tracking, one option is to put
if ( current_user_can( 'manage_options' ) ) { return; }at the start of each function so nothing is output for administrators.
Passing values with inline scripts
To pass PHP values (such as the Ajax URL or a nonce) to JavaScript, use wp_add_inline_script(), which outputs them just before the target script.
<?php
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_script(
'mytheme-main',
get_theme_file_uri( 'js/main.js' ),
array(),
'1.0.0',
true
);
$data = array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'mytheme_nonce' ),
);
wp_add_inline_script(
'mytheme-main',
'const mythemeData = ' . wp_json_encode( $data ) . ';',
'before' // Output before main.js
);
} );
On pages where the script is not loaded, the inline script is not output either, so nothing is wasted. If you must write directly in wp_footer, still convert the values to JSON with wp_json_encode() before embedding them. Passing strings to echo as-is can break the script when a value contains quotes or </script>, or cause XSS.
Checking the output
- While logged out (or in a private window), open the page and view the page source (Ctrl+U on Windows).
- Search for the tags you added and check whether they appear before
</head>, right after<body>or before</body>. - If nothing appears, check that the theme has
wp_head(),wp_body_open()andwp_footer(), and that a caching plugin or server cache is not returning old HTML.
Summary
wp_headoutputs just before</head>,wp_body_openright after<body>, andwp_footerjust before</body>.- Register CSS and JavaScript files with
wp_enqueue_scripts, and limit direct output to meta tags and external service tags. - Output order is set by priority. CSS is 8, head scripts are 9 and footer scripts are 20.
- To pass PHP values to JavaScript, use
wp_add_inline_script()andwp_json_encode().
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)