Content is user-generated and unverified.

Interactive Artifacts Integration & External Services Guide

A comprehensive developer reference for integrating external services, APIs, and data persistence in Claude Interactive Artifacts

Table of Contents

  1. Architecture Overview
  2. Critical Constraints
  3. External Script Imports
  4. API Integrations
  5. Database Integration (Supabase)
  6. File Operations & Downloads
  7. Webhook Integration
  8. State Management
  9. Security Best Practices
  10. Common Use Cases
  11. Troubleshooting

Architecture Overview

Interactive Artifacts operate as React components in a sandboxed iframe environment. Think of them as "serverless frontends" with the following characteristics:

  • Stateless between sessions - no persistent storage
  • External service dependent - data persistence requires cloud services
  • CDN-restricted - only specific external libraries allowed
  • Modern SPA pattern - similar to create-react-app with constraints

Mental Model for Experienced Developers

If you're coming from traditional server-side development, adjust your thinking:

Traditional: Frontend ↔ Your Backend ↔ Database
Artifacts:   React Component ↔ Cloud Services (APIs/Supabase/Webhooks)

Critical Constraints

❌ Prohibited APIs

Browser Storage APIs are COMPLETELY BLOCKED:

javascript
// ❌ These will cause artifacts to FAIL
localStorage.setItem('data', value);
sessionStorage.setItem('data', value);
indexedDB.open('database');

No Direct File System Access:

  • Cannot write files to disk
  • Cannot read local files (except uploads via window.fs.readFile)
  • No server-side file operations

✅ What IS Supported

  • Standard fetch() API for HTTP requests
  • React state management (useState, useReducer)
  • External CDN library imports (cdnjs.cloudflare.com only)
  • File downloads via Blob URLs
  • Claude API access via window.claude.complete

External Script Imports

CDN Restriction Explained

ONLY https://cdnjs.cloudflare.com is whitelisted due to Content Security Policy (CSP) restrictions. This is much more limiting than normal web development.

Finding Available Libraries

  1. Search: Visit https://cdnjs.cloudflare.com
  2. Popular Available Libraries:
    • Visualization: Chart.js, D3.js, Three.js, Plotly.js
    • Utilities: Lodash, Moment.js, Axios
    • Crypto: CryptoJS
    • Data: Papa Parse (CSV), JSZip
    • Real-time: Socket.io, Pusher

Import Patterns

HTML Artifacts:

html
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.0/axios.min.js"></script>

React Artifacts - Dynamic Loading:

javascript
const loadLibrary = async (libraryName, version, globalName) => {
  return new Promise((resolve, reject) => {
    if (window[globalName]) {
      resolve(window[globalName]);
      return;
    }
    
    const script = document.createElement('script');
    script.src = `https://cdnjs.cloudflare.com/ajax/libs/${libraryName}/${version}/${libraryName}.min.js`;
    script.onload = () => resolve(window[globalName]);
    script.onerror = reject;
    document.head.appendChild(script);
  });
};

// Usage
const Chart = await loadLibrary('chart.js', '4.4.0', 'Chart');

Version Pinning Best Practices

javascript
// ✅ Always use specific versions
https://cdnjs.cloudflare.com/ajax/libs/chart.js/4.4.0/chart.min.js

// ❌ Never use 'latest' - will break when updated
https://cdnjs.cloudflare.com/ajax/libs/chart.js/latest/chart.min.js

Workarounds for Missing Libraries

javascript
// Option 1: Inline simple utilities
const utils = {
  uuid: () => Date.now().toString(36) + Math.random().toString(36).substr(2),
  debounce: (func, wait) => {
    let timeout;
    return (...args) => {
      clearTimeout(timeout);
      timeout = setTimeout(() => func.apply(this, args), wait);
    };
  }
};

// Option 2: Use available alternatives
// Instead of: react-query → use fetch with useState
// Instead of: styled-components → use Tailwind classes
// Instead of: react-router → use conditional rendering

API Integrations

Standard REST API Calls

