# How to Integrate Bolt.new with Meetup

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 20 minutes
- Last updated: April 2026

## TL;DR

Integrate Meetup with Bolt.new using Meetup's GraphQL API through a Next.js API route. Meetup migrated from REST to GraphQL in 2021 — fetch events by location or topic using GraphQL queries, display group details and RSVP counts, and generate event calendar views. Meetup migrated to OAuth 2.0 for authentication; Meetup Pro subscription is required for managing events via API. Basic event search is accessible on free accounts.

## Build a Meetup Event Discovery App with Bolt.new

Meetup connects over 40 million members with local communities through in-person and virtual events across virtually every interest category — technology meetups, running clubs, language exchanges, board game nights, and professional networking events. The platform is particularly valuable for tech and startup communities, where Meetup groups provide regular networking and learning opportunities. For applications serving local communities, event discovery, or professional networking, integrating Meetup data can surface high-value real-world events to your users.

Meetup migrated from a traditional REST API to GraphQL in 2021. This means all data queries are sent as GraphQL query strings to a single endpoint (https://api.meetup.com/gql) rather than multiple REST endpoints. The GraphQL approach is more flexible — you request exactly the fields you need for each component — but requires familiarity with GraphQL query syntax. The Meetup API supports querying events by location radius, keyword, topic category, date range, and group URL name.

Authentication uses OAuth 2.0 with a standard authorization code flow. For public event search (finding events near a location without user-specific data), some Meetup API queries work with an OAuth client credentials token or even a consumer key. For user-specific operations — managing RSVPs, creating events (Pro accounts), or accessing a user's group memberships — users must authorize through the three-legged OAuth flow. All API calls must be proxied through Next.js API routes to avoid CORS errors in the browser.

## Before you start

- A Meetup account at https://www.meetup.com (free account supports basic API access)
- A Meetup OAuth Consumer Key and Secret — register at https://www.meetup.com/api/oauth/list/ (requires clicking 'Register a new OAuth Consumer')
- For RSVP management and event creation: a Meetup Pro subscription ($29.99/month per group)
- A Bolt.new account with a Next.js project open
- Basic familiarity with GraphQL query syntax

## Step-by-step guide

### 1. Register a Meetup OAuth Consumer and Get Credentials

To use Meetup's API, you need to register an OAuth consumer application. Go to meetup.com/api/oauth/list/ while logged into your Meetup account and click 'Register a new OAuth Consumer'. Fill in the application name, description, and website URL. For the Redirect URI, enter your deployed URL since the WebContainer preview URL changes each session — use https://your-app.netlify.app/api/meetup/callback for now (you can update this after deployment, or use localhost:3000 for local development).

After registering, Meetup provides a Consumer Key and Consumer Secret. These are your OAuth 2.0 client credentials — the Consumer Key is the client_id and the Consumer Secret is the client_secret in OAuth terminology. Save both in your .env file.

Meetup's OAuth 2.0 uses the same authorization code flow as most OAuth providers. The difference from many APIs is that Meetup is transitioning its GraphQL API access requirements. As of 2024-2025, some GraphQL queries for public data (event search by location) can be made with a server-to-server access token obtained via the OAuth client credentials grant. For user-specific operations (RSVPs, managing events, accessing member data), you need an access token authorized by the user.

For development and testing in Bolt's WebContainer preview, focus on the client credentials approach first — it works for public event search and does not require a redirect callback. You can build and test the event discovery features entirely in the WebContainer, then add the user OAuth flow after deploying to Netlify for features that need it.

```
# .env — add to project root in Bolt
# From: meetup.com/api/oauth/list/ → Register new OAuth Consumer
MEETUP_CONSUMER_KEY=your_consumer_key
MEETUP_CONSUMER_SECRET=your_consumer_secret
# Access token for server-to-server / public API queries
# Obtain via client credentials grant (see step 2)
MEETUP_ACCESS_TOKEN=your_access_token
# For user OAuth flow (update after deployment)
MEETUP_REDIRECT_URI=https://your-app.netlify.app/api/meetup/callback
```

**Expected result:** Meetup OAuth Consumer is registered with Consumer Key and Secret saved in .env. You have noted your redirect URI for the callback setup in a later step.

### 2. Build a GraphQL Client for Meetup API

Meetup's GraphQL API endpoint is https://api.meetup.com/gql. Unlike REST APIs where each resource has a different URL, all GraphQL queries go to this single endpoint as POST requests with a JSON body containing the query string and optional variables. Create a reusable GraphQL client utility that handles the authentication header and request structure.

The basic Meetup GraphQL query structure uses named queries that accept input objects. For event search, the key query is keywordSearch which accepts a SearchConnectionInput with fields for query (keyword), lat and lon (coordinates), radius (miles), and first (number of results). The response shape matches your query structure exactly — this is GraphQL's key advantage. You request only the fields you need, and Meetup returns exactly those fields.

To convert a city name to coordinates for the radius search, use a geocoding API like Google Maps Geocoding or a free alternative like the Open Meteo geocoding API or OpenStreetMap's Nominatim. Alternatively, provide a lat/lon input in your search UI — many users are comfortable entering their city name and searching.

All API calls go through your Next.js API route to avoid CORS. The route constructs the GraphQL query, adds the Bearer token from environment variables, posts to api.meetup.com/gql, and returns the formatted results to your frontend. This pattern works in Bolt's WebContainer during development since it only requires outbound HTTP calls — incoming webhooks are not used by the Meetup API. After deployment to Netlify or Bolt Cloud, the same code runs unchanged in a proper server environment.

```
// lib/meetup-client.ts
export interface MeetupEvent {
  id: string;
  title: string;
  dateTime: string;
  endTime?: string;
  eventUrl: string;
  going: number;
  description?: string;
  venue?: {
    name: string;
    address?: string;
    city?: string;
    state?: string;
  };
  group: {
    id: string;
    name: string;
    urlname: string;
    city: string;
  };
}

export async function meetupQuery<T>(
  query: string,
  variables: Record<string, unknown> = {}
): Promise<T> {
  const token = process.env.MEETUP_ACCESS_TOKEN;
  if (!token) {
    throw new Error('MEETUP_ACCESS_TOKEN not configured');
  }

  const res = await fetch('https://api.meetup.com/gql', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ query, variables }),
  });

  if (!res.ok) {
    throw new Error(`Meetup API error: ${res.status} ${res.statusText}`);
  }

  const { data, errors } = await res.json();

  if (errors?.length) {
    throw new Error(`Meetup GraphQL error: ${errors[0].message}`);
  }

  return data;
}

// Meetup GraphQL queries
export const EVENT_SEARCH_QUERY = `
  query SearchEvents($input: SearchConnectionInput!, $first: Int) {
    keywordSearch(input: $input, filter: { upcoming: true }) {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          id
          result {
            ... on Event {
              id
              title
              dateTime
              endTime
              eventUrl
              going
              description
              venue {
                name
                address
                city
                state
              }
              group {
                id
                name
                urlname
                city
              }
            }
          }
        }
      }
    }
  }
