Merge Guest Data
Transfer guest cart and wishlist to authenticated user account
Merge Guest Data
When a guest user signs up or signs in, merge their cart and wishlist items into their authenticated account.
This endpoint should be called immediately after successful sign-up or sign-in if a guest token exists. It ensures users don't lose items they added while browsing as a guest.
Endpoint
POST /v1/auth/merge-guest-dataRequest Headers
| Header | Type | Required | Description |
|---|---|---|---|
x-api-key | string | Yes | Your API key |
x-customer-id | string | Yes | Authenticated user's ID |
Content-Type | string | Yes | Must be application/json |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
guestToken | string | Yes | The guest token from previous session |
Response
Success Response (200)
{
"success": true,
"message": "Guest data merged successfully",
"merged": {
"cartItems": 3,
"wishlistItems": 2
}
}Error Responses
| Code | Description |
|---|---|
| 400 | Invalid guest token |
| 401 | User must be authenticated (missing x-customer-id) |
| 500 | Failed to merge guest data |
How It Works
- Guest Browsing: User adds items to cart/wishlist without logging in
- Sign Up/In: User creates account or logs in
- Auto Merge: System transfers guest items to user's account
- Clean Up: Guest token is removed
sequenceDiagram
participant Guest
participant App
participant API
Guest->>App: Add items to cart (as guest)
App->>API: POST /v1/cart (with x-guest-token)
API-->>App: Returns guest_token in header
App->>App: Store guest_token in cookie
Guest->>App: Sign up
App->>API: POST /v1/signup
API-->>App: Returns user data & auth token
App->>API: POST /v1/auth/merge-guest-data
Note over API: Merges cart & wishlist<br/>to user account
API-->>App: Success
App->>App: Delete guest_token cookieExamples
interface MergeGuestDataRequest {
guestToken: string;
}
interface MergeGuestDataResponse {
success: boolean;
message: string;
merged: {
cartItems: number;
wishlistItems: number;
};
}
async function mergeGuestData(
guestToken: string,
userId: string
): Promise<MergeGuestDataResponse> {
const response = await fetch(
'https://api.yourstore.com/v1/auth/merge-guest-data',
{
method: 'POST',
headers: {
'x-api-key': process.env.BACKEND_API_KEY!,
'x-customer-id': userId,
'Content-Type': 'application/json',
},
body: JSON.stringify({ guestToken }),
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to merge guest data');
}
return response.json();
}
// Usage
try {
const result = await mergeGuestData('guest_token_123', 'user_456');
console.log(`Merged ${result.merged.cartItems} cart items`);
console.log(`Merged ${result.merged.wishlistItems} wishlist items`);
} catch (error) {
console.error('Merge failed:', error);
// Continue anyway - user is still logged in
}async function mergeGuestData(guestToken, userId) {
const response = await fetch(
'https://api.yourstore.com/v1/auth/merge-guest-data',
{
method: 'POST',
headers: {
'x-api-key': process.env.BACKEND_API_KEY,
'x-customer-id': userId,
'Content-Type': 'application/json',
},
body: JSON.stringify({ guestToken }),
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to merge guest data');
}
return response.json();
}
// Usage
mergeGuestData('guest_token_123', 'user_456')
.then(result => {
console.log(`Merged ${result.merged.cartItems} cart items`);
console.log(`Merged ${result.merged.wishlistItems} wishlist items`);
})
.catch(error => {
console.error('Merge failed:', error);
});'use server'
import { cookies } from 'next/headers';
export async function signInWithGuestMerge(email: string, password: string) {
// 1. Get guest token before signing in
const cookieStore = await cookies();
const guestToken = cookieStore.get('guest_token')?.value;
// 2. Sign in
const signInResponse = await fetch('https://api.yourstore.com/v1/signin', {
method: 'POST',
headers: {
'x-api-key': process.env.BACKEND_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
if (!signInResponse.ok) {
throw new Error('Sign in failed');
}
const { token, user } = await signInResponse.json();
// 3. Store auth data
cookieStore.set('auth_token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 90,
});
cookieStore.set('user_data', JSON.stringify(user), {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 90,
});
// 4. Merge guest data if token exists
if (guestToken) {
try {
const mergeResponse = await fetch(
'https://api.yourstore.com/v1/auth/merge-guest-data',
{
method: 'POST',
headers: {
'x-api-key': process.env.BACKEND_API_KEY!,
'x-customer-id': user.id,
'Content-Type': 'application/json',
},
body: JSON.stringify({ guestToken }),
}
);
if (mergeResponse.ok) {
const mergeData = await mergeResponse.json();
console.log('Guest data merged:', mergeData.merged);
}
} catch (error) {
console.error('Failed to merge guest data:', error);
// Don't fail the sign-in if merge fails
} finally {
// 5. Clear guest token
cookieStore.delete('guest_token');
}
}
return { user };
}Using Cookie Manager
The CookieManager utility simplifies this process:
'use server'
import { cookieManager } from '@/lib/auth-tools';
export async function signInWithAutoMerge(email: string, password: string) {
// Get guest token before it's cleared
const guestToken = await cookieManager.getGuestToken();
// Sign in
const response = await fetch('https://api.yourstore.com/v1/signin', {
method: 'POST',
headers: await cookieManager.buildApiHeaders(),
body: JSON.stringify({ email, password }),
});
if (!response.ok) throw new Error('Sign in failed');
const loginResponse = await response.json();
// This automatically clears guest token
await cookieManager.setAuthenticatedUser(loginResponse);
// Merge if guest token existed
if (guestToken) {
await mergeGuestDataHelper(guestToken, loginResponse.user.id);
}
return loginResponse.user;
}
async function mergeGuestDataHelper(guestToken: string, userId: string) {
try {
const response = await fetch(
'https://api.yourstore.com/v1/auth/merge-guest-data',
{
method: 'POST',
headers: {
'x-api-key': process.env.BACKEND_API_KEY!,
'x-customer-id': userId,
'Content-Type': 'application/json',
},
body: JSON.stringify({ guestToken }),
}
);
if (!response.ok) {
console.error('Merge failed but continuing...');
}
} catch (error) {
console.error('Error merging guest data:', error);
// Don't throw - user is still signed in
}
}UI Feedback
Show users when their items are being merged:
'use client'
import { useState } from 'react';
export default function SignInForm() {
const [merging, setMerging] = useState(false);
const [mergeResult, setMergeResult] = useState<{
cartItems: number;
wishlistItems: number;
} | null>(null);
async function handleSignIn(formData: FormData) {
setMerging(true);
const result = await signInAction(formData);
if (result.merged) {
setMergeResult(result.merged);
setTimeout(() => setMergeResult(null), 3000);
}
setMerging(false);
}
return (
<>
{merging && (
<div className="alert alert-info">
Transferring your cart items...
</div>
)}
{mergeResult && (
<div className="alert alert-success">
✓ Transferred {mergeResult.cartItems} cart items and{' '}
{mergeResult.wishlistItems} wishlist items to your account!
</div>
)}
<form action={handleSignIn}>
{/* form fields */}
</form>
</>
);
}Handling Conflicts
If both guest and authenticated user have the same product in cart, the API typically:
- Combines quantities
- Or keeps the higher quantity
- Check your backend implementation for specific behavior
Best Practices
Always Attempt Merge
Even if you're not sure a guest token exists, attempt the merge:
async function afterAuthentication(user: User) {
const guestToken = await cookieManager.getGuestToken();
if (guestToken) {
// Fire and forget - don't block user flow
mergeGuestData(guestToken, user.id).catch(console.error);
}
// Continue with redirect
redirect('/dashboard');
}Silent Failure
Don't block authentication flow if merge fails:
try {
await mergeGuestData(guestToken, userId);
} catch (error) {
// Log error but don't show to user
console.error('Guest data merge failed:', error);
// User is still successfully authenticated
}Clear Token After Merge
Always clear the guest token after successful merge:
await cookieManager.clearGuestToken();Common Issues
Issue: "Invalid guest token"
Cause: Token expired or already used
Solution:
if (response.status === 400) {
// Token already merged or invalid - safe to continue
await cookieManager.clearGuestToken();
return { success: true, merged: { cartItems: 0, wishlistItems: 0 } };
}Issue: "User must be authenticated"
Cause: Missing x-customer-id header
Solution: Ensure user data is stored before calling merge:
await cookieManager.setAuthenticatedUser(loginResponse);
// Now guest merge will have access to customer ID
await mergeGuestData(guestToken);Testing the Flow
Test both scenarios in your application:
// Test 1: Guest with items signs up
async function testGuestSignup() {
// 1. Add items as guest
await addToCart(productId);
const guestToken = await cookieManager.getGuestToken();
// 2. Sign up
await signUp({ name, email, password });
// 3. Verify items are in authenticated cart
const cart = await getCart();
expect(cart.items.length).toBeGreaterThan(0);
}
// Test 2: User with empty guest session
async function testEmptyGuestSignup() {
// Sign up without adding anything
await signUp({ name, email, password });
// Should not error
expect(response.ok).toBe(true);
}Related Endpoints
- Sign Up - Create new user account
- Sign In - Authenticate existing user
- Get Cart - View cart items
- Get Wishlist - View wishlist items