Integrations

Vanilla JavaScript Integration

Complete guide to integrating FeedbackKit with plain JavaScript applications using our CDN, direct API calls, and lightweight SDK.

CDN Ready

Include with a simple script tag - no build process needed

Simple API

Clean JavaScript API with promise-based methods

Universal

Works in any browser environment and static sites

Lightweight

Minimal bundle size with tree-shaking support

No Dependencies

Self-contained with no external dependencies

Configurable

Flexible configuration and customization options

Quick Start

Get Started in 30 Seconds

Add Promo to your site with just a few lines of code

<!-- Include FeedbackKit SDK -->
<script src="https://cdn.feedbackkit.io/js/v1/feedbackkit.min.js"></script>

<script>
  // Initialize FeedbackKit
  const feedbackkit = new FeedbackKit({
    apiKey: 'fk_live_your_api_key_here'
  });

  // Submit feedback
  feedbackkit.feedback.submit({
    type: 'bug',
    priority: 'high',
    title: 'Login button not working',
    description: 'Users cannot log in on mobile devices'
  }).then(response => {
    console.log('Feedback submitted:', response);
  });
</script>

Installation & Setup

CDN Installation

Include via script tag for instant setup

<!-- Production CDN (recommended) -->
<script src="https://cdn.promo.dev/js/v1/promo.min.js"></script>

<!-- Development CDN (with source maps) -->
<script src="https://cdn.promo.dev/js/v1/promo.js"></script>

<!-- Specific version (for production stability) -->
<script src="https://cdn.promo.dev/js/v1.2.0/promo.min.js"></script>

<!-- Initialize after DOM is loaded -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    window.promo = new Promo({
      apiKey: 'pk_live_your_api_key_here',
      debug: false // Set to true for development
    });
  });
</script>

Waitlist Integration

Basic Waitlist Operations

Add users and get waitlist statistics

// Add someone to waitlist
promo.waitlist.add({
  email: 'user@example.com',
  name: 'John Doe',
  metadata: {
    source: 'landing-page',
    utm_campaign: 'summer-launch'
  }
}).then(response => {
  console.log('Added to waitlist:', response);
  // { 
  //   id: 'wl_123',
  //   position: 42,
  //   referralCode: 'JOHN123',
  //   isNew: true 
  // }
}).catch(error => {
  console.error('Error:', error);
});

// Get waitlist position
promo.waitlist.getPosition('user@example.com')
  .then(data => {
    console.log('Position:', data.position);
    console.log('Total ahead:', data.ahead);
  });

// Get waitlist stats
promo.waitlist.getStats()
  .then(stats => {
    console.log('Total signups:', stats.total);
    console.log('This week:', stats.thisWeek);
  });

Testimonials Integration

Collecting Testimonials

Create forms and collect user feedback

// Submit a new testimonial
promo.testimonials.submit({
  name: 'Sarah Johnson',
  email: 'sarah@example.com',
  content: 'This product has completely transformed our workflow!',
  rating: 5,
  metadata: {
    company: 'Tech Startup Inc',
    role: 'CTO'
  }
}).then(response => {
  console.log('Testimonial submitted:', response);
  // { id: 'test_123', status: 'pending', moderationRequired: true }
});

// Submit with photo
function submitTestimonialWithPhoto(formData) {
  const file = formData.get('photo');
  
  // First upload the photo
  promo.media.upload(file)
    .then(uploadResponse => {
      return promo.testimonials.submit({
        name: formData.get('name'),
        email: formData.get('email'),
        content: formData.get('content'),
        rating: parseInt(formData.get('rating')),
        photoUrl: uploadResponse.url,
        metadata: {
          photoId: uploadResponse.id,
          uploadedAt: new Date().toISOString()
        }
      });
    })
    .then(response => {
      console.log('Testimonial with photo submitted:', response);
    });
}