javascript
const apiCall = async (endpoint, data, options = {}) => {
  const defaultOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${options.apiKey || ''}`
    },
    body: JSON.stringify(data)
  };

  try {
    const response = await fetch(endpoint, { ...defaultOptions, ...options });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('API call failed:', error);
    throw error;
  }
};

Error Handling Pattern

javascript
const robustApiCall = async (url, data, retries = 3) => {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
        signal: AbortSignal.timeout(10000) // 10s timeout
      });
      
      if (response.ok) return await response.json();
      throw new Error(`HTTP ${response.status}`);
      
    } catch (error) {
      console.warn(`API attempt ${i + 1} failed:`, error.message);
      if (i === retries - 1) throw error;
      
      // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
    }
  }
};

Database Integration (Supabase)

Setup and Import

javascript
// Load Supabase from CDN
const loadSupabase = async () => {
  if (window.supabase) return window.supabase;
  
  const script = document.createElement('script');
  script.src = 'https://cdnjs.cloudflare.com/ajax/libs/supabase/2.38.0/supabase.min.js';
  document.head.appendChild(script);
  
  return new Promise(resolve => {
    script.onload = () => resolve(window.supabase);
  });
};

// Initialize client
const supabaseUrl = 'https://your-project.supabase.co';
const supabaseAnonKey = 'your-anon-key';
const { createClient } = await loadSupabase();
const supabase = createClient(supabaseUrl, supabaseAnonKey);

CRUD Operations

javascript
// Create
const saveQuizResult = async (result) => {
  const { data, error } = await supabase
    .from('quiz_results')
    .insert({
      user_id: result.userId,
      quiz_name: result.quizName,
      score: result.score,
      total_questions: result.totalQuestions,
      answers: result.answers,
      completed_at: new Date().toISOString()
    });
    
  if (error) throw error;
  return data;
};

// Read
const getQuizResults = async (userId) => {
  const { data, error } = await supabase
    .from('quiz_results')
    .select('*')
    .eq('user_id', userId)
    .order('completed_at', { ascending: false });
    
  if (error) throw error;
  return data;
};

// Update
const updateQuizResult = async (id, updates) => {
  const { data, error } = await supabase
    .from('quiz_results')
    .update(updates)
    .eq('id', id);
    
  if (error) throw error;
  return data;
};

Real-time Subscriptions

javascript
const subscribeToQuizResults = (userId, callback) => {
  return supabase
    .channel('quiz_results')
    .on('postgres_changes', {
      event: 'INSERT',
      schema: 'public',
      table: 'quiz_results',
      filter: `user_id=eq.${userId}`
    }, callback)
    .subscribe();
};

File Operations & Downloads

Download Functionality

javascript
const downloadFile = (data, filename, type = 'application/json') => {
  const blob = new Blob([data], { type });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
};

// Usage examples
const downloadQuizResults = (results) => {
  // JSON download
  downloadFile(
    JSON.stringify(results, null, 2),
    'quiz-results.json',
    'application/json'
  );
  
  // CSV download
  const csvData = convertToCSV(results);
  downloadFile(csvData, 'quiz-results.csv', 'text/csv');
  
  // PDF report (requires external API)
  generatePDFReport(results).then(pdfBlob => {
    downloadFile(pdfBlob, 'quiz-report.pdf', 'application/pdf');
  });
};

CSV Generation

javascript
const convertToCSV = (data) => {
  if (!data.length) return '';
  
  const headers = Object.keys(data[0]);
  const csvHeaders = headers.join(',');
  const csvRows = data.map(row => 
    headers.map(header => {
      const value = row[header];
      // Escape commas and quotes
      return typeof value === 'string' && (value.includes(',') || value.includes('"'))
        ? `"${value.replace(/"/g, '""')}"` 
        : value;
    }).join(',')
  );
  
  return [csvHeaders, ...csvRows].join('\n');
};

Webhook Integration

Understanding Webhooks

Webhooks are HTTP POST endpoints that trigger external workflows. They enable integration with hundreds of services without building backend infrastructure.

Popular Webhook Services

Zapier Integration

