Add Klaviyo to your Bolt.new app by including the Klaviyo JavaScript tracking snippet for automatic event capture (works in the WebContainer preview), and building API routes for server-side operations like creating campaigns and managing lists. Get your Public API Key and Private API Key from Klaviyo Account → Settings → API Keys. The tracking snippet fires analytics events directly from the browser — no API route needed for basic tracking.
Add E-commerce Email Marketing to Your Bolt.new App with Klaviyo
Klaviyo is the email marketing platform of choice for e-commerce businesses — it tracks individual customer behavior, segments audiences based on purchase history, and triggers automated email flows when specific events occur. Unlike general-purpose email tools, Klaviyo's entire design revolves around connecting marketing actions to revenue. When a user views a product, abandons a cart, or makes a purchase, Klaviyo captures that event, adds it to the customer's profile, and can trigger targeted follow-up emails automatically.
For Bolt.new apps, Klaviyo's JavaScript tracking snippet is the fastest integration path. Add the snippet to your HTML head, identify users with their email address, and start firing custom events using window.klaviyo.track(). This client-side approach works immediately in Bolt's WebContainer preview because it only makes outbound HTTP calls to Klaviyo's servers — no incoming connections required. The Public API Key (safe to expose in client-side code) is all you need for tracking.
For server-side operations — creating subscribers, managing list membership, sending transactional emails, or fetching analytics data — Klaviyo provides a comprehensive REST API (v3) that requires your Private API Key. These operations run through server-side API routes in your Bolt app, keeping the private key out of client-side code. Klaviyo's API is well-documented and follows standard REST conventions, making it straightforward to implement with the patterns Bolt generates.
Integration method
Bolt.new integrates with Klaviyo through two complementary channels: the Klaviyo JavaScript tracking snippet (added directly to the app's HTML) captures behavioral events like page views and product interactions in the WebContainer preview without any server-side code, while server-side API routes handle private API operations like creating profiles, adding subscribers to lists, and fetching campaign analytics. The tracking snippet uses only the Public API Key and works instantly in development.
Prerequisites
- A Klaviyo account (free tier available at klaviyo.com for up to 500 contacts and 500 email sends/month)
- Your Klaviyo Public API Key from Account → Settings → API Keys (safe for client-side use)
- Your Klaviyo Private API Key from the same page (server-side only — never expose in client code)
- At least one Klaviyo List created (Account → Lists & Segments → Create List) for subscriber management
- A Bolt.new project (Vite or Next.js)
Step-by-step guide
Add the Klaviyo JavaScript Tracking Snippet
Add the Klaviyo JavaScript Tracking Snippet
Klaviyo's JavaScript tracking snippet is the fastest way to start capturing behavioral data — it loads asynchronously, identifies users by email, and tracks custom events with a simple push-based API. The snippet works immediately in Bolt's WebContainer preview because it only makes outbound calls to Klaviyo's servers, not the other way around. Start by locating your Public API Key: in your Klaviyo account, click your account name in the bottom-left → Settings → API Keys. Your Public API Key is shown at the top — it starts with a 6-character alphanumeric string. This key is safe to include in client-side code and in your .env file with the VITE_ prefix for Vite projects. The tracking snippet should be added to your app's main HTML file (index.html for Vite projects, or in the layout file for Next.js). Prompt Bolt to add the snippet. Once the snippet loads, the global window._learnq array is available. To identify a user (associate their actions with an email address), call: window._learnq.push(['identify', { '$email': 'user@example.com' }]). Until a user is identified, all tracked events are associated with an anonymous profile in Klaviyo that gets merged when the email is collected later (for example, when they sign up or log in).
Add the Klaviyo JavaScript tracking snippet to my Bolt app. In index.html (or the main layout), add the following snippet in the <head> using my public key from import.meta.env.VITE_KLAVIYO_PUBLIC_KEY. Add a useKlaviyo() React hook in lib/klaviyo.ts that exports: identifyUser(email, properties?) and trackEvent(eventName, properties?) functions. These should call window._learnq.push(['identify', ...]) and window._learnq.push(['track', ...]) respectively. Add a TypeScript declaration for window._learnq so there are no type errors. Add VITE_KLAVIYO_PUBLIC_KEY to the .env file as a placeholder.
Paste this in Bolt.new chat
1<!-- index.html — add to <head> -->2<script>3 !(function () {4 if (!window.klaviyo) {5 window._learnq = window._learnq || [];6 window._learnq.push(['account', import.meta.env.VITE_KLAVIYO_PUBLIC_KEY]);7 var b = document.createElement('script');8 b.type = 'text/javascript';9 b.async = !0;10 b.src = 'https://static.klaviyo.com/onsite/js/klaviyo.js?company_id=' + import.meta.env.VITE_KLAVIYO_PUBLIC_KEY;11 var a = document.getElementsByTagName('script')[0];12 a.parentNode.insertBefore(b, a);13 }14 })();15</script>1617// lib/klaviyo.ts — typed helper18declare global {19 interface Window {20 _learnq: unknown[];21 }22}2324export function identifyUser(email: string, properties?: Record<string, unknown>) {25 window._learnq = window._learnq || [];26 window._learnq.push(['identify', { '$email': email, ...properties }]);27}2829export function trackEvent(eventName: string, properties?: Record<string, unknown>) {30 window._learnq = window._learnq || [];31 window._learnq.push(['track', eventName, properties || {}]);32}Pro tip: The Klaviyo snippet works in Bolt's WebContainer preview immediately — no deployment needed. You can see events flowing in real-time in your Klaviyo account under Activity → Live View after adding the snippet.
Expected result: The Klaviyo snippet loads in your app. Opening Klaviyo → Activity → Live View shows page view events from your Bolt preview sessions.
Build an Email Signup Form with List Subscription API
Build an Email Signup Form with List Subscription API
For collecting email addresses and adding subscribers to Klaviyo lists, you'll use Klaviyo's REST API v3 with your Private API Key. The Private API Key must never be exposed in client-side code — always call it from a server-side API route. In a Vite project, you can use a Vite server proxy to route API calls through a development server, or use Next.js API routes for clean separation. The Klaviyo v3 API uses a specific JSON:API structure for profile creation: you POST to /api/profiles/ with a data object containing type and attributes. After creating or updating the profile, subscribe it to a list with a separate call to /api/lists/{list_id}/relationships/profiles/. The list ID is visible in your Klaviyo account under Lists & Segments — click your list and find the list ID in the URL bar (a 6-character string like AbCdEf). Store the list ID in your .env file. Klaviyo's profile creation is idempotent for existing emails — if the email already exists as a profile, it updates the profile instead of creating a duplicate. The API returns a 201 status for new profiles and 200 for updates. Always handle both response codes in your API route. For form submission security, add validation for email format and implement a honeypot field to reduce bot submissions. Rate limits on Klaviyo's v3 API are generous (500 requests per second), so you won't hit them in normal Bolt app usage.
Create an email newsletter signup form in my Bolt app. The form has a single email input with validation and a Subscribe button. On submit, POST to /api/klaviyo/subscribe with { email }. Create the API route that: 1) Calls POST https://a.klaviyo.com/api/profiles/ with Authorization: Klaviyo-API-Key ${KLAVIYO_PRIVATE_KEY} and revision: 2024-02-15 header, body: { data: { type: 'profile', attributes: { email } } }. 2) Then calls POST https://a.klaviyo.com/api/lists/${KLAVIYO_LIST_ID}/relationships/profiles/ to add the profile to the list. Return success or error. Show a 'You're subscribed!' message on success. Add KLAVIYO_PRIVATE_KEY and KLAVIYO_LIST_ID to .env as placeholders.
Paste this in Bolt.new chat
1// app/api/klaviyo/subscribe/route.ts2import { NextRequest, NextResponse } from 'next/server';34const KLAVIYO_BASE = 'https://a.klaviyo.com/api';5const HEADERS = {6 'Authorization': `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,7 'revision': '2024-02-15',8 'Content-Type': 'application/json',9};1011export async function POST(request: NextRequest) {12 const { email } = await request.json();1314 if (!email || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {15 return NextResponse.json({ error: 'Invalid email address' }, { status: 400 });16 }1718 // Step 1: Create or update profile19 const profileRes = await fetch(`${KLAVIYO_BASE}/profiles/`, {20 method: 'POST',21 headers: HEADERS,22 body: JSON.stringify({23 data: {24 type: 'profile',25 attributes: { email },26 },27 }),28 });2930 const profileData = await profileRes.json();31 const profileId = profileData.data?.id;3233 if (!profileId) {34 return NextResponse.json({ error: 'Failed to create profile' }, { status: 500 });35 }3637 // Step 2: Subscribe to list38 await fetch(`${KLAVIYO_BASE}/lists/${process.env.KLAVIYO_LIST_ID}/relationships/profiles/`, {39 method: 'POST',40 headers: HEADERS,41 body: JSON.stringify({42 data: [{ type: 'profile', id: profileId }],43 }),44 });4546 return NextResponse.json({ success: true });47}Pro tip: Klaviyo's v3 API requires the 'revision' header — use '2024-02-15' or later. Requests without this header return a 400 error. Check the Klaviyo API changelog for the latest revision date.
Expected result: Submitting the signup form creates a new profile in Klaviyo and adds them to your list. You can verify in Klaviyo → Lists & Segments → your list.
Track Custom E-commerce Events
Track Custom E-commerce Events
Klaviyo's power comes from tracking specific user behaviors that trigger automated email flows — viewing a product, adding to cart, starting checkout, and completing a purchase. These events build the customer profiles that power segmentation and automation. Once you have the tracking snippet installed (Step 1), firing custom events is a one-line call anywhere in your React components. For e-commerce events, Klaviyo has defined schemas for standard events: 'Viewed Product', 'Added to Cart', 'Started Checkout', and 'Placed Order'. Using these exact event names unlocks Klaviyo's pre-built automation templates (like abandoned cart flows) without any additional configuration. Each event takes a properties object — for products, include ProductName, ProductID, Price, ImageURL, and URL at minimum. For orders, include OrderId, ItemNames, ItemCount, and Value. The properties object can contain any additional custom fields you want to filter on when creating segments. Call identifyUser() before or alongside trackEvent() to ensure events are associated with a known email address. For logged-in users, identify them on app load; for anonymous visitors, the events are stored as anonymous and merged when the user provides their email. Important: Klaviyo's JavaScript API is client-side only and works in Bolt's WebContainer preview without any deployment or server-side setup. You can see events flowing in Klaviyo → Activity → Live View within seconds of firing them from your preview.
Add Klaviyo event tracking to my product and cart pages. On the product detail page, import the trackEvent function from lib/klaviyo.ts and call it in a useEffect when the page mounts: trackEvent('Viewed Product', { ProductName: product.name, ProductID: product.id, Categories: [product.category], ImageURL: product.imageUrl, URL: window.location.href, Price: product.price, Brand: 'My Store' }). On the cart page, when an item is added, call trackEvent('Added to Cart', { $value: item.price, AddedItemProductName: item.name, AddedItemProductID: item.id, AddedItemPrice: item.price, AddedItemQuantity: quantity, ItemNames: cartItemNames, CheckoutURL: '/checkout' }). On the order confirmation page, call trackEvent('Placed Order', { $value: order.total, OrderId: order.id, ItemNames: order.items.map(i => i.name), Categories: order.items.map(i => i.category) }).
Paste this in Bolt.new chat
1// hooks/useProductTracking.ts2import { useEffect } from 'react';3import { trackEvent } from '@/lib/klaviyo';45interface Product {6 id: string;7 name: string;8 price: number;9 category: string;10 imageUrl: string;11}1213export function useProductTracking(product: Product | null) {14 useEffect(() => {15 if (!product) return;16 trackEvent('Viewed Product', {17 ProductName: product.name,18 ProductID: product.id,19 Categories: [product.category],20 ImageURL: product.imageUrl,21 URL: window.location.href,22 Price: product.price,23 });24 }, [product?.id]);25}2627export function trackAddToCart(product: Product, quantity: number, cartItems: Product[]) {28 trackEvent('Added to Cart', {29 $value: product.price * quantity,30 AddedItemProductName: product.name,31 AddedItemProductID: product.id,32 AddedItemPrice: product.price,33 AddedItemQuantity: quantity,34 ItemNames: cartItems.map((i) => i.name),35 CheckoutURL: `${window.location.origin}/checkout`,36 });37}3839export function trackPurchase(orderId: string, items: Product[], total: number) {40 trackEvent('Placed Order', {41 $value: total,42 OrderId: orderId,43 ItemNames: items.map((i) => i.name),44 Categories: [...new Set(items.map((i) => i.category))],45 });46}Pro tip: Use Klaviyo's exact standard event names ('Viewed Product', 'Added to Cart', 'Placed Order') to unlock pre-built automation flow templates. Custom event names work fine but require you to build flows from scratch.
Expected result: Navigating through your Bolt app triggers Klaviyo events. Check Klaviyo → Activity → Live View to see events appearing in real-time with their properties.
Deploy and Set Environment Variables for Server-Side Operations
Deploy and Set Environment Variables for Server-Side Operations
The Klaviyo tracking snippet (Public API Key) works in Bolt's WebContainer preview, but server-side API routes using the Private API Key need proper environment variable configuration both locally and in production. In Bolt's project, your .env file holds both the public and private keys: VITE_KLAVIYO_PUBLIC_KEY for the tracking snippet (VITE_ prefix makes it available in client-side Vite code), KLAVIYO_PRIVATE_KEY for server-side API routes (no VITE_ prefix keeps it server-only), and KLAVIYO_LIST_ID for your subscriber list ID. When deploying to Netlify, navigate to Site Configuration → Environment Variables and add KLAVIYO_PRIVATE_KEY and KLAVIYO_LIST_ID. Also add NEXT_PUBLIC_KLAVIYO_PUBLIC_KEY (or VITE_KLAVIYO_PUBLIC_KEY) for the client-side snippet. For Bolt Cloud deployment, go to the Secrets panel in your project settings and add the same variables. After deployment, your subscriber API routes are accessible from the public internet and email signups will flow directly into Klaviyo. Klaviyo's webhook system (for receiving notifications when email events occur, like unsubscribes or bounces) also becomes available post-deployment — Klaviyo webhooks send events to your app when subscribers interact with emails. Register your webhook URL in Klaviyo → Account → Settings → Webhooks. During development in Bolt's WebContainer, incoming Klaviyo webhooks cannot be received because the preview URL is dynamic and unreachable from Klaviyo's servers — deploy first, then test webhooks on your production URL.
Configure my Bolt app for production Klaviyo integration. Ensure the .env file has placeholders for VITE_KLAVIYO_PUBLIC_KEY, KLAVIYO_PRIVATE_KEY, and KLAVIYO_LIST_ID. Create a Klaviyo webhook handler at /api/klaviyo/webhook that handles unsubscribe events: verify the request is from Klaviyo, parse the event payload, and when event_type is 'profile.unsubscribed_from_list', log the unsubscribe. Also add error handling to all existing Klaviyo API routes so they return meaningful error messages if the API key is invalid or rate limits are hit.
Paste this in Bolt.new chat
1// app/api/klaviyo/webhook/route.ts2import { NextRequest, NextResponse } from 'next/server';34export async function POST(request: NextRequest) {5 // Klaviyo sends a signing key in headers for verification6 // See Klaviyo docs for HMAC-based webhook verification7 const payload = await request.json();89 const { type, data } = payload;1011 switch (type) {12 case 'profile.unsubscribed_from_list':13 console.log('Unsubscribe event:', data?.attributes?.email);14 // Remove from your database, update user preferences, etc.15 break;16 case 'profile.subscribed_to_list':17 console.log('New subscriber:', data?.attributes?.email);18 break;19 default:20 console.log('Unhandled Klaviyo event:', type);21 }2223 return NextResponse.json({ received: true });24}Pro tip: Klaviyo webhooks require a deployed URL — the WebContainer preview cannot receive incoming HTTP connections from Klaviyo's servers. Set up webhooks in Klaviyo → Settings → Webhooks after deploying to Netlify or Bolt Cloud.
Expected result: The deployed app accepts email signups that appear in your Klaviyo lists. Klaviyo webhooks deliver unsubscribe and subscription events to your deployed webhook endpoint.
Common use cases
Email Signup Form with List Subscription
Add a newsletter signup form to your Bolt app that collects email addresses and subscribes visitors to a specific Klaviyo list. On submission, an API route calls Klaviyo's profiles API to create or update the subscriber profile and add them to your newsletter list — ready to receive your next campaign.
Create an email signup form with an Email input field and a Subscribe button. On submit, call POST /api/klaviyo/subscribe with the email address. The API route should call Klaviyo's REST API v3 to create a profile (POST to https://a.klaviyo.com/api/profiles/) and then subscribe it to my list using the list ID from KLAVIYO_LIST_ID env var. Use the KLAVIYO_PRIVATE_KEY env var for the Authorization header. Show a success message after subscribing. Add a honeypot field to prevent bot submissions.
Copy this prompt to try it in Bolt.new
E-commerce Behavioral Event Tracking
Track product interactions in a Bolt e-commerce app — viewed products, cart additions, and completed purchases. Klaviyo uses these events to build customer segments and trigger automated flows like abandoned cart emails. The JavaScript tracking snippet fires events directly from the browser with no server-side code.
Add Klaviyo event tracking to my e-commerce Bolt app. First, add the Klaviyo tracking snippet to the HTML head using my Public API Key from VITE_KLAVIYO_PUBLIC_KEY. Then, on the product page, call window._learnq.push(['track', 'Viewed Product', { ProductName: product.name, ProductID: product.id, Price: product.price, ImageURL: product.imageUrl }]) when the page loads. On the cart page, fire an 'Added to Cart' event when items are added. On checkout completion, fire a 'Placed Order' event with the order total and item list.
Copy this prompt to try it in Bolt.new
Campaign Analytics Dashboard
Build an internal analytics dashboard in Bolt that pulls Klaviyo campaign performance data — open rates, click rates, revenue per recipient, and unsubscribe rates. The dashboard gives your marketing team a quick overview without logging into Klaviyo's full interface.
Build a Klaviyo analytics dashboard in my Bolt app. Create an API route GET /api/klaviyo/campaigns that fetches the last 20 campaigns from Klaviyo's API: GET https://a.klaviyo.com/api/campaigns/?filter=equals(messages.channel,'email')&sort=-created_at&page[size]=20 using the KLAVIYO_PRIVATE_KEY. For each campaign, also fetch its metrics. Display the campaigns in a table with columns: Name, Status, Send Date, Recipients, Open Rate, Click Rate. Use Recharts to show a bar chart of open rates across the last 10 campaigns.
Copy this prompt to try it in Bolt.new
Troubleshooting
Klaviyo tracking snippet fires but no events appear in Klaviyo Live View
Cause: The Public API Key is incorrect, the snippet is loading before the DOM is ready, or an ad blocker is preventing the Klaviyo script from loading in the browser.
Solution: Open browser DevTools → Network tab and filter for 'klaviyo'. Verify the script request succeeds (200 status). Check the URL includes your correct company ID (Public API Key). Confirm your Public API Key in Klaviyo → Account → Settings → API Keys. Disable browser ad blockers for testing — Klaviyo's tracking domain is sometimes blocked.
POST /api/klaviyo/subscribe returns 400 error with 'Invalid revision'
Cause: The required 'revision' header is missing from the API request. Klaviyo's v3 API requires this header on all requests.
Solution: Add the 'revision' header to all API v3 requests. Use '2024-02-15' or the latest revision from Klaviyo's changelog.
1// All Klaviyo v3 API requests need this header2const headers = {3 'Authorization': `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,4 'revision': '2024-02-15',5 'Content-Type': 'application/json',6};Profile is created but not added to the Klaviyo list
Cause: The list subscription call is made before the profile creation fully resolves, or the list ID is incorrect.
Solution: Ensure you await the profile creation response and extract the profile ID before making the list subscription call. Verify the KLAVIYO_LIST_ID matches the list ID shown in Klaviyo → Lists & Segments → your list URL (6-character alphanumeric ID).
1// Correct: await profile creation, then use returned ID for list subscription2const profileData = await profileRes.json();3const profileId = profileData.data?.id;4if (!profileId) throw new Error('Profile creation failed');5// Now use profileId in the list subscription callBest practices
- Use the Klaviyo JavaScript snippet for all client-side event tracking — it's simpler than API route calls and fires events immediately without server round-trips
- Never put your Klaviyo Private API Key in client-side code or the VITE_ prefix — it belongs in server-side API routes only
- Use Klaviyo's standard event names ('Viewed Product', 'Added to Cart', 'Placed Order') to unlock pre-built automation flow templates without additional configuration
- Always include the required 'revision' header in all Klaviyo v3 API requests — missing it causes 400 errors
- Identify users with window._learnq.push(['identify', ...]) as early as possible in your app flow so events are associated with email addresses, not anonymous profiles
- Set up Klaviyo webhooks after deploying to receive real-time notifications about unsubscribes and bounces — the WebContainer preview cannot receive incoming webhook requests
- Cache Klaviyo analytics responses in your dashboard API routes — campaign data rarely changes minute-to-minute and caching reduces API calls
Alternatives
Mailchimp is a general-purpose email marketing platform with a larger free tier and simpler setup — better for non-e-commerce content newsletters where revenue attribution isn't needed.
SendGrid focuses on transactional email delivery (receipts, notifications, password resets) rather than marketing campaigns — use it alongside Klaviyo for different email types.
ConvertKit (now Kit) uses tag-based subscriber management preferred by content creators and bloggers — better for audience-building than e-commerce revenue attribution.
Drip is a Klaviyo alternative built for e-commerce with similar behavioral tracking and automation flows, often at a lower price point for smaller subscriber lists.
Frequently asked questions
Can I use Klaviyo in Bolt.new without deploying?
Yes — the Klaviyo JavaScript tracking snippet works immediately in Bolt's WebContainer preview. You can track page views, product views, and custom events during development. Server-side API routes for list subscriptions also work in the preview since they make outbound calls only. However, receiving Klaviyo webhooks (for unsubscribe notifications and email event data) requires a deployed app with a public URL.
What is the difference between Klaviyo's Public API Key and Private API Key?
The Public API Key (also called the site ID or company ID) is safe to include in client-side code and the browser-loaded tracking snippet. It only allows event tracking and profile identification. The Private API Key has full API access — creating profiles, managing lists, accessing analytics, and sending emails. It must be kept in server-side API routes only and never exposed in client-side code or committed to a public repository.
Does the Klaviyo free plan allow API access?
Yes — Klaviyo's free plan includes full REST API access for up to 500 contacts and 500 email sends per month. This is sufficient for development and small apps. The API key is available immediately after signup with no approval process. Rate limits are generous at 500 requests per second on the v3 API.
How do I trigger an automated email flow from my Bolt app?
Klaviyo flows are triggered by events, not direct API calls. When your app fires a tracking event (e.g., 'Added to Cart'), Klaviyo evaluates it against all active flows with that trigger. To create an abandoned cart flow: set up the flow in Klaviyo with trigger 'Added to Cart', add a time delay (e.g., 4 hours), then add an email action. The flow fires automatically when anyone tracked in your Bolt app adds to cart and doesn't complete a purchase within that window.
Can Bolt.new receive Klaviyo webhook events during development?
No — during development in Bolt's WebContainer, the preview URL is dynamic and cannot be registered as a stable webhook endpoint in Klaviyo. Deploy your app to Netlify or Bolt Cloud first to get a stable public URL, then register it in Klaviyo → Account → Settings → Webhooks. This is a fundamental limitation of WebContainers — they run in the browser and cannot receive incoming HTTP connections from external services.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation