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