Créer un site de petites annonces avec WordPress : guide technique complet
Un site de petites annonces est l’un des projets WordPress les plus rentables en Afrique de l’Ouest. Que ce soit pour l’immobilier à Dakar, les véhicules d’occasion, l’emploi ou les services, le modèle fonctionne parce que les gens cherchent activement ces plateformes. Ce guide vous montre comment construire un site type Expat-Dakar ou CoinAfrique avec WordPress, étape par étape.
1. Choisir le bon plugin de petites annonces
| Plugin | Prix | Points forts | Limites |
|---|---|---|---|
| ClassiPress (thème AppThemes) | ~69 $ | Solution tout-en-un, paiements intégrés, système de prix par catégorie | Design daté, mises à jour lentes |
| Flavor flavor flavor flavor flavor flavor flavor flavor | Gratuit + Pro 149 $/an | Moderne, compatible Elementor, système de paquets d’annonces | Version gratuite limitée |
| AWP Classifieds | Gratuit + modules payants | Léger, modulaire, bon pour démarrer | Moins de fonctionnalités natives |
| Solution Custom (CPT + ACF) | Gratuit | Contrôle total, performance optimale | Requiert du développement |
Recommandation pour le Sénégal : la solution custom avec CPT + ACF Pro vous donne le contrôle total et les meilleures performances sur les connexions mobiles. C’est ce que nous allons construire.
2. Créer le Custom Post Type « Annonce »
Ajoutez dans functions.php de votre thème enfant :
// Enregistrer le CPT Annonce
add_action('init', function() {
register_post_type('annonce', [
'labels' => [
'name' => 'Annonces',
'singular_name' => 'Annonce',
'add_new' => 'Publier une annonce',
'add_new_item' => 'Nouvelle annonce',
'edit_item' => 'Modifier l\'annonce',
'view_item' => 'Voir l\'annonce',
'search_items' => 'Rechercher une annonce',
],
'public' => true,
'has_archive' => true,
'rewrite' => ['slug' => 'annonces'],
'supports' => ['title', 'editor', 'thumbnail', 'author'],
'menu_icon' => 'dashicons-megaphone',
'show_in_rest' => true,
]);
// Taxonomie : Catégorie d'annonce
register_taxonomy('categorie_annonce', 'annonce', [
'labels' => [
'name' => 'Catégories',
'singular_name' => 'Catégorie',
],
'hierarchical' => true,
'rewrite' => ['slug' => 'catégorie'],
'show_in_rest' => true,
]);
// Taxonomie : Localisation
register_taxonomy('localisation', 'annonce', [
'labels' => [
'name' => 'Localisations',
'singular_name' => 'Localisation',
],
'hierarchical' => true,
'rewrite' => ['slug' => 'lieu'],
'show_in_rest' => true,
]);
});
// Catégories par défaut
add_action('init', function() {
$catégories = [
'Immobilier' => ['Appartements', 'Maisons', 'Terrains', 'Bureaux'],
'Véhicules' => ['Voitures', 'Motos', 'Pièces détachées'],
'Emploi' => ['Offres d\'emploi', 'Demandes d\'emploi', 'Stages'],
'Électronique' => ['Téléphones', 'Ordinateurs', 'TV & Audio'],
'Services' => ['Cours particuliers', 'Artisans', 'Transport'],
];
foreach ($catégories as $parent => $children) {
if (!term_exists($parent, 'categorie_annonce')) {
$term = wp_insert_term($parent, 'categorie_annonce');
$parent_id = $term['term_id'];
foreach ($children as $child) {
if (!term_exists($child, 'categorie_annonce')) {
wp_insert_term($child, 'categorie_annonce',
['parent' => $parent_id]);
}
}
}
}
// Localisations Sénégal
$villes = ['Dakar' => ['Plateau', 'Almadies', 'Parcelles Assainies',
'Guédiawaye', 'Pikine', 'Rufisque'], 'Thiès' => [],
'Saint-Louis' => [], 'Ziguinchor' => [], 'Kaolack' => []];
foreach ($villes as $ville => $quartiers) {
if (!term_exists($ville, 'localisation')) {
$term = wp_insert_term($ville, 'localisation');
foreach ($quartiers as $q) {
if (!term_exists($q, 'localisation')) {
wp_insert_term($q, 'localisation',
['parent' => $term['term_id']]);
}
}
}
}
}, 20);
3. Champs personnalisés avec ACF
Installez ACF (Advanced Custom Fields) et créez ces champs pour le CPT « annonce » :
| Champ | Type ACF | Options |
|---|---|---|
| Prix | Number | Suffixe : « FCFA », minimum : 0 |
| Type d’annonce | Select | Vente, Location, Don, Échange |
| État | Select | Neuf, Occasion – Bon état, Occasion – État moyen |
| Téléphone | Text | Placeholder : « +221 7X XXX XX XX » |
| True/False | Joignable sur WhatsApp | |
| Galerie photos | Gallery | Max 8 images, taille max 2 Mo |
| Urgent | True/False | Annonce urgente (mise en avant) |
4. Template d’affichage des annonces
CSS pour les cartes d’annonces
/* Grille d'annonces responsive */
.annonces-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
padding: 20px 0;
}
.annonce-card {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
transition: transform 0.2s, box-shadow 0.2s;
position: relative;
}
.annonce-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0,0,0,0.12);
}
.annonce-card__image {
width: 100%;
height: 200px;
object-fit: cover;
}
.annonce-card__badge {
position: absolute;
top: 12px;
left: 12px;
background: #e94560;
color: #fff;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.annonce-card__badge--urgent {
background: #ff6b35;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.annonce-card__body {
padding: 15px;
}
.annonce-card__title {
font-size: 16px;
font-weight: 600;
margin: 0 0 8px;
color: #1a1a2e;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.annonce-card__price {
font-size: 20px;
font-weight: 700;
color: #e94560;
margin: 0 0 8px;
}
.annonce-card__meta {
display: flex;
justify-content: space-between;
font-size: 13px;
color: #666;
}
.annonce-card__location::before {
content: "📍 ";
}
.annonce-card__whatsapp {
display: inline-flex;
align-items: center;
gap: 8px;
background: #25D366;
color: #fff;
padding: 10px 20px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
width: 100%;
justify-content: center;
margin-top: 12px;
}
/* Mobile : 1 colonne */
@media (max-width: 480px) {
.annonces-grid {
grid-template-columns: 1fr;
gap: 15px;
padding: 10px;
}
.annonce-card__image { height: 180px; }
}
Template PHP (archive-annonce.php)
<?php get_header(); ?>
<div class="annonces-container" style="max-width:1200px; margin:0 auto; padding:20px;">
<h1>Petites Annonces</h1>
<!-- Barre de recherche -->
<form class="annonces-search" method="GET" action="<?php echo get_post_type_archive_link('annonce'); ?>">
<input type="text" name="s" placeholder="Que recherchez-vous ?"
value="<?php echo esc_attr(get_search_query()); ?>"
style="flex:1; padding:12px; border:2px solid #ddd; border-radius:8px;">
<input type="hidden" name="post_type" value="annonce">
<?php wp_dropdown_categories([
'taxonomy' => 'categorie_annonce',
'name' => 'catégorie',
'show_option_all' => 'Toutes les catégories',
'hide_empty' => false,
]); ?>
<?php wp_dropdown_categories([
'taxonomy' => 'localisation',
'name' => 'lieu',
'show_option_all' => 'Toute localisation',
'hide_empty' => false,
]); ?>
<button type="submit">Rechercher</button>
</form>
<div class="annonces-grid">
<?php while (have_posts()) : the_post();
$prix = get_field('prix');
$type = get_field('type_annonce');
$urgent = get_field('urgent');
$location = wp_get_post_terms(get_the_ID(), 'localisation');
$whatsapp = get_field('whatsapp');
$téléphone = get_field('téléphone');
?>
<article class="annonce-card">
<?php if ($urgent) : ?>
<span class="annonce-card__badge annonce-card__badge--urgent">URGENT</span>
<?php elseif ($type) : ?>
<span class="annonce-card__badge"><?php echo esc_html($type); ?></span>
<?php endif; ?>
<?php if (has_post_thumbnail()) : ?>
<img class="annonce-card__image" src="<?php the_post_thumbnail_url('medium_large'); ?>"
alt="<?php the_title_attribute(); ?>" loading="lazy">
<?php endif; ?>
<div class="annonce-card__body">
<h3 class="annonce-card__title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h3>
<?php if ($prix) : ?>
<p class="annonce-card__price"><?php echo number_format($prix, 0, ',', ' '); ?> FCFA</p>
<?php endif; ?>
<div class="annonce-card__meta">
<span class="annonce-card__location">
<?php echo $location ? esc_html($location[0]->name) : 'Sénégal'; ?>
</span>
<span><?php echo human_time_diff(get_the_time('U'), current_time('timestamp')) . ' ago'; ?></span>
</div>
<?php if ($whatsapp && $téléphone) : ?>
<a class="annonce-card__whatsapp"
href="https://wa.me/<?php echo preg_replace('/[^0-9]/', '', $téléphone); ?>?text=Bonjour, je suis intéressé par : <?php echo urlencode(get_the_title()); ?>">
Contacter via WhatsApp
</a>
<?php endif; ?>
</div>
</article>
<?php endwhile; ?>
</div>
<?php the_posts_pagination(['mid_size' => 2]); ?>
</div>
<?php get_footer(); ?>
5. Soumission d’annonces en front-end
Permettez aux utilisateurs de publier des annonces sans accéder au back-office :
// Shortcode [formulaire_annonce]
add_shortcode('formulaire_annonce', function() {
if (!is_user_logged_in()) {
return '<div class="alert">
<a href="' . wp_login_url(get_permalink()) . '">Connectez-vous</a>
pour publier une annonce.</div>';
}
// Traitement du formulaire
if (isset($_POST['annonce_submit']) && wp_verify_nonce($_POST['annonce_nonce'], 'publier_annonce')) {
$post_id = wp_insert_post([
'post_title' => sanitize_text_field($_POST['titre']),
'post_content' => wp_kses_post($_POST['description']),
'post_type' => 'annonce',
'post_status' => 'pending', // Modération avant publication
'post_author' => get_current_user_id(),
]);
if ($post_id) {
update_field('prix', intval($_POST['prix']), $post_id);
update_field('type_annonce', sanitize_text_field($_POST['type']), $post_id);
update_field('téléphone', sanitize_text_field($_POST['téléphone']), $post_id);
update_field('whatsapp', isset($_POST['whatsapp']), $post_id);
// Gérer l'upload de la photo
if (!empty($_FILES['photo']['name'])) {
require_once ABSPATH . 'wp-admin/includes/image.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
$attach_id = media_handle_upload('photo', $post_id);
if (!is_wp_error($attach_id)) {
set_post_thumbnail($post_id, $attach_id);
}
}
wp_set_object_terms($post_id, intval($_POST['catégorie']), 'categorie_annonce');
wp_set_object_terms($post_id, intval($_POST['localisation']), 'localisation');
return '<div class="alert-success">Annonce soumise !
Elle sera visible après validation par notre équipe.</div>';
}
}
ob_start(); ?>
<form method="post" enctype="multipart/form-data" class="form-annonce">
<?php wp_nonce_field('publier_annonce', 'annonce_nonce'); ?>
<div class="form-group">
<label>Titre de l'annonce *</label>
<input type="text" name="titre" required maxlength="100"
placeholder="Ex: iPhone 13 Pro 128Go">
</div>
<div class="form-row">
<div class="form-group">
<label>Prix (FCFA) *</label>
<input type="number" name="prix" required min="0"
placeholder="Ex: 350000">
</div>
<div class="form-group">
<label>Type</label>
<select name="type">
<option value="Vente">Vente</option>
<option value="Location">Location</option>
<option value="Don">Don</option>
</select>
</div>
</div>
<div class="form-group">
<label>Description *</label>
<textarea name="description" required rows="5"
placeholder="Décrivez votre article en détail..."></textarea>
</div>
<div class="form-group">
<label>Photo principale</label>
<input type="file" name="photo" accept="image/*">
<small>Max 2 Mo. Formats : JPG, PNG, WebP</small>
</div>
<div class="form-row">
<div class="form-group">
<label>Téléphone *</label>
<input type="tel" name="téléphone" required placeholder="+221 7X XXX XX XX">
</div>
<div class="form-group">
<label><input type="checkbox" name="whatsapp"> Joignable sur WhatsApp</label>
</div>
</div>
<button type="submit" name="annonce_submit" class="btn-submit">
Publier mon annonce
</button>
</form>
<?php return ob_get_clean();
});
6. Monétisation du site d’annonces
Les modèles économiques qui fonctionnent au Sénégal :
Annonces premium payantes
// Système de mise en avant payante via PayDunya
add_action('wp_ajax_promouvoir_annonce', function() {
$post_id = intval($_POST['post_id']);
$duree = intval($_POST['duree']); // jours
$tarifs = [
7 => 2000, // 2 000 FCFA / semaine
30 => 5000, // 5 000 FCFA / mois
90 => 12000, // 12 000 FCFA / trimestre
];
$montant = $tarifs[$duree] ?? $tarifs[7];
// Créer la facture PayDunya
$invoice = [
'amount' => $montant,
'description' => 'Mise en avant annonce #' . $post_id . ' - ' . $duree . ' jours',
'callback_url' => home_url('/paydunya-callback/'),
'return_url' => get_permalink($post_id),
'custom_data' => ['post_id' => $post_id, 'duree' => $duree],
];
// Après paiement confirmé via callback :
// update_post_meta($post_id, 'annonce_premium', true);
// update_post_meta($post_id, 'premium_expiry', date('Y-m-d', strtotime("+$duree days")));
});
Modèle de revenus recommandé
- Annonces gratuites : publication basique, visibilité normale
- Annonce Urgente : 1 000 FCFA — badge « URGENT » + mise en avant 3 jours
- Annonce Premium : 2 000 FCFA/semaine — affichée en tête des résultats
- Pack Pro : 15 000 FCFA/mois — 10 annonces premium + logo vendeur
- Bannières publicitaires : vendez des espaces aux entreprises locales
7. Optimisations pour le marché sénégalais
- WhatsApp partout : le bouton WhatsApp est le principal canal de contact. Intégrez-le sur chaque annonce
- Images compressées : limitez les uploads à 2 Mo et convertissez en WebP automatiquement (plugin ShortPixel ou Imagify)
- Lazy loading : chargez les images uniquement quand elles apparaissent à l’écran (attribut
loading="lazy") - Cache agressif : les pages de listing changent souvent, mais les pages d’annonces individuelles peuvent être mises en cache 1h
- SMS de notification : utilisez l’API Orange SMS pour notifier les vendeurs quand quelqu’un contacte via le formulaire
- Paiement mobile : intégrez Wave et Orange Money pour les annonces premium (via PayDunya ou PayTech)
8. SEO pour les petites annonces
// Schema.org pour chaque annonce
add_action('wp_head', function() {
if (is_singular('annonce')) {
$prix = get_field('prix');
$location = wp_get_post_terms(get_the_ID(), 'localisation');
$schema = [
'@context' => 'https://schema.org',
'@type' => 'Product',
'name' => get_the_title(),
'description' => wp_strip_all_tags(get_the_excerpt()),
'image' => get_the_post_thumbnail_url(get_the_ID(), 'large'),
'offers' => [
'@type' => 'Offer',
'price' => $prix ?: 0,
'priceCurrency' => 'XOF',
'availability' => 'https://schema.org/InStock',
'areaServed' => $location ? $location[0]->name : 'Dakar, Sénégal',
],
];
echo '<script type="application/ld+json">' .
json_encode($schema, JSON_UNESCAPED_UNICODE) . '</script>';
}
});
Avec cette architecture, vous avez un site de petites annonces complet, monétisable et adapté au marché sénégalais. Le front-end est mobile-first, les paiements passent par le mobile money, et WhatsApp sert de canal de communication principal entre acheteurs et vendeurs.