PolymarketPolymarketDeveloper22 min read2026-01-21

Polymarket JavaScript API Tutorial 2026 | Node.js & React Guide

AL - Founder of PolyTrack, Polymarket trader & analyst

AL

Founder of PolyTrack, Polymarket trader & analyst

Polymarket JavaScript API Tutorial 2026 | Node.js & React Guide - Developer Guide for Polymarket Traders | PolyTrack Blog

Build Polymarket trading bots, dashboards, and analytics tools using JavaScript and Node.js. With 5+ monthly searches for ws-subscriptions-clob.polymarket.com and growing interest in browser-based trading interfaces, this comprehensive 2026 tutorial covers everything from basic API calls with Fetch to real-time WebSocket connections, React hooks, error handling, and production-ready patterns used by top Polymarket applications.

Whether you're building a Polymarket trading bot in Node.js, creating a React dashboard, or integrating Polymarket data into a web application, this guide provides production-ready code examples. Professional development teams and quantitative firms integrate PolyTrack Pro API to eliminate weeks of custom infrastructure development, gaining immediate access to enterprise-grade whale tracking, real-time analytics, and sub-10-second refresh capabilities via RESTful APIs.

πŸ“š Quick Reference

  • β€’ Gamma API: https://gamma-api.polymarket.com (market data)
  • β€’ CLOB API: https://clob.polymarket.com (trading)
  • β€’ WebSocket: wss://ws-subscriptions-clob.polymarket.com/ws/market
  • β€’ No Auth Required: Gamma API is public, CLOB requires authentication
  • β€’ Full Tutorial: See our Python tutorial for py-clob-client examples

Getting Started: Environment Setup

Node.js vs Browser JavaScript

Polymarket APIs work in both Node.js and browser environments, but there are important differences:

FeatureNode.jsBrowser
Fetch APIβœ… Native (Node 18+)βœ… Native
WebSocketβœ… ws libraryβœ… Native WebSocket
Crypto signingβœ… ethers.js/web3.jsβœ… ethers.js/web3.js
Private keysβœ… Can store securely⚠️ Use MetaMask/Web3 wallets
Rate limitingβœ… Full control⚠️ Browser CORS limits

Installation (Node.js)

# For Node.js projects
npm install ethers axios ws

# Or with yarn
yarn add ethers axios ws

# For TypeScript support
npm install --save-dev @types/ws @types/node typescript

Browser Setup

For browser projects, use a bundler like Vite, Webpack, or include via CDN:

<!-- CDN approach (not recommended for production) -->
<script src="https://cdn.ethers.io/lib/ethers-5.7.2.umd.min.js"></script>

<!-- Or use npm with bundler -->
npm install ethers

πŸ’‘ Pro Tip: Skip Building Custom Trackers

Building custom wallet tracking with JavaScript takes weeks. PolyTrack Pro API provides unlimited wallet tracking with 10s refresh rates, cluster detection, and real-time alerts. Free tier limits you to 3 wallets with 30s refresh - upgrade to track unlimited wallets programmatically.

Fetching Market Data with Gamma API

The Gamma API provides read-only access to Polymarket market data. No authentication required, making it perfect for public dashboards and analytics.

Basic Fetch Example: Get Markets

// Simple fetch example
async function getMarkets(active = true, limit = 10) {
  try {
    const url = `https://gamma-api.polymarket.com/markets?active=${active}&_limit=${limit}`;
    const response = await fetch(url);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data.data || [];
  } catch (error) {
    console.error('Failed to fetch markets:', error);
    throw error;
  }
}

// Usage
const markets = await getMarkets(true, 20);
markets.forEach(market => {
  console.log(`${market.question}: ${market.volume}`);
});

