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>