javascript
const sendToZapier = async (webhookId, data) => {
  const webhookUrl = `https://hooks.zapier.com/hooks/catch/${webhookId}/`;
  
  try {
    const response = await fetch(webhookUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    
    if (!response.ok) throw new Error(`Zapier webhook failed: ${response.status}`);
    return true;
  } catch (error) {
    console.error('Zapier integration failed:', error);
    return false;
  }
};

// Quiz results to Google Sheets via Zapier
const saveToGoogleSheets = async (quizResults) => {
  return await sendToZapier('XXXXX/YYYYY', {
    timestamp: new Date().toISOString(),
    user_name: quizResults.userName,
    user_email: quizResults.userEmail,
    quiz_name: quizResults.quizName,
    score: quizResults.score,
    total_questions: quizResults.totalQuestions,
    percentage: Math.round((quizResults.score / quizResults.totalQuestions) * 100),
    time_taken_minutes: quizResults.timeTaken,
    answers_json: JSON.stringify(quizResults.answers)
  });
};

Make.com (Integromat) Integration

javascript
const sendToMake = async (webhookId, data) => {
  const webhookUrl = `https://hook.eu1.make.com/${webhookId}`;
  
  const response = await fetch(webhookUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  
  return response.ok;
};

Discord Integration

javascript
const sendToDiscord = async (webhookUrl, quizResults) => {
  const embed = {
    title: '🎯 Quiz Completed!',
    description: `**${quizResults.userName}** completed **${quizResults.quizName}**`,
    color: quizResults.percentage >= 80 ? 0x00ff00 : quizResults.percentage >= 60 ? 0xff9900 : 0xff0000,
    fields: [
      { name: 'Score', value: `${quizResults.score}/${quizResults.totalQuestions}`, inline: true },
      { name: 'Percentage', value: `${quizResults.percentage}%`, inline: true },
      { name: 'Time Taken', value: `${quizResults.timeTaken} minutes`, inline: true }
    ],
    timestamp: new Date().toISOString()
  };
  
  return await fetch(webhookUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ embeds: [embed] })
  });
};

Advanced Webhook Patterns

Multiple Service Integration

javascript
const distributeResults = async (quizResults) => {
  const integrations = [
    () => saveToGoogleSheets(quizResults),
    () => sendToDiscord(process.env.DISCORD_WEBHOOK, quizResults),
    () => updateCRM(quizResults),
    () => sendEmailNotification(quizResults)
  ];
  
  const results = await Promise.allSettled(integrations.map(fn => fn()));
  
  const failed = results
    .map((result, index) => ({ result, index }))
    .filter(({ result }) => result.status === 'rejected');
    
  if (failed.length > 0) {
    console.warn('Some integrations failed:', failed);
    // Fallback to download
    downloadFile(JSON.stringify(quizResults, null, 2), 'quiz-results-backup.json');
  }
  
  return results;
};

Webhook with Retry Logic

javascript
const robustWebhook = async (url, data, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
        signal: AbortSignal.timeout(10000)
      });
      
      if (response.ok) return { success: true, attempt };
      
      if (response.status >= 400 && response.status < 500) {
        // Client error - don't retry
        throw new Error(`Client error ${response.status}: ${response.statusText}`);
      }
      
      // Server error - retry
      throw new Error(`Server error ${response.status}: ${response.statusText}`);
      
    } catch (error) {
      console.warn(`Webhook attempt ${attempt}/${maxRetries} failed:`, error.message);
      
      if (attempt === maxRetries) {
        return { success: false, error: error.message };
      }
      
      // Exponential backoff: 1s, 2s, 4s
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt - 1) * 1000));
    }
  }
};

State Management

React State Patterns

javascript
// Simple state for small apps
const [quizState, setQuizState] = useState({
  currentQuestion: 0,
  answers: [],
  startTime: null,
  isCompleted: false
});

// Complex state with useReducer
const quizReducer = (state, action) => {
  switch (action.type) {
    case 'START_QUIZ':
      return { ...state, startTime: Date.now(), isStarted: true };
    case 'ANSWER_QUESTION':
      return {
        ...state,
        answers: [...state.answers, action.answer],
        currentQuestion: state.currentQuestion + 1
      };
    case 'COMPLETE_QUIZ':
      return { ...state, isCompleted: true, endTime: Date.now() };
    default:
      return state;
  }
};

const [quizState, dispatch] = useReducer(quizReducer, initialState);

Context for Sharing State

javascript
const QuizContext = createContext();

const QuizProvider = ({ children }) => {
  const [state, dispatch] = useReducer(quizReducer, initialState);
  const [apiStatus, setApiStatus] = useState('idle');
  
  const submitResults = async (results) => {
    setApiStatus('loading');
    try {
      await saveToSupabase(results);
      await sendToZapier('webhook-id', results);
      setApiStatus('success');
    } catch (error) {
      setApiStatus('error');
      // Fallback to download
      downloadFile(JSON.stringify(results, null, 2), 'quiz-backup.json');
    }
  };
  
  return (
    <QuizContext.Provider value={{ state, dispatch, submitResults, apiStatus }}>
      {children}
    </QuizContext.Provider>
  );
};