`;

export const GROUP_EVENTS_QUERY = `
  query GroupEvents($urlname: String!) {
    groupByUrlname(urlname: $urlname) {
      id
      name
      description
      city
      memberships {
        count
      }
      upcomingEvents(input: { first: 10 }) {
        edges {
          node {
            id
            title
            dateTime
            duration
            going
            eventUrl
            description
            venue {
              name
              address
              city
            }
          }
        }
      }
    }
  }
`;
```

**Expected result:** The Meetup GraphQL client successfully queries the API. The /api/meetup/events route returns event search results when called with lat/lon and keyword parameters from the Bolt preview.

### 3. Build the Event Discovery Interface

With the GraphQL client working, build the event discovery UI. The core interface consists of a search bar where users enter a location (city name or address), keyword filter chips for common categories (Tech, Design, Startup, Sports, Arts), and a results grid showing event cards.

For location-based search, convert the user's city input to lat/lon coordinates. A clean approach is to use the browser's Geolocation API (navigator.geolocation.getCurrentPosition) for automatic location detection, with a manual city search as fallback. For city-to-coordinates conversion, the OpenStreetMap Nominatim API is free and doesn't require an API key: GET https://nominatim.openstreetmap.org/search?q={city}&format=json returns lat/lon for a city name.

The event card design should show the most scannable information first: event date and time (formatted for the local timezone), event title, group name, venue location, and RSVP count. A 'View on Meetup' button opens the event URL in a new tab — this is the standard approach since Meetup handles event registration on their platform.

For the keyword filter chips, common tech community searches include: 'JavaScript', 'Python', 'startup', 'UX design', 'data science', 'AI', 'mobile'. Each chip updates the search query and triggers a new API call. Debounce the location search input to avoid making API calls on every keystroke — wait 500ms after the user stops typing before fetching results. Results load quickly from the Meetup API (typically 200-400ms), so users see data within about a second of entering a location.

```
// app/api/meetup/events/route.ts
import { NextResponse } from 'next/server';
import { meetupQuery, EVENT_SEARCH_QUERY } from '@/lib/meetup-client';

interface SearchEdge {
  node: {
    id: string;
    result: {
      id?: string;
      title?: string;
      dateTime?: string;
      eventUrl?: string;
      going?: number;
      description?: string;
      venue?: { name: string; city: string };
      group?: { name: string; urlname: string; city: string };
    };
  };
}

export async function GET(request: Request) {
  try {
    const { searchParams } = new URL(request.url);
    const lat = parseFloat(searchParams.get('lat') || '37.7749');
    const lon = parseFloat(searchParams.get('lon') || '-122.4194');
    const keyword = searchParams.get('keyword') || 'tech';
    const radius = parseFloat(searchParams.get('radius') || '25');

    const data = await meetupQuery<{
      keywordSearch: { edges: SearchEdge[] };
    }>(EVENT_SEARCH_QUERY, {
      input: {
        query: keyword,
        lat,
        lon,
        radius,
        source: 'EVENTS',
      },
    });

    const events = data.keywordSearch.edges
      .map(edge => edge.node.result)
      .filter(r => r.title && r.dateTime); // Filter to Event type results

    return NextResponse.json({ events });
  } catch (error) {
    const message = error instanceof Error ? error.message : 'Search failed';
    return NextResponse.json({ error: message }, { status: 500 });
  }
}
```

**Expected result:** The event discovery page shows upcoming Meetup events near the searched location in the Bolt preview. Keyword filter chips update the search results. Each event card shows date, title, group, and venue with a working link to Meetup.

### 4. Implement OAuth for User-Specific Features and Deploy

For features that require user authorization — RSVPs, creating events (Pro), accessing private group data — implement Meetup's three-legged OAuth flow. As established earlier, this requires a publicly accessible redirect URI, which means deploying to Netlify or Bolt Cloud before testing.

Create an OAuth initiation route at /api/meetup/auth that redirects to Meetup's authorization endpoint: https://secure.meetup.com/oauth2/authorize with your Consumer Key, redirect_uri, response_type=code, and the required scopes. Meetup OAuth scopes include: ageless (basic profile), basic (group memberships), rsvp (RSVP management), and organizer (event management for Pro accounts). Request only the scopes you need.

Create a callback handler at /api/meetup/callback that receives the authorization code, exchanges it for tokens at https://secure.meetup.com/oauth2/access, stores the access token and refresh token securely (HttpOnly cookie or Supabase session), and redirects back to your app. The access token is long-lived for Meetup (unlike many OAuth providers), but implement refresh token support to handle expiry gracefully.

After deployment, test the complete flow: click 'Connect Meetup' → Meetup login page → authorize → back to your app with the user's data visible. The client credentials approach (using MEETUP_ACCESS_TOKEN for public data) works in both development and production. The three-legged user OAuth is production-only due to the redirect URI requirement. Meetup's rate limit is 200 requests per hour — cache event data for 15-minute windows to avoid hitting this limit on busy discovery pages.

```
// app/api/meetup/callback/route.ts
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const code = searchParams.get('code');
  const state = searchParams.get('state');
  const storedState = cookies().get('meetup_oauth_state')?.value;

  if (!code) {
    return NextResponse.json({ error: 'No authorization code' }, { status: 400 });
  }

  if (state !== storedState) {
    return NextResponse.json({ error: 'Invalid state parameter' }, { status: 400 });
  }

  const tokenRes = await fetch('https://secure.meetup.com/oauth2/access', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      client_id: process.env.MEETUP_CONSUMER_KEY!,
      client_secret: process.env.MEETUP_CONSUMER_SECRET!,
      grant_type: 'authorization_code',
      redirect_uri: process.env.MEETUP_REDIRECT_URI!,
      code: code!,
    }),
  });

  if (!tokenRes.ok) {
    const error = await tokenRes.text();
    return NextResponse.json({ error: `Token exchange failed: ${error}` }, { status: 500 });
  }

  const { access_token, refresh_token, expires_in } = await tokenRes.json();

  const response = NextResponse.redirect(new URL('/events', request.url));

  response.cookies.set('meetup_token', access_token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    maxAge: expires_in || 86400,
  });

  // Store refresh token more persistently (consider Supabase for long-term storage)
  if (refresh_token) {
    response.cookies.set('meetup_refresh', refresh_token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      maxAge: 60 * 60 * 24 * 30, // 30 days
    });
  }

  return response;
}
```