Advanced: Fetch with Error Handling and Retry

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, {
        ...options,
        headers: {
          'Content-Type': 'application/json',
          ...options.headers
        }
      });

      if (response.status === 429) {
        // Rate limited - exponential backoff
        const waitTime = Math.pow(2, attempt) * 1000;
        console.log(`Rate limited. Waiting ${waitTime}ms...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      }

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.json();
    } catch (error) {
      if (attempt === maxRetries - 1) {
        throw error;
      }
      // Wait before retry
      await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
    }
  }
}

// Usage with retry
const markets = await fetchWithRetry(
  'https://gamma-api.polymarket.com/markets?active=true&_limit=10'
);

Parse JSON String Fields

Critical gotcha: Gamma API returns some fields as JSON strings, not arrays. Always parse them:

function parseMarketData(market) {
  return {
    ...market,
    // Parse JSON string fields
    outcome_prices: market.outcome_prices 
      ? JSON.parse(market.outcome_prices) 
      : [],
    outcomes: market.outcomes 
      ? JSON.parse(market.outcomes) 
      : [],
    clob_token_ids: market.clob_token_ids 
      ? JSON.parse(market.clob_token_ids) 
      : []
  };
}

// Usage
const markets = await getMarkets();
const parsedMarkets = markets.map(parseMarketData);

// Now you can use arrays
parsedMarkets.forEach(market => {
  console.log(`Prices: ${market.outcome_prices.join(', ')}`);
  console.log(`Outcomes: ${market.outcomes.join(' / ')}`);
});

Fetch Specific Market by Slug

async function getMarketBySlug(slug) {
  const url = `https://gamma-api.polymarket.com/markets?slug=${slug}`;
  const response = await fetch(url);
  const data = await response.json();
  
  if (data.data && data.data.length > 0) {
    return parseMarketData(data.data[0]);
  }
  
  return null;
}

// Usage
const market = await getMarketBySlug('will-trump-win-2024');
if (market) {
  console.log(`Market: ${market.question}`);
  console.log(`Token IDs: ${market.clob_token_ids.join(', ')}`);
}

See What Whales Are Trading Right Now

Get instant alerts when top traders make moves. Track P&L, win rates, and copy winning strategies.

Track Whales Free

Free forever. No credit card required.

Real-Time Data with WebSocket

For real-time orderbook updates and price changes, use Polymarket's WebSocket API. High search volume: ws-subscriptions-clob.polymarket.com (5+ monthly searches, 0% CTR).

πŸ’‘ Skip Building WebSocket Infrastructure

Managing WebSocket connections, reconnection logic, rate limiting, and error handling requires significant engineering effort. PolyTrack Pro provides real-time whale tracking with 10-second refresh rates, automatic reconnections, and robust error handlingβ€”all without writing a single WebSocket handler. Access unlimited wallet tracking via our JavaScript API or build on our battle-tested infrastructure.

Focus on trading strategy, not infrastructure. Try PolyTrack Pro free for 3 days - no credit card required.

Basic WebSocket Connection

class PolymarketWebSocket {
  constructor(tokenIds = []) {
    this.ws = null;
    this.tokenIds = tokenIds;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
  }

  connect() {
    const wsUrl = 'wss://ws-subscriptions-clob.polymarket.com/ws/market';
    this.ws = new WebSocket(wsUrl);

    this.ws.onopen = () => {
      console.log('WebSocket connected');
      this.reconnectAttempts = 0;
      
      // Subscribe to token updates
      if (this.tokenIds.length > 0) {
        this.subscribe(this.tokenIds);
      }
    };

    this.ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        this.handleMessage(data);
      } catch (error) {
        console.error('Failed to parse WebSocket message:', error);
      }
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };

    this.ws.onclose = () => {
      console.log('WebSocket closed');
      this.reconnect();
    };
  }

  subscribe(tokenIds) {
    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
      console.warn('WebSocket not connected');
      return;
    }

    this.ws.send(JSON.stringify({
      type: 'subscribe',
      assets_ids: tokenIds
    }));
  }

  handleMessage(data) {
    // Handle different message types
    if (data.type === 'subscribed') {
      console.log('Subscribed to:', data.channel);
    } else if (data.asset_id) {
      // Orderbook update
      console.log('Orderbook update:', {
        assetId: data.asset_id,
        bids: data.bids,
        asks: data.asks,
        timestamp: data.timestamp
      });
    }
  }

  reconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('Max reconnection attempts reached');
      return;
    }

    this.reconnectAttempts++;
    const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
    
    console.log(`Reconnecting in ${delay}ms... (attempt ${this.reconnectAttempts})`);
    
    setTimeout(() => {
      this.connect();
    }, delay);
  }

  disconnect() {
    if (this.ws) {
      this.ws.close();
    }
  }
}