Security Best Practices

API Key Management

javascript
// ❌ Never expose sensitive keys in frontend
const OPENAI_API_KEY = 'sk-abcd1234...'; // Visible to all users!

// ✅ Use environment-specific configuration
const getConfig = (environment = 'production') => {
  const configs = {
    development: {
      supabaseUrl: 'https://dev-project.supabase.co',
      zapierWebhook: 'https://hooks.zapier.com/hooks/catch/DEV_ID/'
    },
    production: {
      supabaseUrl: 'https://prod-project.supabase.co',
      zapierWebhook: 'https://hooks.zapier.com/hooks/catch/PROD_ID/'
    }
  };
  return configs[environment] || configs.production;
};

// ✅ Use proxy services for sensitive operations
const callOpenAIViaSupabase = async (prompt) => {
  // Call Supabase Edge Function that handles OpenAI API securely
  const { data } = await supabase.functions.invoke('openai-proxy', {
    body: { prompt }
  });
  return data;
};

Input Validation

javascript
const validateQuizInput = (input) => {
  const errors = [];
  
  if (!input.userName || input.userName.trim().length < 2) {
    errors.push('Name must be at least 2 characters');
  }
  
  if (input.userEmail && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.userEmail)) {
    errors.push('Invalid email format');
  }
  
  if (!input.answers || !Array.isArray(input.answers)) {
    errors.push('Answers must be provided');
  }
  
  return errors;
};

Data Sanitization

javascript
const sanitizeUserInput = (input) => {
  if (typeof input !== 'string') return input;
  
  return input
    .trim()
    .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove scripts
    .replace(/javascript:/gi, '') // Remove javascript: protocols
    .slice(0, 1000); // Limit length
};

Common Use Cases

Quiz Application Architecture

javascript
const QuizApp = () => {
  const [quizState, setQuizState] = useState({
    questions: [], // Embedded or loaded from Supabase
    currentQuestion: 0,
    answers: [],
    startTime: null,
    endTime: null,
    userInfo: {}
  });
  
  const submitQuiz = async () => {
    const results = {
      ...quizState.userInfo,
      score: calculateScore(quizState.answers),
      totalQuestions: quizState.questions.length,
      timeTaken: (quizState.endTime - quizState.startTime) / 1000 / 60, // minutes
      answers: quizState.answers
    };
    
    // Multiple persistence strategies
    const strategies = [
      () => saveToSupabase(results),
      () => sendToZapier('webhook-id', results),
      () => downloadFile(JSON.stringify(results, null, 2), 'quiz-results.json')
    ];
    
    // Try each strategy, continue on failure
    for (const strategy of strategies) {
      try {
        await strategy();
        console.log('Results saved successfully');
        break;
      } catch (error) {
        console.warn('Strategy failed, trying next:', error);
      }
    }
  };
  
  return (
    <div className="quiz-container">
      {/* Quiz UI components */}
    </div>
  );
};

Data Dashboard with External APIs

javascript
const DataDashboard = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        // Combine multiple data sources
        const [quizResults, apiData, webhookStatus] = await Promise.allSettled([
          supabase.from('quiz_results').select('*'),
          fetch('https://api.external-service.com/stats').then(r => r.json()),
          fetch('https://status.zapier.com/api/v2/summary.json').then(r => r.json())
        ]);
        
        setData({
          quizResults: quizResults.value?.data || [],
          apiData: apiData.value || {},
          webhookStatus: webhookStatus.value || {}
        });
      } catch (error) {
        console.error('Failed to load dashboard data:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, []);
  
  return (
    <div className="dashboard">
      {loading ? <LoadingSpinner /> : <DataVisualization data={data} />}
    </div>
  );
};

File Processing with External Services

javascript
const FileProcessor = () => {
  const [results, setResults] = useState([]);
  
  const processFile = async (file) => {
    try {
      // Read uploaded file
      const content = await window.fs.readFile(file.name, { encoding: 'utf8' });
      
      // Process with external API
      const processedData = await fetch('https://api.processing-service.com/analyze', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content })
      }).then(r => r.json());
      
      // Save results
      await saveToSupabase(processedData);
      
      // Trigger webhook for notifications
      await sendToZapier('processing-complete', {
        filename: file.name,
        results: processedData,
        timestamp: new Date().toISOString()
      });
      
      setResults(prev => [...prev, processedData]);
      
    } catch (error) {
      console.error('File processing failed:', error);
      // Fallback: download original file with error log
      downloadFile(
        JSON.stringify({ error: error.message, filename: file.name }), 
        'processing-error.json'
      );
    }
  };
  
  return (
    <div className="file-processor">
      <input type="file" onChange={(e) => processFile(e.target.files[0])} />
      <ResultsList results={results} />
    </div>
  );
};