**Expected result:** After deploying to Netlify and updating the redirect URI, the Meetup OAuth flow completes successfully. Users are redirected to Meetup's login, authorize the app, and return to the events page with their token stored for API calls.

## Best practices

- Cache Meetup API responses for 10-15 minutes — event data changes infrequently and caching significantly reduces API calls, keeping you within the 200 requests/hour rate limit
- Deploy to Netlify or Bolt Cloud before implementing the user OAuth flow — the three-legged OAuth callback requires a publicly accessible redirect URI that the Bolt WebContainer cannot provide
- Request only the Meetup OAuth scopes your application actually uses — the ageless and basic scopes cover most discovery use cases without requiring the rsvp or organizer scopes
- Use inline fragments in GraphQL queries when accessing type-specific fields from union types — Meetup's keywordSearch returns SearchResult union types, not Events directly
- Implement geolocation as the default location detection method with a manual city search as fallback — users appreciate automatic location detection and it reduces friction in the event discovery flow
- Always link to event pages on meetup.com for registration and RSVP rather than trying to replicate Meetup's RSVP flow — users trust Meetup for event management and the official flow handles waitlists and payment correctly
- Filter Meetup search results client-side for additional criteria (free vs paid events, online vs in-person) using the data already fetched rather than making additional API calls

## Use cases

### Local Tech Event Discovery Widget

Build an event discovery widget for a developer community site that shows upcoming tech meetups within a specified radius of the user's city. Uses Meetup's GraphQL API to search events by keyword (JavaScript, Python, startup, etc.) and location, displaying upcoming events with group name, date, location, and RSVP count.

Prompt example:

```
Build a Meetup event discovery page in Next.js. Create an API route at app/api/meetup/events/route.ts that accepts query params for location (city name or lat/lon), keyword, and radius (miles). Use the Meetup GraphQL API at https://api.meetup.com/gql with an Authorization header using a Bearer token from process.env.MEETUP_ACCESS_TOKEN. Write a GraphQL query to search for events: use the keywordSearch query with input fields for query, lat, lon, radius, and first (limit). Return event name, dateTime, venue name, group name, eventUrl, and going count. Build a discovery page at /events with a location search input, keyword filter chips for Tech/Design/Startup/Gaming, and an event card grid showing the results.
```

### Group Calendar and Event Schedule

Display the full event schedule for a specific Meetup group — upcoming events with dates, descriptions, and RSVP counts. Ideal for embedding a community group's calendar in their own website, giving members a single place to see all upcoming gatherings without leaving the site.

Prompt example:

```
Create a Meetup group calendar component for Next.js. Build an API route at app/api/meetup/group/[groupUrlName]/events/route.ts that fetches upcoming events for a group using the Meetup GraphQL API. Query the groupByUrlname field with the urlname from params, fetching upcomingEvents with name, dateTime, duration, description, and going (RSVP count). Build a calendar-style page at /community/[groupUrlName] showing events in a list sorted by date, with event cards including date/time badge, title, description excerpt, venue, and RSVP count. Include a 'View on Meetup' button for each event.
```

### RSVP Manager for Meetup Pro Groups

Build an RSVP management dashboard for Meetup Pro organizers to view attendee lists, send messages to attendees, and track attendance across multiple events. Requires Meetup Pro subscription and three-legged OAuth for organizer-level access.

Prompt example:

```
Create an RSVP management dashboard for Meetup organizers in Next.js. After implementing the OAuth callback flow, build an API route app/api/meetup/events/[eventId]/rsvps/route.ts that fetches the RSVP list for an event using the Meetup GraphQL API eventRsvps query. Return member names, email if available, RSVP status (yes/waitlist), and RSVP date. Build a dashboard page at /organizer/events/[eventId]/rsvps showing a table of attendees with export to CSV functionality. Add summary stats: total RSVPs, waitlist count, and attendance rate from previous events.
```

## Troubleshooting

### Meetup GraphQL query returns errors about inline fragments or unknown fields

Cause: The Meetup GraphQL schema uses union types (SearchResult, Event, Group) and the query is accessing fields directly instead of using inline fragments for type-specific fields.

