Address Verification

Compare user-entered address data against Google's authoritative address information to detect typos, abbreviations, and missing components.

Live Example

Enter Your Address

Verification Results

Enter your address and click "Verify Address" to see results

How It Works

1

Manual Entry

User types their address into individual fields (street, city, postcode, etc.)

2

Verification Search

Click "Verify Address" to search and select their address using Google Places

3

Compare & Correct

System highlights discrepancies and suggests corrections with one-click acceptance

Verification Features

Typo Detection

Fuzzy matching identifies spelling mistakes

Abbreviation Expansion

Recognises "St" vs "Street", "Rd" vs "Road"

Missing Component Detection

Identifies fields left blank that should be filled

Postcode Validation

Verifies UK postcode format and accuracy

One-Click Corrections

Accept individual suggestions or all at once

Verification Score

Visual percentage showing address accuracy

Common Use Cases

🛒 E-commerce Checkout

Prevent delivery failures by ensuring customers enter accurate addresses before order completion

📋 Account Registration

Maintain clean user databases by verifying addresses during sign-up

🏥 Insurance Forms

Critical for policy accuracy where incorrect addresses can cause claim issues

🔒 Fraud Prevention

Detect suspicious address patterns or non-existent addresses during verification

Implementation Code

Here's the complete implementation using vanilla JavaScript:

<script>
import { PlacesAutocomplete } from 'places-autocomplete-js';