// Create testimonial collection form
function createTestimonialForm() {
  return `
    <form id="testimonial-form">
      <div>
        <label>Your Name</label>
        <input type="text" name="name" required>
      </div>
      
      <div>
        <label>Email</label>
        <input type="email" name="email" required>
      </div>
      
      <div>
        <label>Your Experience</label>
        <textarea name="content" rows="4" required></textarea>
      </div>
      
      <div>
        <label>Rating</label>
        <select name="rating" required>
          <option value="5">⭐⭐⭐⭐⭐</option>
          <option value="4">⭐⭐⭐⭐</option>
          <option value="3">⭐⭐⭐</option>
          <option value="2">⭐⭐</option>
          <option value="1">⭐</option>
        </select>
      </div>
      
      <div>
        <label>Photo (optional)</label>
        <input type="file" name="photo" accept="image/*">
      </div>
      
      <button type="submit">Submit Testimonial</button>
    </form>
  `;
}

Changelog Integration

Display Changelog

Show product updates and changes

// Get recent changelog entries
promo.changelog.getEntries({
  limit: 10,
  category: 'feature', // feature, bugfix, improvement
  status: 'published'
}).then(entries => {
  renderChangelog(entries);
});

// Render changelog
function renderChangelog(entries) {
  const container = document.getElementById('changelog-container');
  
  container.innerHTML = `
    <div class="changelog">
      ${entries.map(entry => `
        <article class="changelog-entry">
          <header>
            <div class="changelog-meta">
              <span class="category category-${entry.category}">${entry.category}</span>
              <time>${new Date(entry.publishedAt).toLocaleDateString()}</time>
            </div>
            <h3>${entry.title}</h3>
          </header>
          
          <div class="changelog-content">
            ${entry.content}
          </div>
          
          ${entry.images?.length ? `
            <div class="changelog-images">
              ${entry.images.map(img => `
                <img src="${img.url}" alt="${img.alt}" loading="lazy">
              `).join('')}
            </div>
          ` : ''}
          
          ${entry.metadata?.version ? `
            <div class="version-badge">v${entry.metadata.version}</div>
          ` : ''}
        </article>
      `).join('')}
    </div>
  `;
}

// Subscribe to changelog updates
promo.changelog.subscribe('user@example.com', {
  categories: ['feature', 'improvement'],
  frequency: 'weekly' // immediate, daily, weekly
}).then(response => {
  console.log('Subscribed to changelog:', response);
});

// Real-time updates
promo.changelog.onNewEntry((entry) => {
  // Show notification for new entries
  showNotification(`New update: ${entry.title}`);
  
  // Refresh changelog display
  refreshChangelog();
});

Best Practices

Performance Optimization

Best practices for optimal performance

// Load script asynchronously
const script = document.createElement('script');
script.src = 'https://cdn.promo.dev/js/v1/promo.min.js';
script.async = true;
script.onload = function() {
  initializePromo();
};
document.head.appendChild(script);

// Initialize with caching
const promo = new Promo({
  apiKey: 'pk_live_your_api_key',
  cache: {
    enabled: true,
    ttl: 300000, // 5 minutes
    storage: 'localStorage' // localStorage, sessionStorage, memory
  },
  
  // Request batching
  batching: {
    enabled: true,
    maxBatchSize: 10,
    flushInterval: 1000 // 1 second
  }
});

// Debounce form submissions
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

const debouncedSubmit = debounce(async (formData) => {
  await promo.waitlist.add(formData);
}, 300);

// Preload critical data
document.addEventListener('DOMContentLoaded', () => {
  // Preload waitlist stats for faster display
  promo.waitlist.getStats().then(stats => {
    localStorage.setItem('waitlist-stats', JSON.stringify({
      data: stats,
      timestamp: Date.now()
    }));
  });
});

// Lazy load testimonials
function lazyLoadTestimonials() {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        loadTestimonials();
        observer.unobserve(entry.target);
      }
    });
  });
  
  observer.observe(document.getElementById('testimonials-section'));
}