// Usage
const ws = new PolymarketWebSocket(['0xabcd...', '0xef01...']);
ws.connect();

// Subscribe to additional tokens later
ws.subscribe(['0x1234...']);

WebSocket in Node.js (using ws library)

// npm install ws
import WebSocket from 'ws';

class NodePolymarketWebSocket {
  constructor(tokenIds = []) {
    this.ws = null;
    this.tokenIds = tokenIds;
  }

  connect() {
    const wsUrl = 'wss://ws-subscriptions-clob.polymarket.com/ws/market';
    this.ws = new WebSocket(wsUrl);

    this.ws.on('open', () => {
      console.log('Connected to Polymarket WebSocket');
      if (this.tokenIds.length > 0) {
        this.subscribe(this.tokenIds);
      }
    });

    this.ws.on('message', (data) => {
      try {
        const message = JSON.parse(data.toString());
        this.handleMessage(message);
      } catch (error) {
        console.error('Parse error:', error);
      }
    });

    this.ws.on('error', (error) => {
      console.error('WebSocket error:', error);
    });

    this.ws.on('close', () => {
      console.log('WebSocket closed, reconnecting...');
      setTimeout(() => this.connect(), 5000);
    });
  }

  subscribe(tokenIds) {
    this.ws.send(JSON.stringify({
      type: 'subscribe',
      assets_ids: tokenIds
    }));
  }

  handleMessage(data) {
    // Process orderbook updates
    if (data.asset_id && data.bids && data.asks) {
      console.log(`${data.asset_id}: Bid ${data.bids[0]?.price} / Ask ${data.asks[0]?.price}`);
    }
  }
}

// Usage
const ws = new NodePolymarketWebSocket(['0xabcd...']);
ws.connect();

React Integration: Custom Hooks

React hooks make it easy to fetch and display Polymarket data in your components. Here are production-ready patterns:

usePolymarketMarkets Hook

import { useState, useEffect, useCallback } from 'react';

export function usePolymarketMarkets(options = {}) {
  const {
    active = true,
    questionContains = null,
    limit = 10,
    pollInterval = 30000 // 30 seconds
  } = options;

  const [markets, setMarkets] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  const fetchMarkets = useCallback(async () => {
    try {
      setIsLoading(true);
      setError(null);

      const params = new URLSearchParams({
        active: active.toString(),
        _limit: limit.toString()
      });

      if (questionContains) {
        params.append('question_contains', questionContains);
      }

      const url = `https://gamma-api.polymarket.com/markets?${params}`;
      const response = await fetch(url);

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      const data = await response.json();
      const marketsData = (data.data || []).map(market => ({
        ...market,
        outcome_prices: market.outcome_prices 
          ? JSON.parse(market.outcome_prices) 
          : [],
        outcomes: market.outcomes 
          ? JSON.parse(market.outcomes) 
          : []
      }));

      setMarkets(marketsData);
    } catch (err) {
      setError(err.message);
      console.error('Failed to fetch markets:', err);
    } finally {
      setIsLoading(false);
    }
  }, [active, questionContains, limit]);

  // Initial fetch
  useEffect(() => {
    fetchMarkets();
  }, [fetchMarkets]);

  // Polling
  useEffect(() => {
    if (pollInterval <= 0) return;

    const interval = setInterval(fetchMarkets, pollInterval);
    return () => clearInterval(interval);
  }, [fetchMarkets, pollInterval]);

  return {
    markets,
    isLoading,
    error,
    refetch: fetchMarkets
  };
}