document.addEventListener('DOMContentLoaded', () => {
  let autocomplete;
  let googleAddress = null;
  let comparisons = [];
  let verificationScore = 0;

  // Manual address form data
  const manualAddress = {
    streetNumber: '',
    streetName: '',
    flat: '',
    city: '',
    county: '',
    postcode: '',
    country: 'United Kingdom'
  };

  // Normalise text for comparison
  function normalise(text) {
    return text.toLowerCase()
      .replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, '')
      .trim();
  }

  // Calculate Levenshtein distance for fuzzy matching
  function levenshteinDistance(str1, str2) {
    const matrix = [];
    
    for (let i = 0; i <= str2.length; i++) {
      matrix[i] = [i];
    }
    
    for (let j = 0; j <= str1.length; j++) {
      matrix[0][j] = j;
    }
    
    for (let i = 1; i <= str2.length; i++) {
      for (let j = 1; j <= str1.length; j++) {
        if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
          matrix[i][j] = matrix[i - 1][j - 1];
        } else {
          matrix[i][j] = Math.min(
            matrix[i - 1][j - 1] + 1,
            matrix[i][j - 1] + 1,
            matrix[i - 1][j] + 1
          );
        }
      }
    }
    
    return matrix[str2.length][str1.length];
  }

  // Expand common abbreviations
  function expandAbbreviations(text) {
    const abbreviations = {
      'st': 'street', 'rd': 'road', 'ave': 'avenue',
      'dr': 'drive', 'ln': 'lane', 'ct': 'court',
      'pl': 'place', 'blvd': 'boulevard', 'pk': 'park',
      'sq': 'square', 'ter': 'terrace', 'wy': 'way'
    };
    
    let expanded = text.toLowerCase();
    Object.keys(abbreviations).forEach(abbr => {
      const regex = new RegExp(`\\b${abbr}\\b`, 'gi');
      expanded = expanded.replace(regex, abbreviations[abbr]);
    });
    
    return expanded;
  }

  // Compare two address values
  function compareValues(userValue, googleValue) {
    if (!userValue && !googleValue) return 'match';
    if (!userValue || !googleValue) return 'mismatch';
    
    const normUser = normalise(userValue);
    const normGoogle = normalise(googleValue);
    
    // Exact match
    if (normUser === normGoogle) return 'match';
    
    // Check with abbreviations expanded
    const expandedUser = expandAbbreviations(normUser);
    const expandedGoogle = expandAbbreviations(normGoogle);
    
    if (expandedUser === expandedGoogle) return 'partial';
    
    // Fuzzy match for typos (80% similarity threshold)
    const distance = levenshteinDistance(normUser, normGoogle);
    const maxLength = Math.max(normUser.length, normGoogle.length);
    const similarity = 1 - (distance / maxLength);
    
    if (similarity > 0.8) return 'partial';
    
    return 'mismatch';
  }

  // Parse Google address components
  function parseGoogleAddress(addressComponents) {
    const mapped = addressComponents.reduce((acc, component) => {
      const type = component.types[0];
      acc[type] = component.longText;
      return acc;
    }, {});
    
    return {
      streetNumber: mapped.street_number || '',
      streetName: mapped.route || '',
      flat: mapped.subpremise || '',
      city: mapped.postal_town || mapped.locality || '',
      county: mapped.administrative_area_level_2 || 
              mapped.administrative_area_level_1 || '',
      postcode: mapped.postal_code || '',
      country: mapped.country || ''
    };
  }

  // Perform verification
  function performVerification() {
    if (!googleAddress) return;
    
    const parsed = parseGoogleAddress(googleAddress.addressComponents);
    
    const fields = [
      { field: 'streetNumber', label: 'Street Number' },
      { field: 'streetName', label: 'Street Name' },
      { field: 'flat', label: 'Flat/Unit' },
      { field: 'city', label: 'City' },
      { field: 'county', label: 'County' },
      { field: 'postcode', label: 'Postcode' },
      { field: 'country', label: 'Country' }
    ];
    
    comparisons = [];
    let matches = 0;
    
    fields.forEach(({ field, label }) => {
      const userValue = manualAddress[field] || '';
      const googleValue = parsed[field] || '';
      
      let status;
      let suggestion;
      
      if (!userValue && googleValue) {
        status = 'missing';
        suggestion = `Add: ${googleValue}`;
      } else if (userValue && !googleValue) {
        status = 'match';
      } else {
        status = compareValues(userValue, googleValue);
        if (status === 'partial' || status === 'mismatch') {
          suggestion = googleValue;
        }
      }
      
      if (status === 'match') matches++;
      
      comparisons.push({
        field,
        label,
        userValue,
        googleValue,
        status,
        suggestion
      });
    });
    
    // Calculate verification score
    const totalFields = fields.filter(f => 
      manualAddress[f.field] || parsed[f.field]
    ).length;
    verificationScore = totalFields > 0 
      ? Math.round((matches / totalFields) * 100) 
      : 0;
    
    renderResults();
  }

  // Render verification results
  function renderResults() {
    const container = document.getElementById('results');
    
    if (!googleAddress) {
      container.innerHTML = `
        <div class="empty-state">
          <p>Enter your address and click "Verify Address" to see results</p>
        </div>
      `;
      return;
    }
    
    const scoreColor = verificationScore >= 80 ? '#10b981' : 
                       verificationScore >= 50 ? '#f59e0b' : '#ef4444';
    
    let html = `
      <div class="verification-score">
        <div class="score-header">
          <span>Verification Score</span>
          <span class="score-value">${verificationScore}%</span>
        </div>
        <div class="score-bar">
          <div class="score-fill" style="width: ${verificationScore}%; background: ${scoreColor}"></div>
        </div>
      </div>
    `;
    
    if (verificationScore < 100) {
      html += `
        <div class="alert alert-warning">
          ⚠️ Some discrepancies detected. Review suggestions below.
        </div>
        <button onclick="acceptAllSuggestions()" class="btn btn-success">
          ✓ Accept All Google Suggestions
        </button>
      `;
    } else {
      html += `
        <div class="alert alert-success">
          ✓ Address verified successfully!
        </div>
      `;
    }
    
    html += '<div class="comparisons">';
    
    comparisons.forEach(comp => {
      if (!comp.userValue && !comp.googleValue) return;
      
      const statusIcons = {
        match: '✓',
        partial: '⚠',
        mismatch: '✗',
        missing: 'ℹ'
      };
      
      const statusColors = {
        match: '#10b981',
        partial: '#f59e0b',
        mismatch: '#ef4444',
        missing: '#3b82f6'
      };
      
      const statusLabels = {
        match: 'Verified',
        partial: 'Typo/Abbreviation',
        mismatch: 'Mismatch',
        missing: 'Missing'
      };
      
      html += `
        <div class="comparison-item">
          <div class="comparison-header">
            <span class="field-label">${comp.label}</span>
            <span class="status-badge" style="background: ${statusColors[comp.status]}20; color: ${statusColors[comp.status]}">
              ${statusIcons[comp.status]} ${statusLabels[comp.status]}
            </span>
          </div>
          
          <div class="comparison-values">
            <div class="user-value">
              <span class="value-label">Your Entry</span>
              <span class="value-text">${comp.userValue || '—'}</span>
            </div>
            
            ${comp.status !== 'match' && comp.googleValue ? `
              <div class="google-suggestion">
                <span class="value-label">Google Suggests</span>
                <span class="value-text">${comp.googleValue}</span>
                <button 
                  onclick="acceptSuggestion('${comp.field}', '${comp.googleValue}')" 
                  class="btn-accept"
                >
                  Accept
                </button>
              </div>
            ` : ''}
          </div>
        </div>
      `;
    });
    
    html += '</div>';
    container.innerHTML = html;
  }

  // Accept all suggestions
  window.acceptAllSuggestions = function() {
    comparisons.forEach(comp => {
      if (comp.googleValue) {
        manualAddress[comp.field] = comp.googleValue;
        document.getElementById(comp.field).value = comp.googleValue;
      }
    });
    performVerification();
  };

  // Accept single suggestion
  window.acceptSuggestion = function(field, value) {
    manualAddress[field] = value;
    document.getElementById(field).value = value;
    performVerification();
  };

  // Open verification modal
  function openVerificationModal() {
    const hasData = Object.values(manualAddress).some(v => v.trim() !== '');
    if (!hasData) {
      alert('Please enter some address details first');
      return;
    }
    
    document.getElementById('modal').classList.remove('hidden');
    setTimeout(() => initAutocomplete(), 100);
  }

  // Close modal
  function closeModal() {
    document.getElementById('modal').classList.add('hidden');
    if (autocomplete) {
      autocomplete.destroy();
    }
  }

  // Initialise autocomplete
  function initAutocomplete() {
    const container = document.getElementById('verification-autocomplete');
    if (!container) return;
    
    if (autocomplete) {
      autocomplete.destroy();
    }
    
    autocomplete = new PlacesAutocomplete({
      containerId: 'verification-autocomplete',
      googleMapsApiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
      onResponse: (placeDetails) => {
        googleAddress = placeDetails;
        performVerification();
        
        const alert = document.getElementById('modal-alert');
        alert.classList.remove('hidden');
        alert.innerHTML = '✓ Address selected. Check results on the form.';
      },
      onError: (error) => {
        console.error('Autocomplete Error:', error.message || error);
      },
      requestParams: {
        region: 'GB',
        includedRegionCodes: ['GB']
      },
      fetchFields: ['formattedAddress', 'addressComponents'],
      options: {
        placeholder: 'Search for your address...',
        clear_input: false
      }
    });
  }

  // Bind form inputs
  ['streetNumber', 'streetName', 'flat', 'city', 'county', 'postcode'].forEach(field => {
    const input = document.getElementById(field);
    input.addEventListener('input', (e) => {
      manualAddress[field] = e.target.value;
    });
  });

  // Event listeners
  document.getElementById('verify-btn').addEventListener('click', openVerificationModal);
  document.getElementById('close-modal').addEventListener('click', closeModal);
  document.getElementById('reset-btn').addEventListener('click', () => {
    Object.keys(manualAddress).forEach(key => {
      manualAddress[key] = key === 'country' ? 'United Kingdom' : '';
      const input = document.getElementById(key);
      if (input) input.value = manualAddress[key];
    });
    googleAddress = null;
    comparisons = [];
    verificationScore = 0;
    renderResults();
  });

  // Initial render
  renderResults();
});
</script>

