API Reference
Complete API documentation for the WhatIf SDK đ
API Reference
Complete reference for all classes, methods, and types in the WhatIf SDK.
Class: WhatIf
Main SDK class for initializing and controlling the widget.
Constructor
new WhatIf(projectId: string, baseUrl?: string, theme?: ThemeConfig)Creates a new WhatIf instance.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
projectId | string | â | Your project ID from the dashboard |
baseUrl | string | â | API base URL (default: "/api") |
theme | ThemeConfig | â | Theme configuration object |
Example:
const whatif = new WhatIf('your-app-id', '/api', {
darkMode: 'auto',
colors: { primary: '#3b82f6' }
});Methods
ask()
Display a question widget with intelligent user tracking and question filtering.
ask(questionId: string, options?: AskOptions): Promise<void>Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
questionId | string | â | Question ID from dashboard |
options | AskOptions | â | User, organization, and context options |
Returns: Promise<void>
Intelligent Features:
- Smart Question Filtering: Backend automatically decides if user should see the question based on response history and question preferences
- Response Threading: Tracks how individual users' feedback evolves over time across multiple iterations
- Automatic Iteration Tracking: Maintains response history and enables trend analysis
- Browser & Device Context: Automatically captures and displays enhanced browser/device information
- Global Configuration: Set user/org data once during initialization for simplified usage
Example:
await whatif.ask('question-id', {
user: {
id: 'user_123', // Required for smart filtering
name: 'Sarah Chen', // Optional - for dashboard display
email: 'sarah@example.com', // Optional - for dashboard display
avatar: 'https://example.com/avatar.jpg' // Optional - for dashboard display
},
org: {
id: 'org_456', // Optional - for organization tracking
name: 'Acme Corp', // Required if org provided - for dashboard display
logo: 'https://example.com/logo.jpg' // Optional - for dashboard display
},
context: {
// Custom metadata - any additional data you want to track
page: 'checkout',
feature: 'new-payment-flow',
userType: 'premium'
}
});Automatic Data Collection: WhatIf automatically captures timestamp, browser info, device info, page info, and previous URL with every response - no code required! See the Context Guide for details.
Global Initialization
Set user and organization data once during SDK initialization for simplified usage across your app.
Constructor Options
const whatif = new WhatIf('your-project-id', '/api', {
user: {
id: 'user_123', // Required for smart filtering
name: 'Sarah Chen', // Optional - for dashboard display
email: 'sarah@example.com',
avatar: 'https://example.com/avatar.jpg'
},
org: {
id: 'org_456', // Optional - for organization tracking
name: 'Acme Corp', // Required if org provided
logo: 'https://example.com/logo.jpg'
},
context: {
appVersion: '1.2.3', // Custom metadata
environment: 'production'
}
});Simplified Usage
Once initialized, you can use the simple API:
// Simple call - uses pre-configured user/org data
await whatif.ask('checkout-feedback');
// Override for specific cases
await whatif.ask('admin-feedback', {
user: { id: 'admin_789', name: 'Admin User' }, // Override user
context: { page: 'admin-panel' } // Additional context
});Benefits:
- â
Cleaner Code: No need to pass user/org data with every
ask()call - â Consistent Context: Same user/org data across all questions
- â Flexible Overrides: Still customize per call when needed
- â Backward Compatible: Existing code continues to work
Intelligent Question Filtering
WhatIf now includes smart backend logic that automatically decides whether to show questions to users based on their response history and question preferences.
Key Features:
- Response History Tracking: The backend remembers which users have answered which questions
- Question Preferences: Creators can set rules like "re-ask after 30 days" or "re-ask when content updates"
- Manual Overrides: Creators can manually trigger "Ask Again" to show questions to all users
- Response Threading: Tracks how individual feedback evolves across multiple interactions
How It Works:
- User Identification: Pass
user.idin theask()options - Backend Decision: Server checks response history and preferences
- Smart Display: Widget only appears if the user should see the question
- Automatic Tracking: All responses are threaded and tracked by iteration
Dashboard Controls:
Creators can manage question visibility through the dashboard:
- Re-asking Rules: Set time-based or update-based re-asking
- Manual Overrides: Click "Ask Again" to immediately show questions to all users
- Analytics: View response evolution and engagement metrics
setTheme()
Update the theme configuration at runtime.
setTheme(theme: ThemeConfig): voidParameters:
| Name | Type | Required | Description |
|---|---|---|---|
theme | ThemeConfig | â | New theme configuration |
Example:
whatif.setTheme({
darkMode: 'dark',
colors: { primary: '#10b981' }
});remove()
Manually close the currently displayed widget.
remove(): voidExample:
whatif.remove();destroy()
Clean up all SDK resources including event listeners and widgets.
destroy(): voidAlways call destroy() when unmounting to prevent memory leaks.
Example:
// Vanilla JS
whatif.destroy();
// React
useEffect(() => {
return () => whatif.destroy();
}, []);React SDK
WhatIfProvider
Provider component that initializes and manages the SDK.
function WhatIfProvider(props: WhatIfProviderProps): JSX.ElementProps:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
projectId | string | â | - | Your project ID |
baseUrl | string | â | "/api" | API endpoint |
user | User | â | - | Default user data for all questions |
org | Org | â | - | Default organization data for all questions |
context | Record<string, unknown> | â | - | Default custom metadata for all questions |
theme | ThemeConfig | â | - | Theme configuration |
children | ReactNode | â | - | Child components |
Example:
<WhatIfProvider
projectId="your-project-id"
user={{
id: 'user_123', // Required for smart filtering
name: 'Sarah Chen', // Optional display data
email: 'sarah@example.com'
}}
org={{
name: 'Acme Corp', // Required if org provided
logo: 'https://example.com/logo.jpg'
}}
context={{
appVersion: '1.2.3', // Custom metadata
environment: 'production'
}}
theme={{ darkMode: 'auto' }}
>
<App />
</WhatIfProvider>useWhatIf
Hook that provides access to SDK methods. Automatically uses user/org/context data configured in the WhatIfProvider.
function useWhatIf(): {
ask: (questionId: string, options?: AskOptions) => Promise<void>;
setTheme: (theme: ThemeConfig) => void;
}Returns:
| Property | Type | Description |
|---|---|---|
ask | Function | Trigger a question (uses provider defaults) |
setTheme | Function | Update theme |
Key Features:
- Automatic Data: Uses user/org/context data from WhatIfProvider
- Override Support: Pass options to customize per call
- Smart Filtering: Backend automatically decides question visibility
Examples:
// Simple usage - uses provider configuration
const { ask } = useWhatIf();
<button onClick={() => ask('feedback-question')}>
Give Feedback
</button>
// Override for specific cases
<button onClick={() => ask('admin-question', {
user: { id: 'admin-user', name: 'Admin' },
context: { page: 'admin-panel' }
})}>
Admin Feedback
</button>Data Inheritance & User ID Requirement
Provider Data Inheritance
The SDK automatically inherits user/org/context data from the WhatIfProvider, but you can override it per question:
Provider Setup:
<WhatIfProvider
projectId="project-id"
user={{ id: 'default-user', name: 'John', email: 'john@example.com', avatar: 'avatar.jpg' }}
org={{ name: 'Acme Corp', logo: 'logo.png' }}
context={{ app: 'my-app' }}
>
{/* All questions inherit this data */}
</WhatIfProvider>Automatic Inheritance:
const { ask } = useWhatIf();
// â
Inherits all provider data
ask('question-id');Selective Overrides:
const { ask } = useWhatIf();
// â
Inherits user/org, overrides context
ask('checkout-question', {
context: { page: 'checkout', step: 'payment' }
});
// â
Overrides user, inherits org/context
ask('admin-feedback', {
user: { id: 'admin-user', name: 'Admin' }
});User ID Requirement
â ď¸ Critical: Always provide a user.id to enable smart question filtering and prevent repeated questions:
// â
Good - enables question filtering
<WhatIfProvider user={{
id: 'user123', // Required for filtering
name: 'John Doe', // Optional for display
email: 'john@example.com', // Optional for display
avatar: 'avatar.jpg' // Optional for display
}}>
<App />
</WhatIfProvider>
// â Bad - questions will show repeatedly
<WhatIfProvider user={{
name: 'John Doe' // Missing id - no filtering possible
}}>
<App />
</WhatIfProvider>Why it matters: Without user.id, WhatIf cannot track which users have seen questions, causing the same question to appear every time the page loads.
Changelog
v1.1.0 (Latest)
- New: Global user/org/context initialization - set once, use everywhere
- New: Simplified API -
ask('question-id')with pre-configured defaults - New: Override capability - still customize per call when needed
v1.0.0
- New: Smart question filtering based on user response history and question preferences
- New: Response threading and iteration tracking for individual users
- New: Manual "Ask Again" functionality for dashboard users
- New: Enhanced browser and device context parsing and display
- New: Question preferences system (re-asking rules, time-based and update-based filtering)
<WhatIfProvider user={{
id: 'user-123',
name: 'John' // Optional display data
}}
org={{
name: 'Acme Corp' // Required if org provided
}}
context={{
customData: 'value' // Only custom metadata now
}}>
<App />
</WhatIfProvider>
## Types
### ThemeConfig
Complete theme configuration object.
```typescript
interface ThemeConfig {
darkMode?: DarkMode;
position?: WidgetPosition;
colors?: ColorPalette;
typography?: Typography;
spacing?: Spacing;
border?: BorderConfig;
shadow?: ShadowConfig;
animation?: AnimationConfig;
customCss?: string;
}DarkMode
Dark mode strategy.
type DarkMode = 'auto' | 'light' | 'dark' | 'disabled';'auto'- Auto-detect system preference'light'- Force light mode'dark'- Force dark mode'disabled'- No dark mode support (default)
WidgetPosition
Widget position on screen.
type WidgetPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';ColorPalette
Color configuration for the widget.
interface ColorPalette {
primary?: string;
primaryHover?: string;
background?: string;
textPrimary?: string;
textSecondary?: string;
border?: string;
borderHover?: string;
hoverBackground?: string;
closeButton?: string;
closeButtonHover?: string;
ratingFilled?: string;
ratingEmpty?: string;
}All colors accept:
- Hex:
#3b82f6 - RGB:
rgb(59, 130, 246) - RGBA:
rgba(59, 130, 246, 0.5) - CSS variables:
var(--primary-color)
Typography
Typography configuration.
interface Typography {
fontFamily?: string;
questionSize?: string;
questionWeight?: string;
inputSize?: string;
buttonSize?: string;
buttonWeight?: string;
}Size values: Any valid CSS size (16px, 1rem, 1.5em)
Weight values: '400', '500', '600', '700', '800', 'bold', etc.
Spacing
Spacing and layout configuration.
interface Spacing {
padding?: string;
gap?: string;
offset?: string;
}All values: Any valid CSS size (16px, 1rem, 20px, etc.)
BorderConfig
Border styling configuration.
interface BorderConfig {
radius?: string;
buttonRadius?: string;
inputRadius?: string;
width?: string;
}Radius values: '4px', '8px', '12px', '24px', '50%', etc.
Width values: '1px', '2px', etc.
ShadowConfig
Shadow effect configuration.
interface ShadowConfig {
enabled?: boolean;
container?: string;
}Container value: Any valid CSS box-shadow value
Examples:
'0 4px 12px rgba(0,0,0,0.08)'
'0 8px 24px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.08)'AnimationConfig
Animation configuration.
interface AnimationConfig {
enabled?: boolean;
duration?: number;
easing?: string;
}Duration: Milliseconds (200, 400, 600)
Easing: Any CSS easing function
Common easing functions:
'ease'
'ease-in-out'
'cubic-bezier(0.16, 1, 0.3, 1)'
'cubic-bezier(0.68, -0.55, 0.265, 1.55)' // BouncyAskOptions
Options for the ask() method. The user object enables intelligent question filtering and response tracking.
interface AskOptions {
user?: {
id: string; // For tracking/identification
name?: string; // For display
email?: string; // For display
avatar?: string; // For display
};
org?: {
id?: string; // For identification
name: string; // For display
logo?: string; // For display
};
context?: Record<string, unknown>; // Custom metadata only
userFingerprint?: string; // Fallback identification
}Properties:
| Name | Type | Description |
|---|---|---|
user | object | User identification and display data |
org | object | Organization identification and display data |
context | Record<string, unknown> | Custom metadata (no longer contains user/org data) |
userFingerprint | string | Fallback anonymous user identifier |
User & Organization Objects:
User and organization data are now passed as top-level properties for better API clarity:
// User object (for identification + display)
user?: {
id: string; // Required: Unique user identifier for tracking
name?: string; // Optional: User's display name
email?: string; // Optional: User's email
avatar?: string; // Optional: URL to user's avatar
};
// Organization object (for identification + display)
org?: {
id?: string; // Optional: Organization's unique ID
name: string; // Required: Organization's display name
logo?: string; // Optional: URL to organization's logo
};
// Context (pure custom metadata)
context?: {
page: string; // Current page
customMetric: number; // Any custom data
[key: string]: unknown;
};Example:
{
context: {
// Beautiful user display
user: {
name: 'Sarah Chen',
email: 'sarah@example.com',
avatar: 'https://example.com/avatar.jpg'
},
// Beautiful organization display
org: {
name: 'Acme Corp',
id: 'org_acme_123',
logo: 'https://example.com/logo.png'
},
// Custom context
userId: 'user-123',
page: 'checkout',
plan: 'pro',
timestamp: Date.now()
},
userFingerprint: 'anon-123456'
}Question
Question object structure (returned from API).
interface Question {
questionId: string;
text: string;
type: 'single_choice' | 'text' | 'rating';
options?: string[];
targetElementId?: string;
}Error Handling
All async methods can throw errors. Always handle them:
try {
await whatif.ask('question-id');
} catch (error) {
console.error('Failed to show feedback:', error);
// Handle error appropriately
}TypeScript
Full TypeScript support with exported types:
import {
WhatIf,
type ThemeConfig,
type DarkMode,
type WidgetPosition,
type ColorPalette,
type Typography,
type Spacing,
type BorderConfig,
type ShadowConfig,
type AnimationConfig,
type AskOptions,
type Question
} from '@twenty1tools/whatif';Browser Compatibility
Supported:
- Chrome/Edge 90+
- Firefox 90+
- Safari 14+
- iOS Safari 14+
- Chrome Android 90+
Requirements:
- ES6+ support
- Shadow DOM support
prefers-color-schememedia query (for auto dark mode)
Migration Guide
From v0.1.x to v0.2.x
No breaking changes! All existing code works as before.
New features:
- Theme customization
- Dark mode support
- Dynamic theme updates
setTheme()methoddestroy()method
// Old code still works
const whatif = new WhatIf('app-id');
whatif.ask('question-id');
// New features available
whatif.setTheme({ darkMode: 'dark' });
whatif.destroy();Examples
Complete Example
import { WhatIf, type ThemeConfig } from '@twenty1tools/whatif';
const theme: ThemeConfig = {
darkMode: 'auto',
position: 'bottom-right',
colors: {
primary: '#3b82f6',
primaryHover: '#2563eb',
},
typography: {
fontFamily: 'Inter, sans-serif',
},
border: {
radius: '16px',
},
animation: {
enabled: true,
duration: 400,
}
};
const whatif = new WhatIf('your-app-id', '/api', theme);
// Trigger with context
await whatif.ask('feedback-question', {
context: {
userId: 'user-123',
page: window.location.pathname,
timestamp: Date.now()
}
});
// Update theme later
whatif.setTheme({
darkMode: 'dark',
colors: { primary: '#10b981' }
});
// Clean up
whatif.destroy();