// Usage in component
function MarketsList() {
  const { markets, isLoading, error } = usePolymarketMarkets({
    active: true,
    questionContains: 'bitcoin',
    limit: 20,
    pollInterval: 30000 // Refresh every 30 seconds
  });

  if (isLoading) return <div>Loading markets...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      {markets.map(market => (
        <div key={market.condition_id}>
          <h3>{market.question}</h3>
          <p>Volume: {'$'}{market.volume}</p>
        </div>
      ))}
    </div>
  );
}

usePolymarketPrice Hook (Real-Time)

import { useState, useEffect, useRef } from 'react';

export function usePolymarketPrice(tokenId, enabled = true) {
  const [price, setPrice] = useState(null);
  const [orderbook, setOrderbook] = useState(null);
  const wsRef = useRef(null);

  useEffect(() => {
    if (!enabled || !tokenId) return;

    const wsUrl = 'wss://ws-subscriptions-clob.polymarket.com/ws/market';
    wsRef.current = new WebSocket(wsUrl);

    wsRef.current.onopen = () => {
      wsRef.current.send(JSON.stringify({
        type: 'subscribe',
        assets_ids: [tokenId]
      }));
    };

    wsRef.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      
      if (data.asset_id === tokenId && data.bids && data.asks) {
        const bestBid = data.bids[0]?.price;
        const bestAsk = data.asks[0]?.price;
        const mid = bestBid && bestAsk 
          ? (parseFloat(bestBid) + parseFloat(bestAsk)) / 2 
          : null;

        setPrice({
          bid: bestBid,
          ask: bestAsk,
          mid: mid
        });
        setOrderbook({
          bids: data.bids,
          asks: data.asks
        });
      }
    };

    return () => {
      if (wsRef.current) {
        wsRef.current.close();
      }
    };
  }, [tokenId, enabled]);

  return { price, orderbook };
}

// Usage
function PriceDisplay({ tokenId }) {
  const { price, orderbook } = usePolymarketPrice(tokenId);

  if (!price) return <div>Loading price...</div>;

  return (
    <div>
      <p>Bid: {price.bid}</p>
      <p>Ask: {price.ask}</p>
      <p>Mid: {price.mid}</p>
    </div>
  );
}

CLOB API: Placing Orders

The CLOB API handles order placement and requires authentication. You'll need to sign orders using EIP-712 signatures. See our complete CLOB API reference for detailed authentication.

Get Market Price

// Real example from production codebase
const CLOB_API = 'https://clob.polymarket.com';

async function getMarketPrice(tokenId) {
  try {
    const res = await fetch(`${CLOB_API}/price?token_id=${tokenId}`);
    if (!res.ok) return null;
    
    const data = await res.json();
    return {
      bid: parseFloat(data.bid || 0),
      ask: parseFloat(data.ask || 0),
      mid: parseFloat(data.mid || 0)
    };
  } catch (err) {
    console.error('Failed to get market price:', err);
    return null;
  }
}

// Usage
const price = await getMarketPrice('0xabcd...');
console.log(`Bid: $${price.bid}, Ask: $${price.ask}, Mid: $${price.mid}`);

Get Orderbook

async function getOrderbook(tokenId) {
  try {
    const res = await fetch(`${CLOB_API}/book?token_id=${tokenId}`);
    if (!res.ok) return null;
    
    const orderbook = await res.json();
    return {
      bids: orderbook.bids || [],
      asks: orderbook.asks || [],
      hash: orderbook.hash
    };
  } catch (err) {
    console.error('Failed to get orderbook:', err);
    return null;
  }
}