<!-- Manual Entry Form -->
<div class="form-container">
  <h3>Enter Your Address</h3>
  
  <div class="form-row">
    <div class="form-group">
      <label>Street Number</label>
      <input type="text" id="streetNumber" placeholder="10">
    </div>
    <div class="form-group">
      <label>Flat/Unit</label>
      <input type="text" id="flat" placeholder="Flat 2A">
    </div>
  </div>
  
  <div class="form-group">
    <label>Street Name</label>
    <input type="text" id="streetName" placeholder="Downing Street">
  </div>
  
  <div class="form-group">
    <label>City</label>
    <input type="text" id="city" placeholder="Westminster">
  </div>
  
  <div class="form-group">
    <label>County</label>
    <input type="text" id="county" placeholder="Greater London">
  </div>
  
  <div class="form-row">
    <div class="form-group">
      <label>Postcode</label>
      <input type="text" id="postcode" placeholder="SW1A 2AA">
    </div>
    <div class="form-group">
      <label>Country</label>
      <input type="text" id="country" value="United Kingdom" disabled>
    </div>
  </div>
  
  <div class="form-actions">
    <button id="verify-btn" class="btn btn-primary">📍 Verify Address</button>
    <button id="reset-btn" class="btn btn-secondary">Reset</button>
  </div>
</div>

<!-- Results Container -->
<div id="results" class="results-container"></div>

<!-- Verification Modal -->
<div id="modal" class="modal hidden">
  <div class="modal-content">
    <h3>Verify Your Address</h3>
    <p class="modal-description">
      Search for your address using Google Places to verify your entry
    </p>
    
    <div id="verification-autocomplete"></div>
    
    <div id="modal-alert" class="alert alert-success hidden"></div>
    
    <div class="modal-footer">
      <button id="close-modal" class="btn btn-secondary">Close</button>
    </div>
  </div>
