Supabase allows large datasets and can perform very fast lookup via their built-in API tools.

Step 1: Set up your Supabase project

  • Sign up or log in at supabase.com
  • Click “New Project” and fill in your project details
  • Select your region and set a secure password
  • Wait for your database to be provisioned (usually takes 1-2 minutes)

Step 2: Upload your CSV

  • Navigate to the “Table Editor” in the left sidebar
  • Click “Create a new table” or select an existing table (Recommended to use lowercase and use an underscore to separate words e.g. table_name or column_name)
  • For new CSV upload: Click “Import data from CSV” (Recommended to have a unique identifier column like row_id)
  • Upload your CSV file, Review the column types and adjust if needed
  • Confirm the import

Step 3: Find the API details and request URL

  • Go to “Project Settings” (gear icon in the bottom left)
  • Click on “Data API” in the settings sidebar
  • Copy your project URL from the “Project URL” field
  • Find your API keys in the “API Keys” sidebar under Project Settings
  • Use the “anon” (public) key for client-side requests
  • Use the “service_role” key for server-side requests (keep this secure)

Sample Request URL:
https://<your_project_id>.supabase.co/rest/v1/<table_name>?<table_column>=eq.{zip}

  • <your_project_id> – This is your Supabase Project ID
  • <table_name> – This is the name of your table
  • <table_column> – Your table column
  • {zip} – Your Leadshook custom field

Headers:

apikey: SUPABASE_CLIENT_ANON_PUBLIC_KEY
Authorization: Bearer SUPABASE_SERVICE_ROLE_KEY
Content-Type: application/x-www-form-urlencoded

Short video of setting it up:

How to setup autocomplete lookup to your Supabase database

Step1: Follow the Steps 1 and 2 above.

Step2: Create your custom page node and paste this code.

