Un bloc « portrait d’équipe » avec une seule image, un texte alternatif propre et un rendu qui respecte les dimensions réelles du fichier téléversé : voici étape par étape la construction complète, du placeholder vide jusqu’au rendu final côté front.
Étape 1 : déclarer les attributs
{
"attributes": {
"mediaId": { "type": "number" },
"mediaUrl": { "type": "string", "default": "" },
"mediaAlt": { "type": "string", "default": "" }
}
}
Stocker l’identifiant (mediaId) en plus de l’URL n’est pas superflu : il permet de retrouver l’image dans la médiathèque, de la remplacer facilement, et surtout de régénérer les tailles d’image côté serveur si un thème change ses dimensions par défaut.
Étape 2 : l’état vide avec MediaPlaceholder
import { MediaPlaceholder } from '@wordpress/block-editor';
if ( ! attributes.mediaUrl ) {
return (
<MediaPlaceholder
icon="format-image"
labels={ { title: 'Portrait' } }
onSelect={ ( media ) => {
setAttributes( {
mediaId: media.id,
mediaUrl: media.url,
mediaAlt: media.alt,
} );
} }
accept="image/*"
allowedTypes={ [ 'image' ] }
/>
);
}
MediaPlaceholder affiche par défaut un bouton d’import (glisser-déposer, sélection dans la médiathèque, ou saisie d’une URL externe selon la configuration). Il ne s’affiche que tant qu’aucune image n’est choisie, d’où la condition en tête de la fonction edit.
Étape 3 : remplacer l’image avec MediaUpload

Une fois une image choisie, il faut permettre à l’auteur de la changer sans tout recommencer. MediaUpload encapsule ce comportement via une fonction de rendu (render props) :
import { MediaUpload } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
<MediaUpload
onSelect={ ( media ) =>
setAttributes( {
mediaId: media.id,
mediaUrl: media.url,
mediaAlt: media.alt,
} )
}
allowedTypes={ [ 'image' ] }
value={ attributes.mediaId }
render={ ( { open } ) => (
<Button onClick={ open } variant="secondary">
Changer l'image
</Button>
) }
/>
La prop value alimentée avec mediaId permet à la médiathèque de présélectionner l’image actuelle quand elle s’ouvre, un détail qui évite à l’utilisateur de devoir la rechercher à nouveau parmi des centaines de fichiers.
Étape 4 : le texte alternatif, souvent oublié
Le texte alternatif récupéré automatiquement (media.alt) correspond au champ renseigné dans la médiathèque, pas nécessairement adapté au contexte précis du bloc. Un champ TextControl dans l’inspecteur, relié à mediaAlt, permet de le personnaliser sans modifier la médiathèque globale :
<TextControl
label="Texte alternatif"
value={ attributes.mediaAlt }
onChange={ ( mediaAlt ) => setAttributes( { mediaAlt } ) }
help="Décrit l'image pour les lecteurs d'écran."
/>
Étape 5 : un rendu responsive dans save()
export default function save( { attributes } ) {
const { mediaUrl, mediaAlt } = attributes;
if ( ! mediaUrl ) return null;
return (
<figure>
<img src={ mediaUrl } alt={ mediaAlt } loading="lazy" />
</figure>
);
}
L’attribut loading="lazy" sur l’élément img profite du chargement différé natif du navigateur sans script additionnel. Pour un rendu vraiment responsive avec plusieurs tailles (srcset), il est possible de basculer sur un bloc dynamique qui reconstruit la balise via wp_get_attachment_image côté PHP, au prix d’un aller-retour serveur supplémentaire.
- Toujours prévoir un état où
mediaUrlest vide, y compris danssave(), pour éviter un bloc cassé si l’attribut n’a jamais été rempli. - Ne jamais coder en dur un identifiant d’image issu d’un autre environnement : entre un serveur de développement et la production, les identifiants de la médiathèque ne coïncident pas forcément.
Un bloc image sans champ de texte alternatif accessible dans l’inspecteur reste, à nos yeux, un bloc incomplet, quelle que soit la qualité du reste de l’implémentation.
Notre verdict
MediaPlaceholder et MediaUpload couvrent, à eux deux, tout le cycle de vie d’une image dans un bloc personnalisé : sélection initiale, remplacement, et accès direct à la médiathèque native de WordPress sans réinventer un composant d’upload maison.