// Usage
const book = await getOrderbook('0xabcd...');
console.log('Top 5 bids:', book.bids.slice(0, 5));
console.log('Top 5 asks:', book.asks.slice(0, 5));

Calculate Limit Price with Slippage

// Real function from production codebase
function calculateLimitPrice(side, currentPrice, slippage = 0.02) {
  if (side === 'BUY') {
    // For buys, we're willing to pay a bit more
    return Math.min(currentPrice * (1 + slippage), 0.99);
  } else {
    // For sells, we're willing to receive a bit less
    return Math.max(currentPrice * (1 - slippage), 0.01);
  }
}

// Usage
const currentPrice = 0.45;
const buyPrice = calculateLimitPrice('BUY', currentPrice, 0.02); // 0.459
const sellPrice = calculateLimitPrice('SELL', currentPrice, 0.02); // 0.441

Error Handling Best Practices

Comprehensive Error Handler

class PolymarketAPIError extends Error {
  constructor(message, status, data) {
    super(message);
    this.name = 'PolymarketAPIError';
    this.status = status;
    this.data = data;
  }
}

async function safeFetch(url, options = {}) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      }
    });

    const data = await response.json().catch(() => ({}));

    if (!response.ok) {
      // Handle specific error codes
      if (response.status === 429) {
        throw new PolymarketAPIError(
          'Rate limit exceeded. Please slow down requests.',
          429,
          data
        );
      } else if (response.status === 404) {
        throw new PolymarketAPIError(
          'Resource not found. Check your parameters.',
          404,
          data
        );
      } else if (response.status >= 500) {
        throw new PolymarketAPIError(
          'Server error. Please try again later.',
          response.status,
          data
        );
      } else {
        throw new PolymarketAPIError(
          data.message || `HTTP ${response.status}`,
          response.status,
          data
        );
      }
    }

    return data;
  } catch (error) {
    if (error instanceof PolymarketAPIError) {
      throw error;
    }
    // Network errors
    throw new PolymarketAPIError(
      `Network error: ${error.message}`,
      null,
      null
    );
  }
}

// Usage with error handling
try {
  const markets = await safeFetch('https://gamma-api.polymarket.com/markets');
} catch (error) {
  if (error instanceof PolymarketAPIError) {
    console.error(`API Error [${error.status}]: ${error.message}`);
  } else {
    console.error('Unexpected error:', error);
  }
}

Complete Example: Market Dashboard

Here's a complete React component that fetches and displays Polymarket markets with real-time updates:

import React, { useState, useEffect } from 'react';
import { usePolymarketMarkets } from './hooks/usePolymarketMarkets';
import { usePolymarketPrice } from './hooks/usePolymarketPrice';

