
Pour ajouter du JavaScript dans WordPress, ou une feuille CSS, on ne colle pas de balise <script> dans header.php : on déclare le fichier avec wp_enqueue_script() ou wp_enqueue_style(), dans une fonction accrochée au hook wp_enqueue_scripts, depuis le functions.php du thème (enfant) ou depuis une extension. WordPress imprime alors la balise au bon endroit, dans le bon ordre et une seule fois. Depuis WordPress 6.3, le cinquième argument accepte un tableau qui ajoute defer ou async : array( 'in_footer' => true, 'strategy' => 'defer' ).
Ce guide, à jour pour WordPress 7.1, couvre les paramètres, la différence avec wp_register_script, le chargement différé et conditionnel, les données vers JavaScript, les modules, le retrait d’un script et les erreurs fréquentes. Les exemples suivent les règles que j’applique en développement de thèmes WordPress sur mesure : code préfixé, conforme aux standards WordPress, et fichiers chargés uniquement là où ils servent.
Pourquoi passer par wp_enqueue_script plutôt qu’une balise script
Une balise écrite à la main échappe au gestionnaire de dépendances de WordPress. La file d’attente (« enqueue ») apporte :
- un ordre garanti : un script est toujours imprimé après ceux dont il dépend ;
- aucun doublon : un même identifiant (le « handle ») n’est chargé qu’une fois ;
- un fichier que l’on peut retirer ou remplacer depuis un thème enfant ou une extension ;
- un chargement différé sans casse : WordPress n’applique
deferouasyncque si l’ordre des dépendances le permet (voir plus bas).
Même le code d’un service tiers se met en file avec son URL complète, ou s’attache à un script avec wp_add_inline_script().
Les paramètres de wp_enqueue_script et wp_enqueue_style
wp_enqueue_script( $handle, $src, $deps, $ver, $args )
$handle: identifiant unique, à préfixer. Si un autre code a déjà enregistré ce handle, votre déclaration est ignorée.$src: URL complète, avecget_theme_file_uri()ouplugins_url().$deps: handles requis avant ce script ; jQuery seulement si votre code l’utilise.$ver: ajouté à l’URL pour forcer le rechargement ;falseajoute la version de WordPress,nullrien.$args: depuis 6.3,in_footer,strategy,fetchpriority(6.9) etmodule_dependencies(7.0). Un booléen vautin_footer.
wp_enqueue_style( $handle, $src, $deps, $ver, $media )
Le cinquième argument d’une feuille de style est le média : 'all' par défaut, 'print' ou une requête média. Ce n’est pas un réglage « pied de page » : y passer true, comme dans l’ancienne version de cet article, produit un attribut media invalide.
| Position | wp_enqueue_script | wp_enqueue_style | Par défaut |
|---|---|---|---|
| 1 | $handle |
$handle |
obligatoire |
| 2 | $src : URL du .js |
$src : URL du .css |
vide (handle déjà enregistré) |
| 3 | $deps : handles de scripts |
$deps : handles de feuilles |
array() |
| 4 | $ver |
$ver |
false (version de WordPress) |
| 5 | $args : in_footer, strategy, fetchpriority, module_dependencies |
$media : all, print, requête média |
array() (en-tête, bloquant) / all |
Numéro de version et cache du navigateur
Trois options : une constante incrémentée à chaque livraison, la version du thème (wp_get_theme()->get( 'Version' )) ou filemtime(), qui change à chaque modification du fichier. filemtime() lit le disque à chaque page et émet un avertissement si le fichier manque : vérifiez son existence avant.
Charger un script et une feuille de style dans un thème ou une extension
Dans un thème ou un thème enfant
Voici l’exemple « simple » de l’ancienne version de cet article, corrigé : CSS dans assets/css/ et non plus dans /js/, pas de dépendance inutile à jQuery, script différé.
/**
* Charge la feuille de style et le script principal du thème sur le front.
*/
function devwp_enqueue_assets() {
$version = wp_get_theme()->get( 'Version' );
wp_enqueue_style(
'devwp-main',
get_theme_file_uri( 'assets/css/main.css' ),
array(),
$version
);
wp_enqueue_script(
'devwp-main',
get_theme_file_uri( 'assets/js/main.js' ),
array(),
$version,
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
}
add_action( 'wp_enqueue_scripts', 'devwp_enqueue_assets' );
Scripts et styles ont des registres séparés : le même handle sert aux deux. Le choix de la fonction de chemin compte si vous créez un thème enfant WordPress :
get_template_directory_uri()renvoie toujours le thème parent ;get_stylesheet_directory_uri()renvoie le thème actif, donc l’enfant ;get_theme_file_uri()cherche dans le thème actif puis se replie sur le parent : c’est la fonction que recommande le Theme Handbook.
Dans une extension
Dans le fichier principal d’une extension, l’URL se construit avec plugins_url() et __FILE__, le chemin disque avec plugin_dir_path(). La garde ABSPATH empêche l’appel direct du fichier.
<?php
/**
* Plugin Name: DevWP Exemple
*/
defined( 'ABSPATH' ) || exit;
define( 'DEVWP_EXEMPLE_VERSION', '1.0.0' );
function devwp_exemple_enqueue_assets() {
wp_enqueue_style( 'devwp-exemple', plugins_url( 'assets/css/front.css', __FILE__ ), array(), DEVWP_EXEMPLE_VERSION );
wp_enqueue_script(
'devwp-exemple',
plugins_url( 'assets/js/front.js', __FILE__ ),
array(),
DEVWP_EXEMPLE_VERSION,
array( 'strategy' => 'defer' )
);
}
add_action( 'wp_enqueue_scripts', 'devwp_exemple_enqueue_assets' );
__FILE__ désigne le fichier en cours : appelé depuis un fichier rangé dans un sous-dossier, plugins_url() partirait de ce sous-dossier. Définissez plutôt une constante dans le fichier principal, par exemple avec plugin_dir_url( __FILE__ ). C’est la structure que j’utilise en développement d’extensions WordPress sur mesure ; pour aller plus loin, voyez comment se former au développement de plugins WordPress.
wp_register_script ou wp_enqueue_script ?
Ce que fait chaque fonction
wp_register_script() déclare un script sans rien imprimer. wp_enqueue_script() le met en file pour l’imprimer ; si vous lui passez une URL et que le handle n’existe pas, elle l’enregistre au passage, mais elle n’écrase jamais un enregistrement existant (il faut alors wp_deregister_script()). Même logique pour wp_register_style() et wp_enqueue_style().
Quand enregistrer sans charger
Enregistrez quand le script ne sert que sous condition (shortcode, bloc, modèle de page), quand il est chargé depuis plusieurs endroits, ou quand il sert de dépendance : il sera chargé automatiquement avec le script qui en dépend. Mettez directement en file quand il sert partout, ou que la condition se vérifie dans le hook.
// 1. Déclarer, sans charger.
function devwp_register_assets() {
wp_register_script(
'devwp-carte',
get_theme_file_uri( 'assets/js/carte.js' ),
array(),
'1.2.0',
array( 'strategy' => 'defer' )
);
wp_register_style( 'devwp-carte', get_theme_file_uri( 'assets/css/carte.css' ), array(), '1.2.0' );
}
add_action( 'wp_enqueue_scripts', 'devwp_register_assets' );
// 2. Charger seulement quand le shortcode est affiché.
function devwp_carte_shortcode( $atts ) {
$atts = shortcode_atts( array( 'ville' => 'Orange' ), $atts, 'devwp_carte' );
wp_enqueue_script( 'devwp-carte' );
wp_enqueue_style( 'devwp-carte' );
return '<div class="devwp-carte" data-ville="' . esc_attr( $atts['ville'] ) . '"></div>';
}
add_shortcode( 'devwp_carte', 'devwp_carte_shortcode' );
Le shortcode est rendu après wp_head : la feuille de style part alors en pied de page, d’où un bref affichage sans mise en forme. Pour l’éviter, détectez le shortcode dès le hook (voir le chargement conditionnel).
Le hook wp_enqueue_scripts n’est pas la fonction wp_enqueue_script
wp_enqueue_scripts, avec un « s », est une action, déclenchée par la fonction wp_enqueue_scripts(), elle-même accrochée à wp_head en priorité 1. On y place les scripts et les styles du site public. Les autres contextes ont leur hook : admin_enqueue_scripts (qui reçoit $hook_suffix : comparez-le, par exemple à 'settings_page_mon-extension', pour ne charger le script que sur votre page de réglages), login_enqueue_scripts, enqueue_block_editor_assets pour l’interface de l’éditeur et enqueue_block_assets pour le contenu, dans l’éditeur comme sur le site.
defer, async et pied de page : l’argument $args depuis WordPress 6.3
defer ou async ?
- defer : téléchargé en parallèle, exécuté une fois le HTML analysé, dans l’ordre des balises. Le bon choix par défaut pour un script qui manipule la page.
- async : exécuté dès qu’il est téléchargé, sans ordre garanti. À réserver aux scripts indépendants (mesure d’audience, widget tiers).
- in_footer : imprimé avant
</body>. Avecdefer, l’emplacement compte moins. Avant WordPress 6.3, le tableau est lu commetrue: le script part en pied de page, sansdefer.
Comment WordPress respecte les dépendances
La stratégie demandée est une intention : WordPress calcule la stratégie « éligible » en parcourant les dépendances et les dépendants, pour ne jamais casser l’ordre d’exécution (voir la note officielle de WordPress 6.3). Un script différé dont un dépendant est bloquant redevient bloquant. Un script auquel est attaché un script en ligne en position 'after', la position par défaut de wp_add_inline_script(), est lui aussi traité comme bloquant : je l’ai vérifié dans le code de WordPress 7.1.3.
Pour contrôler, lisez le code source : data-wp-strategy indique la stratégie demandée, l’attribut defer ou async celle appliquée. Et n’ajoutez plus defer avec le filtre script_loader_tag : cette méthode ignore l’arbre des dépendances.
Depuis WordPress 6.9, la clé fetchpriority ('low', 'high' ou 'auto') règle la priorité de téléchargement : 'low' pour un script non essentiel, 'high' rarement. Une dépendance hérite de la priorité la plus haute de ses dépendants.
Et les feuilles de style ?
strategy ne concerne que les scripts. Pour les CSS, les leviers sont un média ciblé, qui ne bloque pas l’affichage, le chargement conditionnel, l’impression en ligne d’une petite feuille avec wp_style_add_data( $handle, 'path', $chemin ) (budget total de 40 000 octets par défaut depuis WordPress 6.9) et le CSS critique des extensions de performance, que je détaille pour optimiser les performances d’un site Avada.
Charger un script uniquement sur les pages qui en ont besoin
Quand je reprends un site, c’est souvent la première piste : des scripts de formulaire ou de diaporama chargés partout pour une seule page. Les balises conditionnelles (is_front_page(), is_page(), is_singular( 'product' )), has_block() et has_shortcode() s’utilisent directement dans la fonction accrochée au hook :
function devwp_conditional_assets() {
if ( is_singular() && has_block( 'core/gallery' ) ) {
wp_enqueue_script( 'devwp-lightbox' );
wp_enqueue_style( 'devwp-lightbox' );
}
$current = get_post();
if ( is_singular() && $current instanceof WP_Post && has_shortcode( $current->post_content, 'devwp_carte' ) ) {
wp_enqueue_style( 'devwp-carte' ); // Imprimée dans le head : pas de contenu sans style.
}
}
// Priorité 20 : après l'enregistrement des handles (priorité 10).
add_action( 'wp_enqueue_scripts', 'devwp_conditional_assets', 20 );
Pour vos blocs, déclarez viewScript, viewScriptModule et viewStyle dans block.json : WordPress les met en file au rendu du bloc, donc seulement sur les pages qui l’affichent.
Passer des données de PHP à JavaScript : wp_add_inline_script ou wp_localize_script
Selon la documentation officielle, wp_add_inline_script() est la bonne pratique pour transmettre des données ; wp_localize_script() est réservée aux chaînes à traduire, et convertit les valeurs simples du premier niveau en chaînes (true devient "1").
function devwp_enqueue_app() {
wp_enqueue_script(
'devwp-app',
get_theme_file_uri( 'assets/js/app.js' ),
array(),
'1.0.0',
array( 'strategy' => 'defer' )
);
$data = array(
'restUrl' => esc_url_raw( rest_url( 'devwp/v1/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
);
// Position « before » : le script garde sa stratégie defer.
wp_add_inline_script( 'devwp-app', 'const devwpApp = ' . wp_json_encode( $data ) . ';', 'before' );
}
add_action( 'wp_enqueue_scripts', 'devwp_enqueue_app' );
fetch( devwpApp.restUrl + 'favoris', {
method: 'POST',
headers: { 'X-WP-Nonce': devwpApp.nonce },
} );
Attention : un nonce expire après 12 à 24 heures, ce qui pose problème sur une page gardée plus longtemps en cache. Pour les textes du script, utilisez wp_set_script_translations().
Les modules JavaScript : wp_enqueue_script_module
Depuis WordPress 6.5, les modules ES (import / export) ont leur API : wp_register_script_module(), wp_enqueue_script_module(), wp_dequeue_script_module(). WordPress génère une « import map » : l’identifiant du module est le nom utilisé dans import. Une dépendance statique est préchargée ; une dépendance dynamique ('import' => 'dynamic') n’est chargée que si le code l’importe (voir la note de WordPress 6.5).
function devwp_enqueue_modules() {
wp_register_script_module( '@devwp/utils', get_theme_file_uri( 'assets/js/utils.js' ), array(), '1.0.0' );
wp_register_script_module( '@devwp/graphique', get_theme_file_uri( 'assets/js/graphique.js' ), array(), '1.0.0' );
$deps = array(
'@devwp/utils',
array(
'id' => '@devwp/graphique',
'import' => 'dynamic',
),
);
wp_enqueue_script_module( '@devwp/recherche', get_theme_file_uri( 'assets/js/recherche.js' ), $deps, '1.0.0' );
}
add_action( 'wp_enqueue_scripts', 'devwp_enqueue_modules' );
// Données transmises au module (WordPress 6.7 et plus).
function devwp_recherche_data( $data ) {
$data['restUrl'] = esc_url_raw( rest_url( 'wp/v2/search' ) );
return $data;
}
add_filter( 'script_module_data_@devwp/recherche', 'devwp_recherche_data' );
wp_localize_script() ne fonctionne pas avec les modules : depuis WordPress 6.7, le filtre script_module_data_{$module_id} imprime les données en JSON, que le module lit ainsi :
import { formaterDate } from '@devwp/utils';
let data = {};
const conteneur = document.querySelector( 'script[id="wp-script-module-data-@devwp/recherche"]' );
if ( conteneur instanceof HTMLScriptElement ) {
try {
data = JSON.parse( conteneur.text );
} catch {}
}
// data.restUrl contient l'URL transmise par PHP.
WordPress 6.9 ajoute aux modules un tableau $args (in_footer, pour les thèmes de blocs, et fetchpriority). WordPress 7.0 permet à un script classique d’importer dynamiquement des modules en les listant dans la clé module_dependencies de $args, à condition d’être en pied de page ou en defer.
D’où l’erreur « wp_enqueue_script type module not working » : passé à wp_enqueue_script(), un module est imprimé comme un script classique (« Cannot use import statement outside a module »), et un type="module" ajouté par filtre reste inconnu de WordPress : les modules qu’il importe ne sont ni ajoutés à l’import map ni préchargés. Utilisez l’API dédiée.
Retirer le script d’une extension : wp_dequeue_script
Une extension charge souvent ses fichiers sur toutes les pages. Sans la modifier, retirez-les là où ils ne servent pas, avec une priorité plus tardive que la sienne :
function devwp_dequeue_plugin_assets() {
if ( is_page( 'contact' ) ) {
return; // Le formulaire sert ici : on le garde.
}
// Remplacez par les handles réels, lus dans le code source de la page.
wp_dequeue_script( 'extension-formulaire' );
wp_dequeue_style( 'extension-formulaire' );
}
// Priorité 100 : après la mise en file par l'extension (souvent 10).
add_action( 'wp_enqueue_scripts', 'devwp_dequeue_plugin_assets', 100 );
- Le handle se lit dans le code source : identifiant
handle-jsouhandle-css. - Script mis en file plus tard (rendu d’un shortcode) : agissez sur
wp_footeravec une priorité inférieure à 20. wp_deregister_script()supprime la déclaration, pour la remplacer sous le même handle.- Prudence : ne retirez pas jQuery ni un script du cœur sans raison, et testez en préproduction.
C’est la même démarche que pour modifier une extension sans toucher à son code : on agit par les hooks.
Organiser les ressources d’un projet plus important
Sur un thème sur mesure, je déclare les ressources dans un tableau, je les enregistre en boucle, puis je ne mets en file que ce que la page demande. Cet exemple corrige les méthodes « automatisée » et « expert » de l’ancienne version.
function devwp_register_theme_assets() {
$assets = array(
'devwp-base' => array( 'type' => 'style', 'file' => 'assets/css/base.min.css' ),
'devwp-menu' => array( 'type' => 'script', 'file' => 'assets/js/menu.min.js' ),
'devwp-slider' => array( 'type' => 'script', 'file' => 'assets/js/slider.min.js' ),
);
foreach ( $assets as $handle => $asset ) {
$path = get_theme_file_path( $asset['file'] );
if ( ! file_exists( $path ) ) {
continue;
}
$url = get_theme_file_uri( $asset['file'] );
$version = (string) filemtime( $path );
if ( 'script' === $asset['type'] ) {
wp_register_script( $handle, $url, array(), $version, array( 'strategy' => 'defer' ) );
} else {
wp_register_style( $handle, $url, array(), $version );
}
}
wp_enqueue_style( 'devwp-base' );
wp_enqueue_script( 'devwp-menu' );
if ( is_front_page() ) {
wp_enqueue_script( 'devwp-slider' );
}
}
add_action( 'wp_enqueue_scripts', 'devwp_register_theme_assets' );
file_exists() n’est pas une mesure de sécurité : il évite seulement une URL en 404. Avec @wordpress/scripts, la compilation produit un fichier index.asset.php qui renvoie les dépendances et un hash de version : lisez-le plutôt que de recopier ces valeurs.
function devwp_enqueue_build() {
$asset_file = get_theme_file_path( 'build/index.asset.php' );
if ( ! file_exists( $asset_file ) ) {
return;
}
$asset = include $asset_file;
wp_enqueue_script(
'devwp-build',
get_theme_file_uri( 'build/index.js' ),
$asset['dependencies'],
$asset['version'],
array( 'strategy' => 'defer' )
);
}
add_action( 'wp_enqueue_scripts', 'devwp_enqueue_build' );
Pour garder ce code homogène, configurez les WordPress Coding Standards dans VS Code ou PhpStorm.
Pourquoi un script ne se charge pas : les erreurs fréquentes
Quand wp_enqueue_script « ne marche pas », je commence par le code source : balise id="handle-js" absente, le problème est côté PHP ; fichier en 404, c’est le chemin. Puis je vérifie :
- Le hook : un appel trop tôt (avant
init, par exemple au chargement d’une extension) est signalé en mode débogage ; aprèswp_footer(), rien n’est imprimé. - Le chemin :
get_template_directory_uri()dans un thème enfant, ou une barre oblique oubliée. - Le handle : déjà pris par une autre extension, vos paramètres sont ignorés.
- Une dépendance non enregistrée : le script n’est pas imprimé ; depuis WordPress 6.9.1, un avertissement le signale en mode débogage.
- Le thème : sans
wp_head()etwp_footer(), rien ne s’imprime. - Un script exécuté trop tôt : dans le
<head>sansdefer, le DOM n’existe pas encore. - jQuery : « $ is not a function » vient du mode noConflict ; déclarez
'jquery'en dépendance et utilisezjQuery( function( $ ) { … } ). - Le cache : changez
$ver, puis videz le cache de page et du CDN. - Les optimisations : minification, regroupement ou report de l’exécution du JavaScript (comme l’option de WP Rocket) peuvent casser l’ordre d’exécution ; excluez le fichier pour vérifier.
Questions fréquentes sur wp_enqueue_script
En résumé
Déclarez scripts et styles sur le hook wp_enqueue_scripts, avec la stratégie defer en cinquième argument, et ne chargez chaque fichier que là où il sert.
Besoin d’un thème dont les scripts sont chargés proprement ? Je réalise des thèmes WordPress sur mesure : présentez-moi votre projet.
Mis à jour en octobre 2026 pour WordPress 7.1, par un développeur WordPress freelance senior (14 ans d’expérience WordPress).