In this example, I have the table name as “my_database” and the column name as “energy_retailers”

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Energy Retailer Lookup</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2.39.7"></script>
    <style>
        /* --- Added/Updated Styles for Autocomplete --- */
        .autocomplete-container {
            position: relative;
            width: 100%;
        }

        .autocomplete-list {
            position: absolute;
            top: 100%;
            left: 0;
            right: 0;
            border: 1px solid #dfe1e5;
            border-radius: 0 0 8px 8px; /* Rounded bottom corners */
            border-top: none; /* Remove top border to connect with input */
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);
            background-color: #fff;
            z-index: 1000; /* Ensure it's above other elements */
            overflow: hidden; /* Contain rounded corners */
            margin-top: -1px; /* Pull up to touch the input */
        }

        .autocomplete-item {
            padding: 10px 12px;
            cursor: pointer;
            font-size: 16px; /* Standard text size */
            line-height: 24px; /* Better readability */
            border-bottom: 1px solid #eee;
            transition: background-color 0.2s ease;
        }

         /* Remove border from the last item */
        .autocomplete-item:last-child {
            border-bottom: none;
        }

        .autocomplete-item:hover,
        .autocomplete-item.highlighted {
            background-color: #f8f9fa; /* Light grey background */
        }

        /* --- Loading Indicator Styles --- */
        .loading-indicator {
            position: absolute;
            right: 12px; /* Position inside the input */
            top: 50%;
            transform: translateY(-50%); /* Center vertically */
            display: none; /* Hidden by default */
        }

        .spinner {
            width: 18px;
            height: 18px;
            border: 2px solid #f3f3f3; /* Light grey background */
            border-top: 2px solid #4285f4; /* Google blue */
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        /* --- End of Added/Updated Styles --- */
    </style>
</head>
<body class="bg-gradient-to-br from-blue-50 to-blue-100">
    <div class="container-center">
        <div class="bg-white shadow-2xl rounded-2xl p-8 w-full max-w-md space-y-6 transform transition-all duration-300 hover:scale-105 relative mx-auto">
            <div class="text-center">
                <h1 class="text-3xl font-bold text-blue-600 mb-2">Energy Retailer Lookup</h1>
                <p class="text-gray-500 text-sm">Find your energy retailer</p>
            </div>
            <!-- Wrap input and list in a container for positioning -->
            <div class="autocomplete-container">
                <input
                    type="text"
                    id="retailerInput"
                    placeholder="Type to search energy retailers..."
                    class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition duration-300 ease-in-out"
                >
                <!-- Loading indicator inside the container -->
                <div class="loading-indicator" id="loadingIndicator">
                    <div class="spinner"></div>
                </div>
                 <!-- Autocomplete list inside the container -->
                <div id="retailerList" class="autocomplete-list hidden"></div>
            </div>
            <button id="forwardButton" type="submit" onclick="logSelection()" class="w-full bg-blue-500 text-white font-bold py-3 rounded-lg hover:bg-blue-600 transition duration-300 hidden">Continue</button>
        </div>
    </div>
    <script>
        // Initialize Supabase
        const supabaseUrl = 'YOUR_SUPABASE_URL';
        const supabaseKey = 'YOUR_SUPABASE_API_KEY_ANON_PUBLIC';
        const supabase = window.supabase.createClient(supabaseUrl, supabaseKey);

        // Debug: Check if Supabase is initialized
        console.log('Supabase initialized:', supabase ? 'Yes' : 'No');
        console.log('Supabase URL:', supabaseUrl);
        console.log('Supabase Key set:', supabaseKey ? 'Yes' : 'No');

        const retailerInput = document.getElementById('retailerInput');
        const retailerList = document.getElementById('retailerList');
        const forwardButton = document.getElementById('forwardButton');
        // Reference the loading indicator correctly
        const loadingIndicator = document.getElementById('loadingIndicator');

        // For debouncing
        let debounceTimeout;
        const debounceDelay = 300; // milliseconds

        // For keyboard navigation
        let highlightedIndex = -1;

        // Track if the selected retailer is valid (from the Supabase database)
        let isValidRetailer = false;

        // Add event listeners
        retailerInput.addEventListener('input', handleInput);
        retailerInput.addEventListener('keydown', handleKeyDown);
        retailerInput.addEventListener('focus', () => {
            if (retailerInput.value.trim().length >= 2) {
                filterRetailers();
            }
        });

        // Hide dropdown when clicking outside
        document.addEventListener('click', (event) => {
            // Check if click is outside the autocomplete container
            if (!event.target.closest('.autocomplete-container')) {
                 retailerList.classList.add('hidden');
            }
        });

        function handleInput() {
            // Clear previous debounce timeout
            clearTimeout(debounceTimeout);
            // Set a new debounce timeout
            debounceTimeout = setTimeout(() => {
                filterRetailers();
            }, debounceDelay);
            // Reset validity when user types
            isValidRetailer = false;
            toggleForwardButton();
        }

        async function filterRetailers() {
            const query = retailerInput.value.trim();
            highlightedIndex = -1;
            if (query.length < 2) {
                retailerList.classList.add('hidden');
                return;
            }

            // Show loading indicator
            if (loadingIndicator) { // Check if element exists
                loadingIndicator.style.display = 'block';
            }
            // Hide the list while loading
            retailerList.classList.add('hidden');

            try {
                console.log('Searching for:', query);
                // Query Supabase for retailers that match the search term
                const { data, error, status } = await supabase
                    .from('YOUR_SUPABASE_TABLE')
                    .select('YOUR_SUPABASE_COLUMN')
                    .ilike('YOUR_SUPABASE_COLUMN', `%${query}%`)
                    .limit(10);

                // Debug: Log the response
                console.log('Supabase response:', { data, error, status });

                if (error) {
                    console.error('Supabase error:', error);
                    throw error;
                }

                if (!data || data.length === 0) {
                    console.log('No results found');
                    displayResults([]);
                    return;
                }

                // Transform data to match expected format
                const results = data.map(item => [item['YOUR_SUPABASE_COLUMN']]);
                console.log('Transformed results:', results);
                displayResults(results);
            } catch (error) {
                console.error('Error fetching data:', error);
                displayError(`Error: ${error.message || "Unable to fetch retailers. Please try again later."}`);
            } finally {
                // Hide loading indicator
                if (loadingIndicator) { // Check if element exists
                    loadingIndicator.style.display = 'none';
                }
            }
        }

        function displayResults(results) {
            retailerList.innerHTML = ''; // Clear previous results
            if (results.length > 0) {
                results.forEach((result, index) => {
                    const item = document.createElement('div');
                    item.classList.add('autocomplete-item');
                    item.textContent = result[0]; // The retailer name
                    item.dataset.index = index;
                    item.addEventListener('click', () => {
                        selectRetailer(result[0]);
                    });
                    retailerList.appendChild(item);
                });
                retailerList.classList.remove('hidden');
            } else {
                 // Show "No results" message if needed, or just keep it hidden
                 // For now, we'll keep it simple and just hide if no results after loading
                 retailerList.classList.add('hidden');
            }
        }

        function displayError(message) {
            retailerList.innerHTML = '';
            const item = document.createElement('div');
            item.classList.add('autocomplete-item');
            item.textContent = message;
            item.style.color = '#666';
            item.style.fontStyle = 'italic';
            retailerList.appendChild(item);
            retailerList.classList.remove('hidden');
        }

        function selectRetailer(retailer) {
            retailerInput.value = retailer;
            retailerList.classList.add('hidden');
            // Mark as valid retailer since it was selected from the list
            isValidRetailer = true;
            toggleForwardButton();
             // Ensure button visibility is updated
            if (isValidRetailer) {
                forwardButton.classList.remove('hidden');
            }
        }

        function toggleForwardButton() {
            // Only show the button if we have a valid retailer from the Supabase database
            if (isValidRetailer && retailerInput.value.trim().length > 0) {
                forwardButton.classList.remove('hidden');
                forwardButton.disabled = false;
            } else {
                forwardButton.classList.add('hidden');
                forwardButton.disabled = true;
            }
        }

        function logSelection() {
            console.log('Selected Retailer:', retailerInput.value);
            // Assuming DT is defined globally elsewhere
            if (typeof DT !== 'undefined' && DT.setField) {
                DT.setField('YOUR_LH_CUSTOM_FIELD', retailerInput.value);
            } else {
                 console.warn('DT object or setField method not found. Selection not logged.');
            }
        }

        function handleKeyDown(e) {
            const items = retailerList.querySelectorAll('.autocomplete-item');
            if (retailerList.classList.contains('hidden') || items.length === 0) {
                return;
            }

            // Down arrow
            if (e.key === 'ArrowDown') {
                e.preventDefault();
                highlightedIndex = (highlightedIndex + 1) % items.length;
                updateHighlightedItem(items);
            }
            // Up arrow
            else if (e.key === 'ArrowUp') {
                e.preventDefault();
                highlightedIndex = (highlightedIndex - 1 + items.length) % items.length;
                updateHighlightedItem(items);
            }
            // Enter
            else if (e.key === 'Enter' && highlightedIndex >= 0) {
                e.preventDefault();
                const retailer = items[highlightedIndex].textContent;
                selectRetailer(retailer);
            }
            // Escape
            else if (e.key === 'Escape') {
                retailerList.classList.add('hidden');
            }
        }

        function updateHighlightedItem(items) {
            // Remove previous highlight
            items.forEach(item => {
                item.classList.remove('highlighted');
            });
            // Add highlight to current item
            if (highlightedIndex >= 0 && highlightedIndex < items.length) {
                items[highlightedIndex].classList.add('highlighted');
                // Scroll the highlighted item into view if needed
                items[highlightedIndex].scrollIntoView({
                    block: 'nearest'
                });
            }
        }
    </script>
</body>
</html>

Note: Replace these instances in the code:

YOUR_SUPABASE_URL
YOUR_SUPABASE_API_KEY_ANON_PUBLIC
YOUR_SUPABASE_TABLE
YOUR_SUPABASE_COLUMN
YOUR_LH_CUSTOM_FIELD

Short Video