</div>

<style>
  .form-container {
    padding: 1.5rem;
    background: white;
    border-radius: 0.5rem;
    border: 1px solid #e5e7eb;
    margin-bottom: 1.5rem;
  }

  .form-container h3 {
    margin-bottom: 1rem;
    font-size: 1.25rem;
    font-weight: 600;
  }

  .form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
  }

  .form-group {
    margin-bottom: 1rem;
  }

  .form-group label {
    display: block;
    font-weight: 500;
    margin-bottom: 0.5rem;
    font-size: 0.875rem;
  }

  .form-group input {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #d1d5db;
    border-radius: 0.375rem;
    font-size: 0.875rem;
  }

  .form-group input:disabled {
    background: #f3f4f6;
    cursor: not-allowed;
  }

  .form-actions {
    display: flex;
    gap: 0.75rem;
    margin-top: 1rem;
  }

  .btn {
    padding: 0.5rem 1rem;
    border-radius: 0.375rem;
    border: none;
    cursor: pointer;
    font-weight: 500;
    font-size: 0.875rem;
  }

  .btn-primary {
    background: #3b82f6;
    color: white;
    flex: 1;
  }

  .btn-primary:hover {
    background: #2563eb;
  }

  .btn-secondary {
    background: #f3f4f6;
    color: #374151;
  }

  .btn-secondary:hover {
    background: #e5e7eb;
  }

  .btn-success {
    background: #10b981;
    color: white;
    width: 100%;
    margin-bottom: 1rem;
  }

  .btn-success:hover {
    background: #059669;
  }

  .results-container {
    padding: 1.5rem;
    background: white;
    border-radius: 0.5rem;
    border: 1px solid #e5e7eb;
  }

  .verification-score {
    margin-bottom: 1.5rem;
  }

  .score-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 0.5rem;
    font-size: 0.875rem;
  }

  .score-value {
    font-size: 1.5rem;
    font-weight: 700;
  }

  .score-bar {
    width: 100%;
    height: 0.75rem;
    background: #e5e7eb;
    border-radius: 9999px;
    overflow: hidden;
  }

  .score-fill {
    height: 100%;
    transition: width 0.5s ease;
    border-radius: 9999px;
  }

  .alert {
    padding: 0.75rem;
    border-radius: 0.375rem;
    margin-bottom: 1rem;
    font-size: 0.875rem;
  }

  .alert-success {
    background: #d1fae5;
    color: #065f46;
  }

  .alert-warning {
    background: #fef3c7;
    color: #92400e;
  }

  .comparisons {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
  }

  .comparison-item {
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    padding: 0.75rem;
  }

  .comparison-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.75rem;
  }

  .field-label {
    font-weight: 500;
    font-size: 0.875rem;
  }

  .status-badge {
    padding: 0.25rem 0.5rem;
    border-radius: 0.25rem;
    font-size: 0.75rem;
    font-weight: 500;
  }

  .comparison-values {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .user-value,
  .google-suggestion {
    padding: 0.5rem;
    border-radius: 0.375rem;
  }

  .user-value {
    background: #f9fafb;
  }

  .google-suggestion {
    background: #eff6ff;
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }

  .value-label {
    display: block;
    font-size: 0.75rem;
    color: #6b7280;
    margin-bottom: 0.25rem;
  }

  .value-text {
    font-weight: 500;
    font-size: 0.875rem;
    flex: 1;
  }

  .btn-accept {
    padding: 0.25rem 0.75rem;
    background: #3b82f6;
    color: white;
    border: none;
    border-radius: 0.25rem;
    cursor: pointer;
    font-size: 0.75rem;
  }

  .btn-accept:hover {
    background: #2563eb;
  }

  .empty-state {
    text-align: center;
    padding: 3rem;
    color: #6b7280;
  }

  .modal {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
  }

  .modal.hidden {
    display: none;
  }

  .modal-content {
    background: white;
    padding: 2rem;
    border-radius: 0.5rem;
    max-width: 500px;
    width: 90%;
  }

  .modal-content h3 {
    margin-bottom: 0.5rem;
    font-size: 1.25rem;
    font-weight: 600;
  }

  .modal-description {
    color: #6b7280;
    font-size: 0.875rem;
    margin-bottom: 1rem;
  }

  #verification-autocomplete {
    margin-bottom: 1rem;
  }

  .modal-footer {
    display: flex;
    justify-content: flex-end;
  }
</style>

Related Examples