Complete reference for the window.Meticulous JavaScript API available in your application.
The window.Meticulous object is exposed by the Meticulous recorder snippet and provides methods for:
- Detecting test mode
- Recording custom data
- Controlling replay timing
- Handling custom events
Availability: Only when Meticulous recorder is loaded.
TypeScript types: See TypeScript Types for full type definitions.
Type: boolean | undefined
Description: Indicates whether the app is running as a Meticulous test.
Values:
true: Running in test/replay modefalseorundefined: Running normally (production/development)
Example:
if (window.Meticulous?.isRunningAsTest) {
// Skip validation, use test data, etc.
console.log('Running as Meticulous test');
} else {
// Normal application logic
console.log('Running normally');
}
Common use cases:
- Skip form validation
- Bypass authentication
- Use deterministic values (timestamps, IDs)
- Disable analytics/tracking
- Skip animations
Signature: recordCustomValues(values: Record<string, any>): void
Description: Store custom data during recording to be retrieved during replay.
Parameters:
values: Object with key-value pairs to store
Size limits:
- Development: 20MB total (set
data-is-production-environment="false") - Production: 1MB total
When to use:
- Store file upload contents
- Record feature flag values
- Save user context
- Preserve random/dynamic values
Example:
// During recording
const handleFileUpload = (file: File) => {
const reader = new FileReader();
reader.onload = (e) => {
const fileData = e.target?.result as string;
// Store for replay
if (window.Meticulous?.recordCustomValues) {
window.Meticulous.recordCustomValues({
uploadedFile: fileData,
fileName: file.name,
fileType: file.type,
});
}
processFile(fileData);
};
reader.readAsDataURL(file);
};
Multiple calls: Values are merged, not overwritten. Later calls add/update keys.
// First call
window.Meticulous.recordCustomValues({ key1: 'value1' });
// Second call - adds key2, keeps key1
window.Meticulous.recordCustomValues({ key2: 'value2' });
// Result: { key1: 'value1', key2: 'value2' }
Signature: getCustomValues(): Record<string, any> | undefined
Description: Retrieve custom values stored during recording.
Returns: Object with stored values, or undefined if none.
When to use:
- Restore file upload data during replay
- Get feature flag values
- Restore user context
Example:
// During replay
useEffect(() => {
if (window.Meticulous?.isRunningAsTest) {
const stored = window.Meticulous.getCustomValues();
if (stored?.uploadedFile) {
// Restore file preview
setPreview(stored.uploadedFile);
}
if (stored?.featureFlags) {
// Apply feature flags
setFlags(stored.featureFlags);
}
}
}, []);
Complete pattern:
function useStoredValue(key: string) {
const [value, setValue] = useState(null);
useEffect(() => {
if (window.Meticulous?.isRunningAsTest) {
const stored = window.Meticulous.getCustomValues();
if (stored?.[key]) {
setValue(stored[key]);
}
}
}, [key]);
return value;
}
// Usage
const userContext = useStoredValue('userContext');
Signature: pause(): void
Description: Pause replay until resume() is called.
When to use:
- Before async operations (data loading, image loading)
- Before third-party script initialization
- When content loads dynamically
Important: Always pair with resume(). Unpaired pause() will hang the test.
Example - Data Loading:
async function loadCriticalData() {
window.Meticulous?.pause?.();
try {
const data = await fetchDataFromAPI();
setData(data);
} finally {
window.Meticulous?.resume?.();
}
}
Example - Image Loading:
function LazyImage({ src }: { src: string }) {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (!loaded && window.Meticulous?.isRunningAsTest) {
window.Meticulous?.pause?.();
}
}, [loaded]);
const handleLoad = () => {
setLoaded(true);
window.Meticulous?.resume?.();
};
return <img src={src} onLoad={handleLoad} />;
}
Signature: resume(): void
Description: Resume replay after pause().
When to use: After the async operation started with pause() completes.
Example - Third-Party Script:
function loadGoogleMaps() {
return new Promise((resolve) => {
if (window.google?.maps) {
resolve(window.google.maps);
return;
}
window.Meticulous?.pause?.();
const script = document.createElement('script');
script.src = 'https://maps.googleapis.com/.../js?key=KEY';
script.onload = () => {
window.Meticulous?.resume?.();
resolve(window.google.maps);
};
script.onerror = () => {
window.Meticulous?.resume?.();
reject(new Error('Failed to load'));
};
document.head.appendChild(script);
});
}
Signature: recordCustomEvent(event: { type: string; payload?: any }): void
Description: Record a custom event with optional payload.
Parameters:
event.type: String identifying the event typeevent.payload: Optional data to store with event
When to use: Advanced scenarios requiring fine-grained replay control.
Example:
// Record event during session
function handlePaymentComplete(transaction: Transaction) {
if (window.Meticulous?.recordCustomEvent) {
window.Meticulous.recordCustomEvent({
type: 'PAYMENT_COMPLETE',
payload: {
transactionId: transaction.id,
amount: transaction.amount,
timestamp: Date.now(),
},
});
}
showSuccessMessage();
}
Signature: onReplayCustomEvent(handler: (event: CustomEvent) => void): void
Description: Register handler for custom events during replay.
Parameters:
handler: Function called when custom event is replayed
Example:
// Setup handler during component mount
useEffect(() => {
if (window.Meticulous?.onReplayCustomEvent) {
window.Meticulous.onReplayCustomEvent((event) => {
if (event.type === 'PAYMENT_COMPLETE') {
console.log('Replaying payment:', event.payload);
handlePaymentReplay(event.payload);
}
});
}
}, []);
Check for the meticulous-is-test header in server-side code:
Value: "1" when running as test
Security: For secure detection, set a custom secret header in project settings.
import { headers } from 'next/headers';
export function isMeticulousTest(): boolean {
return headers().get('meticulous-is-test') === '1';
}
// Usage in Server Component
export default function Page() {
const isTest = isMeticulousTest();
if (isTest) {
// Skip auth, use test data, etc.
}
return <div>...</div>;
}
export const getServerSideProps = (context) => {
const { req } = context;
const isTest = req.headers['meticulous-is-test'] === '1';
return {
props: {
isRunningAsMeticulousTest: isTest,
},
};
};
function Page({ isRunningAsMeticulousTest }) {
if (isRunningAsMeticulousTest) {
// Test-specific rendering
}
return <div>...</div>;
}
app.get('/api/data', (req, res) => {
const isTest = req.headers['meticulous-is-test'] === '1';
if (isTest) {
// Return test data
res.json({ data: 'test-data' });
} else {
// Return real data
res.json({ data: getRealData() });
}
});
function submitForm(data: FormData) {
if (!window.Meticulous?.isRunningAsTest) {
// Only validate in normal mode
if (!data.email) {
throw new Error('Email required');
}
}
// Submit form
return api.post('/submit', data);
}
function generateId(): string {
if (window.Meticulous?.isRunningAsTest) {
return 'test-id-12345';
}
return crypto.randomUUID();
}
function getCurrentTimestamp(): string {
if (window.Meticulous?.isRunningAsTest) {
return '2024-01-01T00:00:00Z';
}
return new Date().toISOString();
}
function initializeServices() {
if (!window.Meticulous?.isRunningAsTest) {
initializeAnalytics();
initializeSentry();
initializeIntercom();
}
}
function useFeatureFlag(flagName: string): boolean {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
if (window.Meticulous?.isRunningAsTest) {
const stored = window.Meticulous.getCustomValues();
if (stored?.featureFlags?.[flagName] !== undefined) {
setEnabled(stored.featureFlags[flagName]);
return;
}
}
// Normal flag fetching
fetchFlag(flagName).then(setEnabled);
}, [flagName]);
return enabled;
}
// Record flags during session
if (window.Meticulous?.recordCustomValues) {
window.Meticulous.recordCustomValues({
featureFlags: {
newCheckout: true,
experimentalUI: false,
},
});
}
function WelcomeBanner() {
if (window.Meticulous?.isRunningAsTest) {
// Don't show banner during tests
return null;
}
return <div>Welcome!</div>;
}
function useMeticulous() {
return {
isTest: window.Meticulous?.isRunningAsTest ?? false,
recordValues: window.Meticulous?.recordCustomValues,
getValues: window.Meticulous?.getCustomValues,
pause: window.Meticulous?.pause,
resume: window.Meticulous?.resume,
};
}
// Usage
function MyComponent() {
const { isTest, recordValues } = useMeticulous();
if (isTest) {
// Test-specific logic
}
}
const MeticulousContext = createContext({
isTest: false,
recordValues: () => {},
getValues: () => ({}),
});
function MeticulousProvider({ children }) {
const value = {
isTest: window.Meticulous?.isRunningAsTest ?? false,
recordValues: window.Meticulous?.recordCustomValues?.bind(window.Meticulous) ?? (() => {}),
getValues: window.Meticulous?.getCustomValues?.bind(window.Meticulous) ?? (() => ({})),
};
return (
<MeticulousContext.Provider value={value}>
{children}
</MeticulousContext.Provider>
);
}
// Usage
const { isTest } = useContext(MeticulousContext);
// Good
window.Meticulous?.isRunningAsTest
// Bad - throws error if Meticulous not loaded
window.Meticulous.isRunningAsTest
// Good - always resume
async function load() {
window.Meticulous?.pause?.();
try {
await fetchData();
} finally {
window.Meticulous?.resume?.();
}
}
// Bad - might not resume
async function load() {
window.Meticulous?.pause?.();
await fetchData();
window.Meticulous?.resume?.(); // Skipped if fetchData throws
}
// Good
if (window.Meticulous?.isRunningAsTest) {
const values = window.Meticulous.getCustomValues();
}
// Also good
const values = window.Meticulous?.getCustomValues?.();
// Record as soon as data is available
useEffect(() => {
if (userData && window.Meticulous?.recordCustomValues) {
window.Meticulous.recordCustomValues({ user: userData });
}
}, [userData]);
// Retrieve during component initialization
useEffect(() => {
if (window.Meticulous?.isRunningAsTest) {
const stored = window.Meticulous.getCustomValues();
if (stored?.user) {
setUser(stored.user);
}
}
}, []); // Empty deps - run once
Cause: Recorder not loaded
Solutions:
- Verify recorder script tag is in HTML
- Check script loads before app code
- Check for CSP blocking
Cause: Values not recorded or size limit exceeded
Solutions:
- Check
recordCustomValueswas called during recording - Verify value size < 1MB (prod) or 20MB (dev)
- Check
data-is-production-environmentsetting
Cause: resume() never called
Solutions:
- Use try/finally to ensure
resume()always runs - Check async callbacks complete
- Add timeout fallback
For full TypeScript type definitions:
interface Meticulous {
isRunningAsTest?: boolean;
recordCustomValues?: (values: Record<string, any>) => void;
getCustomValues?: () => Record<string, any> | undefined;
pause?: () => void;
resume?: () => void;
recordCustomEvent?: (event: { type: string; payload?: any }) => void;
onReplayCustomEvent?: (handler: (event: any) => void) => void;
}
declare global {
interface Window {
Meticulous?: Meticulous;
}
}
See TypeScript Types for importable types.
- Record Custom Values - Detailed custom values guide
- Custom Event API - Advanced event handling
- Handle File Uploads - File upload patterns