Autocomplétion d'adresse via l'API BAN
(Base Adresse Nationale)
- Alternative à l'API Google Places
- Simple d'installation
- Gestion avec script & attributs
- 100% gratuit
- Pas de clé API !
- Solution Made in France 🇫🇷
- Limite de 50 requêtes API / seconde / IP
Implémentation dans Webflow
Ajoutez le script en before </body>
<!-- [script by nicolastizio.co] API BAN Adresse -->
<script src="https://cdn.jsdelivr.net/npm/@nicolastizioco/scrypts@latest/src/api-ban/api-ban.min.js"></script>Où celui-ci pour le customiser
<!-- [script by nicolastizio.co] API BAN Adresse -->
<script>
"use strict";
document.addEventListener("DOMContentLoaded", function () {
const style = document.createElement("style");
style.textContent = `
[data-ban-results]:not([data-ban-results="no-css"]) {
display: none;
position: absolute;
left: 0%;
top: 100%;
z-index: 999;
overflow: auto;
width: 100%;
max-height: 15rem;
padding: 0.5rem;
border-radius: 0.5rem;
background-color: white;
box-shadow: var(--ban-box-shadow, 0 2px 5px 0 rgba(0,0,0,0.2));
}
[data-ban-item]:not([data-ban-item="no-css"]) {
padding: 0.5rem;
border-radius: 0.2rem;
transition: all 300ms ease;
cursor: pointer;
background-color: transparent;
scroll-margin: 0.5rem;
}
[data-ban-item]:not([data-ban-item="no-css"]).active,
[data-ban-item]:not([data-ban-item="no-css"]):hover {
background-color: #eee;
}
`;
document.head.appendChild(style);
const hasLogWrapper = document.querySelector('[data-ban-wrapper="log"]');
const logMessage = (shouldLog, ...args) => {
if (shouldLog) console.log(...args);
};
if (hasLogWrapper) {
console.log("API BAN 🇫🇷 | Made by nicolastizio.co");
}
const debounce = (fn, delay) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
};
const fetchSuggestions = async (query, shouldLog, maxResults = 5) => {
const apiUrl = `https://data.geopf.fr/geocodage/completion/?text=${encodeURIComponent(
query
)}&maximumResponses=${maxResults}&type=StreetAddress`;
logMessage(shouldLog, "🔎 :", query);
try {
const response = await fetch(apiUrl);
const data = await response.json();
return data.results || [];
} catch (e) {
return [];
}
};
const highlightItems = (items, currentFocus) => {
items.forEach((item, index) => {
item.classList.remove("active");
if (index === currentFocus) {
item.classList.add("active");
item.scrollIntoView({ block: "nearest", behavior: "smooth" });
}
});
};
const handleSelection = (
{ street, zipcode, city, fulltext },
inputField,
streetField,
zipcodeField,
cityField,
resultsContainer,
shouldLog
) => {
if (inputField) {
inputField.value = fulltext || `${street || ""}, ${zipcode || ""} ${city || ""}`;
logMessage(shouldLog, "📍 :", inputField.value);
}
if (streetField) {
const addressPart = fulltext ? fulltext.split(",")[0] : street;
streetField.value = addressPart || "";
logMessage(shouldLog, "🛣️ :", streetField.value);
}
if (zipcodeField) {
zipcodeField.value = zipcode || "";
logMessage(shouldLog, "📮 :", zipcodeField.value);
}
if (cityField) {
cityField.value = city || "";
logMessage(shouldLog, "🏙️ :", cityField.value);
}
resultsContainer.style.display = "none";
};
const showSuggestions = (
suggestions,
query,
resultsContainer,
handleClick,
resetFocus
) => {
const existingItem = resultsContainer.querySelector("[data-ban-item]");
const originalClass = existingItem ? existingItem.className : "";
const originalDataBanItem = existingItem ? existingItem.getAttribute("data-ban-item") : "";
resultsContainer.innerHTML = "";
resetFocus();
if (suggestions.length === 0) {
resultsContainer.style.display = "none";
return;
}
const queryRegex = new RegExp(`(${query})`, "gi");
suggestions.forEach((result) => {
const resultItem = document.createElement("div");
resultItem.setAttribute("data-ban-item", originalDataBanItem);
if (originalClass) {
resultItem.className = originalClass;
}
const highlightedLabel = result.fulltext.replace(
queryRegex,
"<strong>$1</strong>"
);
resultItem.innerHTML = highlightedLabel;
resultItem.addEventListener("click", () => handleClick(result));
resultsContainer.appendChild(resultItem);
});
resultsContainer.style.display = "block";
};
document
.querySelectorAll("[data-ban-results]")
.forEach((resultsContainer) => {
const customBoxShadow = resultsContainer.getAttribute("data-ban-results");
if (customBoxShadow) {
resultsContainer.style.setProperty("--ban-box-shadow", customBoxShadow);
}
});
document.querySelectorAll("[data-ban-wrapper]").forEach((wrapper) => {
const shouldLog = wrapper.getAttribute("data-ban-wrapper") === "log";
wrapper.style.position = "relative";
const inputField = wrapper.querySelector("[data-ban-input]");
const resultsContainer = wrapper.querySelector("[data-ban-results]");
const streetField = wrapper.querySelector("[data-ban-street]");
const cityField = wrapper.querySelector("[data-ban-city]");
const zipcodeField = wrapper.querySelector("[data-ban-zipcode]");
const mainField = inputField || streetField;
let currentFocus = -1;
if (!mainField || !resultsContainer) return;
const maxResultsAttr = resultsContainer.getAttribute(
"data-ban-results-count"
);
const maxResults =
maxResultsAttr && !isNaN(maxResultsAttr)
? Math.min(Math.max(parseInt(maxResultsAttr), 1), 15)
: 5;
logMessage(
shouldLog,
"Max résultats :",
maxResults
);
const resetFocus = () => {
currentFocus = -1;
};
const performSearch = debounce(async (query) => {
if (query.length > 2) {
const suggestions = await fetchSuggestions(
query,
shouldLog,
maxResults
);
showSuggestions(
suggestions,
query,
resultsContainer,
(result) =>
handleSelection(
result,
inputField,
streetField,
zipcodeField,
cityField,
resultsContainer,
shouldLog
),
resetFocus
);
} else {
resultsContainer.style.display = "none";
}
}, 300);
mainField.addEventListener("input", () => {
const query = mainField.value.trim();
performSearch(query);
});
mainField.addEventListener("keydown", (e) => {
const items = resultsContainer.querySelectorAll("[data-ban-item]");
if (e.key === "Escape") {
e.preventDefault();
resultsContainer.style.display = "none";
currentFocus = -1;
return;
}
if (!items.length) return;
if (e.key === "ArrowDown") {
e.preventDefault();
currentFocus = (currentFocus + 1) % items.length;
highlightItems(items, currentFocus);
} else if (e.key === "ArrowUp") {
e.preventDefault();
currentFocus = (currentFocus - 1 + items.length) % items.length;
highlightItems(items, currentFocus);
} else if (e.key === "Enter") {
e.preventDefault();
if (currentFocus > -1 && items[currentFocus]) {
items[currentFocus].click();
}
}
});
document.addEventListener("click", (e) => {
if (!wrapper.contains(e.target)) {
resultsContainer.style.display = "none";
}
});
});
});
</script>Ajouter les attributs dans le formulaire
Foire aux questions
Cette solution maison est bien plus pratique à utiliser que l'API Place de Google.
Chez Google vous êtes obligés de créer un compte développeur, créer une application, ajouter une carte bleue, générer une clé, gérer les droits d'accès et à la fin cela à un vrai coup financier. 👇
De plus, l'intégration de Place API de Google n'est pas facile à intégrer et demande de vraie compétence technique.
Enfin, cela vous éviter d'avoir à envoyer des données à Google sur votre site et votre recherche reste anonyme sur des données et serveurs Français.