Solution: Add ... on Event (or ... on Group) inline fragments when accessing type-specific fields in search results. Fields like title, dateTime, venue, and going are Event-specific and must be accessed inside a ... on Event fragment in the keywordSearch results.

```
// WRONG: accessing event fields directly
query { keywordSearch(input: $input) { edges { node { result { title dateTime } } } } }

// CORRECT: using inline fragment for Event type
query { keywordSearch(input: $input) { edges { node { result { ... on Event { title dateTime going venue { name } } } } } } }
```

### Meetup OAuth callback returns 'redirect_uri_mismatch' error

Cause: The redirect_uri in the authorization request does not exactly match the one registered in your Meetup OAuth consumer settings.

Solution: Log into meetup.com/api/oauth/list/, click your OAuth consumer, and verify the redirect URI matches MEETUP_REDIRECT_URI in your .env exactly — including https://, no trailing slash. The Bolt WebContainer preview URL cannot be registered as a redirect URI. Deploy to Netlify first and register the deployed URL.

### Event search returns empty results despite valid credentials and location

Cause: The search radius is too small for the requested location, there are no upcoming events matching the keyword in that area, or the lat/lon coordinates are incorrect for the city name entered.

Solution: Increase the search radius to 50 miles for initial testing. Verify the lat/lon by checking a geocoding service — San Francisco is 37.7749/-122.4194, New York is 40.7128/-74.0059. Try a broader keyword like 'tech' or 'programming' instead of a specific language to confirm the API is returning results.

```
// Test with known coordinates and broad keyword first
const testParams = {
  lat: 37.7749, // San Francisco
  lon: -122.4194,
  keyword: 'tech',
  radius: 50, // miles
};
```

### Meetup API returns 429 Too Many Requests

Cause: The application is exceeding Meetup's rate limit of 200 requests per hour. Common when search results trigger multiple follow-up requests for event details.

Solution: Implement response caching using Next.js Route Handler cache settings or a Supabase table. Cache event search results for 15 minutes — Meetup event data does not change frequently enough to require real-time fetching. Reduce parallel API calls by batching data into fewer, larger queries using GraphQL's ability to request multiple resources in one query.

```
// Add caching to Next.js route handler
export const revalidate = 900; // Cache for 15 minutes (900 seconds)

export async function GET(request: Request) {
  // Route handler logic...
}
```

## Frequently asked questions

### Does Meetup have a public REST API or only GraphQL?

Meetup has fully migrated to GraphQL. Their original REST API (api.meetup.com/v3/) is deprecated as of 2021 and will be removed. All new integrations should use the GraphQL API at api.meetup.com/gql. While this requires learning GraphQL query syntax, it provides more flexible data fetching — you request exactly the fields you need in a single query instead of multiple REST calls.

### Do I need a Meetup Pro account to use the API?

Basic API access for reading public events and group data is available on free Meetup accounts. You need Meetup Pro ($29.99/month per group) to manage events programmatically — creating events, managing RSVPs, accessing attendee lists, and sending messages to members via the API. For event discovery dashboards that only read public data, a free account is sufficient.

### Can I test Meetup OAuth in Bolt.new's development preview?

Partially. The OAuth initiation and public API calls work in the Bolt WebContainer preview since they only require outbound HTTP calls. The OAuth callback — where Meetup redirects back to your app after login — requires a publicly accessible URL that the WebContainer cannot provide. Deploy to Netlify or Bolt Cloud, register the deployed URL as your redirect URI in Meetup's OAuth consumer settings, and test the complete login flow on the deployed site.

### How do I handle Meetup's rate limits in a busy application?

Meetup limits authenticated requests to 200 per hour. Implement response caching with a 15-minute TTL for event search results — Meetup events change infrequently enough that slightly stale data is acceptable for a discovery page. Use Next.js Route Handler revalidation (export const revalidate = 900) or a Supabase cache table to store results. For high-traffic pages, consider building a background job that pre-fetches popular location searches on a schedule rather than fetching on every page request.

### How is Meetup's GraphQL API different from REST for Bolt.new developers?

With REST, each type of data has a separate URL (GET /events, GET /groups/:id). With GraphQL, all queries go to one endpoint (POST api.meetup.com/gql) with the query written as a string in the request body. The main Bolt.new impact is in your API route: instead of building multiple proxy routes for different endpoints, you build one proxy that passes GraphQL queries through. The query syntax uses named queries with typed input variables, and inline fragments for union types like search results.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/meetup
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/meetup