function MarketDashboard() {
  const [selectedToken, setSelectedToken] = useState(null);
  const { markets, isLoading, error } = usePolymarketMarkets({
    active: true,
    limit: 20,
    pollInterval: 30000
  });

  const { price, orderbook } = usePolymarketPrice(selectedToken, !!selectedToken);

  if (isLoading) {
    return <div className="loading">Loading markets...</div>;
  }

  if (error) {
    return <div className="error">Error: {error}</div>;
  }

  return (
    <div className="dashboard">
      <h1>Polymarket Markets</h1>
      
      <div className="markets-list">
        {markets.map(market => {
          const tokenId = market.clob_token_ids?.[0];
          return (
            <div 
              key={market.condition_id}
              className="market-card"
              onClick={() => setSelectedToken(tokenId)}
            >
              <h3>{market.question}</h3>
              <p>Volume: {'$'}{market.volume}</p>
              <p>Liquidity: {market.liquidity}</p>
              {market.outcome_prices && (
                <p>
                  Prices: {market.outcome_prices.map((p, i) => (
                    <span key={i}>
                      {market.outcomes[i]}: {(p * 100).toFixed(1)}%
                    </span>
                  ))}
                </p>
              )}
            </div>
          );
        })}
      </div>

      {selectedToken && price && (
        <div className="price-panel">
          <h2>Real-Time Price</h2>
          <p>Bid: {'{'}price.bid{'}'}</p>
          <p>Ask: {'}price.ask{'}</p>
          <p>Mid: {'}price.mid{'}</p>
          
          {orderbook && (
            <div>
              <h3>Orderbook</h3>
              <div>
                <h4>Bids</h4>
                {orderbook.bids.slice(0, 5).map((bid, i) => (
                  <div key={i}>
                    {bid.price} x {bid.size}
                  </div>
                ))}
              </div>
              <div>
                <h4>Asks</h4>
                {orderbook.asks.slice(0, 5).map((ask, i) => (
                  <div key={i}>
                    {'}ask.price{'} x {ask.size}
                  </div>
                ))}
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

export default MarketDashboard;

Performance Optimization

Request Batching

// Batch multiple requests
async function batchFetchMarkets(slugs) {
  const promises = slugs.map(slug => 
    fetch(`https://gamma-api.polymarket.com/markets?slug=${slug}`)
      .then(res => res.json())
      .then(data => data.data?.[0] || null)
  );

  const results = await Promise.allSettled(promises);
  return results
    .filter(r => r.status === 'fulfilled' && r.value)
    .map(r => r.value);
}

// Usage
const markets = await batchFetchMarkets([
  'will-trump-win-2024',
  'bitcoin-100k-2025',
  'ethereum-5000-2025'
]);

Caching Strategy

// Simple in-memory cache
const cache = new Map();

function getCachedOrFetch(key, fetchFn, ttl = 60000) {
  const cached = cache.get(key);
  
  if (cached && Date.now() - cached.timestamp < ttl) {
    return Promise.resolve(cached.data);
  }

  return fetchFn().then(data => {
    cache.set(key, {
      data,
      timestamp: Date.now()
    });
    return data;
  });
}

// Usage
const markets = await getCachedOrFetch(
  'active-markets',
  () => fetch('https://gamma-api.polymarket.com/markets?active=true')
    .then(res => res.json()),
  30000 // 30 second cache
);

Troubleshooting Common Issues

CORS Errors in Browser

Polymarket APIs support CORS, but if you encounter issues:

  • Use a proxy server for development
  • Use Node.js backend to make API calls
  • Check browser console for specific CORS error messages

WebSocket Connection Drops

WebSocket connections can drop due to network issues. Always implement reconnection logic (see examples above).

Rate Limiting

Gamma API has ~100 requests/minute limits. Implement exponential backoff and request queuing for high-frequency applications.

Next Steps

Now that you understand JavaScript integration with Polymarket APIs:

πŸš€ Enterprise-Grade Wallet Tracking API

Building production-ready wallet tracking infrastructure with JavaScript requires weeks of development and ongoing maintenance. PolyTrack Pro API delivers institutional-quality capabilities:

  • Unlimited wallet tracking via REST API (free tier: only 3 wallets)
  • 10s refresh rates for real-time data (free tier: 30s)
  • Cluster detection - identify multi-wallet whales programmatically
  • Dev Picks access - get curated profitable wallets via API
  • CSV export - download all data for your analysis
  • Real-time alerts - webhook support for price movements

Try PolyTrack Pro free for 3 days and integrate whale tracking into your JavaScript apps without building from scratch.

See What Whales Are Trading Right Now

Get instant alerts when top traders make moves. Track P&L, win rates, and copy winning strategies.

Track Whales Free

Free forever. No credit card required.

Frequently Asked Questions

Use the WebSocket API: wss://ws-subscriptions-clob.polymarket.com/ws/market. Send a subscribe message with assets_ids array to receive real-time orderbook updates. See our complete WebSocket example in the guide.

12,400+ TRADERS

Stop Guessing. Start Following Smart Money.

Get instant alerts when whales make $10K+ trades. Track P&L, win rates, and copy winning strategies.

Track Whales FreeNo credit card required