Dans un sens, oui.
L'API fournie par l'État est mise à jour chaque semaine sur la base de nouvelles rues et adresses listées auprès de toutes les villes de France sur la base des "adresses BAL".
Cependant la finesse des premiers résultats dans le champ de recherche n’est pas aussi performance que se propose Google, mais vous trouvez bien votre adresse.
Google piste votre activité et connaît les adresses les plus susceptibles de vous intéresser. Ainsi, il remonte bien plus rapidement une adresse en tapant très peu de caractères, l'API de l'État quant à elle plus neutre.
L'API BAN (Base Adresse Nationale) est un service public français qui permet d'accéder à la base de données officielle des adresses en France. C'est une API gratuite et ouverte qui vous permet de suggérer des adresses pendant que l'utilisateur tape (comme sur Google Maps).
Cette base est collaborative et regroupe les données des communes, d'OpenStreetMap, et d'autres sources officielles et est mise à jour 2 fois par semaine.
L'API est complètement gratuite, sans inscription ni clé d'API nécessaire. Une limite d'usage est appliquée de 50 appels/IP/seconde.
La documentation complète est disponible sur https://adresse.data.gouv.fr/api-doc/adresse et https://geoservices.ign.fr/documentation/services/services-geoplateforme/autocompletion
Update : L'API Adresse BAN est dépréciée et intégrée dans le nouveau Service de géocodage de la Géoplateforme.
Absolument que Non !
Le script s'exécute sur la machine de l'utilisateur et va uniquement faire un call API et remontez les résultats de votre recherche.
Je ne récupère aucune donnée et l'État non plus.
Je vous invite à lire attentivement la doc ci-dessus ☝ et de voir le tutoriel vidéo ici.
Oui absolument, le script fonctionne à 99% via des attributs. Seulement 2 attributs contiennent des propriétés CSS en dur que vous pouvez désactiver en ajoutant la valeur "no-css" sur [data-ban-results] & [data-ban-input].
Aussi le code est proposé via un CDN NPM ou à copier sur votre page, donc toute modification est possible.
Oui absolument, à partir du moment où vous pouvez sur votre CMS ajouter des DIV & attributs alors vous pouvez utiliser cette solution.
Cependant, cette solution a été pensée pour une utilisation sur Webflow. Donc si vous trouvez des incompatibilités sur votre CMS ou des difficultés à l'intégrer, Je vous invite à me contacter pour vous aider.
Il n'y a pas de raison pour que le script ne marche pas d'ici quelques mois / années.
Tant que l'API n'est pas mise à jour techniquement, le script sera maintenu.
Cette solution est une initiative personnelle qui dans un 1er temps a été créée pour un client spécifique puis partagé pour tous en open-source.