// Use RequestAnimationFrame for smooth animations
function animatePosition(newPosition) {
  const element = document.getElementById('position-counter');
  const startPosition = parseInt(element.textContent);
  const endPosition = newPosition;
  const duration = 1000;
  const startTime = performance.now();
  
  function updatePosition(currentTime) {
    const elapsed = currentTime - startTime;
    const progress = Math.min(elapsed / duration, 1);
    
    const currentPosition = Math.floor(
      startPosition + (endPosition - startPosition) * progress
    );
    
    element.textContent = currentPosition;
    
    if (progress < 1) {
      requestAnimationFrame(updatePosition);
    }
  }
  
  requestAnimationFrame(updatePosition);
}

Complete Example

Full Implementation

A complete example with all features integrated

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Product - Join the Waitlist</title>
  <style>
    /* Your styles here */
    .hidden { display: none; }
    .error { color: #ef4444; }
    .success { color: #10b981; }
  </style>
</head>
<body>
  <div id="app">
    <header>
      <h1>Revolutionary Product Coming Soon</h1>
      <p>Join thousands of others waiting for launch</p>
    </header>
    
    <main>
      <!-- Waitlist Form -->
      <form id="waitlist-form">
        <input type="email" name="email" placeholder="your@email.com" required>
        <input type="text" name="name" placeholder="Your Name" required>
        <button type="submit">Join Waitlist</button>
      </form>
      
      <!-- Success State -->
      <div id="success-state" class="hidden">
        <h2>🎉 You're In!</h2>
        <p id="position-text"></p>
        <div id="referral-section">
          <p>Share with friends to move up in line:</p>
          <input type="text" id="referral-link" readonly>
          <button onclick="copyReferralLink()">Copy Link</button>
        </div>
      </div>
      
      <!-- Testimonials -->
      <section id="testimonials">
        <h2>What Others Are Saying</h2>
        <div id="testimonials-container"></div>
      </section>
      
      <!-- Changelog -->
      <section id="changelog">
        <h2>Development Updates</h2>
        <div id="changelog-container"></div>
      </section>
    </main>
  </div>

  <!-- Promo SDK -->
  <script src="https://cdn.promo.dev/js/v1/promo.min.js"></script>
  <script>
    // Initialize Promo
    const promo = new Promo({
      apiKey: 'pk_live_your_api_key_here',
      debug: window.location.hostname === 'localhost'
    });

    // Handle form submission
    document.getElementById('waitlist-form').addEventListener('submit', async (e) => {
      e.preventDefault();
      
      const formData = new FormData(e.target);
      const referralCode = new URLSearchParams(window.location.search).get('ref');
      
      try {
        const response = await promo.waitlist.add({
          email: formData.get('email'),
          name: formData.get('name'),
          referredBy: referralCode
        });
        
        showSuccess(response);
      } catch (error) {
        showError(error.message);
      }
    });

    function showSuccess(response) {
      document.getElementById('waitlist-form').classList.add('hidden');
      document.getElementById('success-state').classList.remove('hidden');
      document.getElementById('position-text').textContent = 
        `You're #${response.position} in line!`;
      document.getElementById('referral-link').value = 
        `${window.location.origin}?ref=${response.referralCode}`;
    }

    function showError(message) {
      alert('Error: ' + message);
    }

    function copyReferralLink() {
      const input = document.getElementById('referral-link');
      input.select();
      document.execCommand('copy');
      alert('Link copied!');
    }

    // Load testimonials
    promo.testimonials.getApproved({ limit: 3 }).then(testimonials => {
      const container = document.getElementById('testimonials-container');
      container.innerHTML = testimonials.map(t => `
        <blockquote>
          <p>"${t.content}"</p>
          <cite>— ${t.name}</cite>
        </blockquote>
      `).join('');
    });

    // Load changelog
    promo.changelog.getEntries({ limit: 5 }).then(entries => {
      const container = document.getElementById('changelog-container');
      container.innerHTML = entries.map(entry => `
        <article>
          <h3>${entry.title}</h3>
          <p>${entry.content}</p>
          <time>${new Date(entry.publishedAt).toLocaleDateString()}</time>
        </article>
      `).join('');
    });
  </script>
</body>
</html>