Troubleshooting

Common Issues and Solutions

CDN Library Loading Failures

javascript
// Problem: Library not loading
const loadLibraryWithFallback = async (primary, fallback) => {
  try {
    return await loadLibrary(primary.name, primary.version, primary.global);
  } catch (error) {
    console.warn('Primary library failed, trying fallback:', error);
    return await loadLibrary(fallback.name, fallback.version, fallback.global);
  }
};

// Usage
const chart = await loadLibraryWithFallback(
  { name: 'chart.js', version: '4.4.0', global: 'Chart' },
  { name: 'chart.js', version: '3.9.1', global: 'Chart' }
);

API CORS Issues

javascript
// Problem: CORS blocking API calls
// Solution: Use proxy services or CORS-enabled endpoints

const corsProxyCall = async (url, options) => {
  // Use public CORS proxy (not for production)
  const proxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`;
  
  // Better: Use your own proxy service
  const betterProxyUrl = `https://your-proxy.vercel.app/api/proxy?url=${encodeURIComponent(url)}`;
  
  return fetch(betterProxyUrl, options);
};

Webhook Failures

javascript
// Problem: Webhooks failing silently
const debugWebhook = async (url, data) => {
  console.log('Webhook URL:', url);
  console.log('Payload:', JSON.stringify(data, null, 2));
  
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    
    console.log('Response status:', response.status);
    console.log('Response headers:', [...response.headers.entries()]);
    
    const responseText = await response.text();
    console.log('Response body:', responseText);
    
    return response.ok;
  } catch (error) {
    console.error('Webhook debug error:', error);
    return false;
  }
};

State Management Issues

javascript
// Problem: State not updating correctly
const useDebugState = (initialState, label) => {
  const [state, setState] = useState(initialState);
  
  useEffect(() => {
    console.log(`${label} state changed:`, state);
  }, [state, label]);
  
  const debugSetState = useCallback((newState) => {
    console.log(`${label} state update:`, { from: state, to: newState });
    setState(newState);
  }, [state, label]);
  
  return [state, debugSetState];
};

Performance Optimization

javascript
// Debounce API calls
const useDebouncedApiCall = (apiFunction, delay = 500) => {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  const debouncedCall = useCallback(
    debounce(async (...args) => {
      setLoading(true);
      setError(null);
      try {
        const result = await apiFunction(...args);
        return result;
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    }, delay),
    [apiFunction, delay]
  );
  
  return { call: debouncedCall, loading, error };
};

// Batch operations
const batchApiCalls = async (calls, batchSize = 3) => {
  const results = [];
  for (let i = 0; i < calls.length; i += batchSize) {
    const batch = calls.slice(i, i + batchSize);
    const batchResults = await Promise.allSettled(batch.map(call => call()));
    results.push(...batchResults);
    
    // Rate limiting delay
    if (i + batchSize < calls.length) {
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
  return results;
};

Summary

Interactive Artifacts provide a powerful platform for building data-driven applications with external service integration. Key takeaways:

  1. Think "serverless frontend" - embrace stateless architecture
  2. Use external services for persistence (Supabase, APIs, webhooks)
  3. CDN libraries only - plan around cdnjs.cloudflare.com availability
  4. Multiple persistence strategies - always have fallbacks
  5. Security first - never expose sensitive keys in frontend code

This guide covers the fundamental patterns you'll need for most integration scenarios. The combination of React state management, external APIs, and webhook integrations enables building sophisticated applications without traditional backend infrastructure.


Last updated: July 2025 For questions or contributions, refer to the Claude Interactive Artifacts documentation.

Content is user-generated and unverified.
    Interactive Artifacts Integration & External Services Guide | Claude