# Meetup

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 50 minutes
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to Meetup by routing OAuth-authenticated GraphQL requests through a Firebase or Supabase proxy. Unlike REST APIs, Meetup uses a single POST endpoint where the query goes in the request body — every feature (upcoming events, RSVPs) is a different GraphQL query string sent to the same URL. Deeply nested responses must be flattened into Data Types before binding to FlutterFlow widgets.

## Build a Mobile Events and RSVP Companion with Meetup and FlutterFlow

Meetup's primary use-case for a FlutterFlow app is a mobile companion for a community group — showing upcoming events, letting members RSVP, and giving organizers visibility into attendance. What makes Meetup genuinely different from every other social API in this category is that its API is GraphQL, not REST. Instead of many endpoints for different resources (GET /events, GET /rsvps), there is exactly one endpoint: `https://api.meetup.com/gql`. Every request is a POST with a `query` field in the JSON body containing a GraphQL query string. Want upcoming events? Different GraphQL query, same URL. Want RSVPs for a specific event? Another query, same URL. This is the thing that surprises REST-trained developers most — and it shapes how you configure FlutterFlow API Calls for this integration.

Meetup's OAuth 2.0 flow is standard: get a Bearer token, attach it to every request. However, the scope of data you can access depends on the role of the authenticated account. A plain member token returns limited event data. An organizer or co-organizer token unlocks full event management, RSVP lists, attendance tracking, and member data. For most useful FlutterFlow apps — especially organizer dashboards or community companions — you need the organizer-level token. Meetup Pro or organizer subscriptions may be required for full API access; verify current tiers in Meetup's developer documentation.

The other challenge Meetup brings is response nesting. GraphQL responses arrive deeply nested under paths like `data.groupByUrlname.events.edges[].node`. Binding `edges[0].node.title` to a Text widget in FlutterFlow is cumbersome and fragile. The cleanest pattern is to flatten these responses in the Firebase or Supabase proxy — the proxy receives the deeply nested GraphQL result and returns a simple array of flat event objects, making JSON path parsing in FlutterFlow straightforward.

## Before you start

- A Meetup account with organizer or co-organizer access to at least one group (required for full event and RSVP data)
- A Meetup OAuth app created via the Meetup developer portal or API registration page
- A Firebase project with Cloud Functions enabled, or a Supabase project with Edge Functions enabled
- A FlutterFlow project on a paid plan (API Calls and Custom Code require paid tiers)
- Basic familiarity with GraphQL query syntax (query fields and nested selection sets) — you don't need to write a schema, just the query strings for the data you want

## Step-by-step guide

### 1. Create a Meetup OAuth app and obtain a Bearer token

Go to the Meetup developer portal (secure.meetup.com/meetup_api/oauth_consumers or the equivalent in Meetup's current developer settings) and register a new OAuth application. Provide an app name, description, and a redirect URI — for a Firebase-proxied flow, this will be the URL of your Firebase Cloud Function that handles the OAuth callback.

Meetup uses OAuth 2.0 Authorization Code flow. After registering the app, you receive a client ID and client secret. The authorization flow is: your app redirects the user to Meetup's authorization URL with the client ID and requested scopes, the user authorizes the app, Meetup redirects to your callback URL with an authorization code, and your proxy exchanges the code for an access token (Bearer token) and a refresh token.

For the scopes, request `event:read` for reading upcoming events and event details, and `rsvp:write` if you need RSVP mutations. For organizer-level data (RSVP lists, attendance), the authenticated user must be an organizer or co-organizer of the group — scope alone does not grant organizer data if the account is a plain member.

If you are building a single-organizer internal tool, you can perform the OAuth flow once manually (using a tool like Postman or your browser) and store the resulting access token in your proxy's environment variables as a long-lived personal token. For multi-user apps, the full OAuth callback flow runs in the proxy as described in Step 2. Verify current Meetup subscription requirements for API access — organizer and Pro tiers may have different access levels.

**Expected result:** You have a Meetup OAuth app with client ID and client secret, and at least one access token (either a personally obtained token or the credentials to run the OAuth flow in your proxy). The token works against api.meetup.com/gql when tested manually.

### 2. Deploy a Firebase Cloud Function that proxies GraphQL queries and flattens responses

The proxy serves two purposes here. First, it holds the OAuth Bearer token server-side so it never appears in the compiled FlutterFlow app. Second, it flattens Meetup's deeply nested GraphQL responses into simple flat arrays before returning them to the app — this makes FlutterFlow's JSON path binding dramatically simpler.

Meetup's GraphQL responses nest events under `data.groupByUrlname.events.edges` where each item is a `{ node: { id, title, dateTime, venue: { name, lat, lng }, going } }` object. Instead of binding `$.data.groupByUrlname.events.edges[0].node.title` in FlutterFlow's JSON path field, the proxy maps this to a flat array of `{ id, title, dateTime, venueName, venueLat, venueLng, goingCount }` objects. FlutterFlow can then bind `$.events[0].title` — much cleaner and less likely to break when the nesting structure changes.

Store the Bearer token (or OAuth credentials) in Firebase Secret Manager or a Firebase environment variable — not in the function source code. The function below accepts a GraphQL query string from the FlutterFlow app and a `groupUrlname` variable, sends the appropriate GraphQL query to Meetup, and returns a flattened event list.

```
// Firebase Cloud Function: Meetup GraphQL proxy
// index.js (Node.js 18+)
const { https } = require('firebase-functions/v2');
const fetch = require('node-fetch');

const MEETUP_TOKEN = process.env.MEETUP_BEARER_TOKEN;
const MEETUP_GQL = 'https://api.meetup.com/gql';

const UPCOMING_EVENTS_QUERY = `
  query UpcomingEvents($urlname: String!) {
    groupByUrlname(urlname: $urlname) {
      upcomingEvents(input: { first: 20 }) {
        edges {
          node {
            id
            title
            dateTime
            description
            going
            venue {
              name
              lat
              lng
              address
            }
          }
        }
      }
    }
  }
`;

exports.meetupProxy = https.onRequest(async (req, res) => {
  res.set('Access-Control-Allow-Origin', '*');
  if (req.method === 'OPTIONS') {
    res.set('Access-Control-Allow-Methods', 'POST');
    res.set('Access-Control-Allow-Headers', 'Content-Type');
    res.status(204).send('');
    return;
  }

  const { action, groupUrlname } = req.body || {};

  let gqlBody;
  if (action === 'upcomingEvents') {
    gqlBody = {
      query: UPCOMING_EVENTS_QUERY,
      variables: { urlname: groupUrlname }
    };
  } else {
    res.status(400).json({ error: 'Unknown action' });
    return;
  }

  try {
    const response = await fetch(MEETUP_GQL, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${MEETUP_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(gqlBody)
    });

    const data = await response.json();

    // Flatten nested GraphQL response into a simple array
    const edges = data?.data?.groupByUrlname?.upcomingEvents?.edges || [];
    const events = edges.map(e => ({
      id: e.node.id,
      title: e.node.title,
      dateTime: e.node.dateTime,
      description: e.node.description,
      goingCount: e.node.going,
      venueName: e.node.venue?.name || '',
      venueAddress: e.node.venue?.address || '',
      venueLat: e.node.venue?.lat || 0,
      venueLng: e.node.venue?.lng || 0
    }));

    res.status(200).json({ events });
  } catch (err) {
    res.status(500).json({ error: 'Proxy error', details: err.message });
  }
});
```

**Expected result:** The proxy function is deployed. Calling it with `{ action: 'upcomingEvents', groupUrlname: 'your-group-name' }` returns a flat `{ events: [...] }` array with one object per event. Test this directly before connecting FlutterFlow.

### 3. Create a FlutterFlow API Call that sends action parameters to the proxy

In FlutterFlow, click API Calls in the left navigation panel. Click + Add → Create API Group. Name the group `MeetupProxy`. Set the Base URL to your deployed proxy URL — for example `https://us-central1-your-project.cloudfunctions.net/meetupProxy` (Firebase) or `https://your-project.supabase.co/functions/v1/meetup-proxy` (Supabase).

Add a Content-Type header at the group level: set `Content-Type` to `application/json`. This is required because the proxy accepts a JSON body.

Now add an API Call inside the group. Click + Add → Add API Call. Name it `GetUpcomingEvents`. Set Method to POST. Leave the endpoint path empty (your proxy URL is the full endpoint). In the Variables tab, add two variables: `action` (String, default value `upcomingEvents`) and `groupUrlname` (String — your Meetup group's URL name, e.g. `your-city-python`). In the Body tab, set the body to JSON and include both variables: `{ "action": "{{ action }}", "groupUrlname": "{{ groupUrlname }}" }`.

Click the Response & Test tab. Enter the variables (`action: upcomingEvents`, `groupUrlname: your-actual-group-name`) and run a test. The proxy should return a flat `{ events: [...] }` object. Paste the response and click Generate JSON Paths. FlutterFlow will generate paths like `$.events`, `$.events[0].title`, `$.events[0].dateTime`, `$.events[0].venueName`, `$.events[0].goingCount`. Name these paths clearly — you will bind them to List widgets in the next step.

For adding RSVPs, create a second API Call named `CreateRSVP` with POST method, and add variables for `eventId` and `rsvp` (boolean — true for RSVP yes, false for cancel). Add the corresponding case to the proxy function's action router.

```
// Example API Call body config (reference only)
{
  "api_group": "MeetupProxy",
  "method": "POST",
  "body_type": "JSON",
  "body": {
    "action": "{{ action }}",
    "groupUrlname": "{{ groupUrlname }}"
  },
  "json_paths": {
    "eventsList": "$.events",
    "firstTitle": "$.events[0].title",
    "firstDateTime": "$.events[0].dateTime",
    "firstVenueName": "$.events[0].venueName",
    "firstGoingCount": "$.events[0].goingCount"
  }
}
```

**Expected result:** The `GetUpcomingEvents` API Call in the `MeetupProxy` group returns a clean flat array of events. JSON paths for title, dateTime, venueName, and goingCount are generated and named in FlutterFlow.

### 4. Create a Data Type and bind events to a ListView

Create a FlutterFlow Data Type to represent a Meetup event in a structured way. Go to the Data Types section in the left navigation (the database icon), click + Add Data Type, and name it `MeetupEvent`. Add these fields: `eventId` (String), `title` (String), `dateTime` (String — store as String initially to avoid timezone parsing issues), `description` (String), `venueName` (String), `venueAddress` (String), `goingCount` (Integer).

In the `GetUpcomingEvents` API Call settings, go to the Response tab and set the Response Type to `List of MeetupEvent`. Map the JSON path `$.events[].id` to `eventId`, `$.events[].title` to `title`, and so on for each field. This creates a typed list that FlutterFlow can iterate in a ListView.

On the events screen, add a ListView widget. In the widget's properties, set it to use a Backend Query backed by the `GetUpcomingEvents` API Call with your group URL name passed as the `groupUrlname` variable. Inside each list item, add Text widgets bound to `currentEvent.title`, `currentEvent.dateTime`, `currentEvent.venueName`, and an integer Text showing `currentEvent.goingCount` (formatted as 'X going'). Format the `dateTime` string using FlutterFlow's built-in DateTime utilities if you convert it from ISO 8601 format — or display it as-is for simplicity and format it in the proxy before returning.

Add a tap action on each list item to navigate to an event detail screen, passing the `currentEvent` object as a parameter. On the detail screen, show the full description and venue address, and add an RSVP button that triggers the `CreateRSVP` API Call with the event's ID.

**Expected result:** The events screen shows a scrollable list of upcoming Meetup events with title, date/time, venue, and RSVP count. Tapping an event opens a detail screen with the full description and an RSVP button.

### 5. Handle RSVP mutations and refresh the events list

To let users RSVP from the app, add an RSVP mutation to the proxy. A Meetup GraphQL mutation for RSVP looks like:

```graphql
mutation CreateRsvp($input: CreateRsvpInput!) {
  createRsvp(input: $input) {
    rsvp {
      id
      status
    }
  }
}
```

With an input of `{ eventId: "your-event-id", response: "yes" }` for an affirmative RSVP. Add a new case to the proxy's action router for `action: rsvp`, which builds and sends this mutation to Meetup's GraphQL endpoint with the event ID and response from the request body.

In FlutterFlow, the RSVP API Call is a POST to the proxy with `action: rsvp`, `eventId: {{ eventId }}`, and `rsvpStatus: {{ rsvpStatus }}` variables. Trigger this from the RSVP button's Actions panel: + Add Action → Backend/API Calls → API Call → select `CreateRSVP`. After the action completes, add a second action to refresh the events list (re-call `GetUpcomingEvents`) so the updated `goingCount` reflects the RSVP immediately.

For the rate limit consideration: Meetup's GraphQL API has per-token throttling (verify current limits in Meetup's documentation). Event lists change relatively infrequently — add a short App State cache (a 2-3 minute TTL on the events list) to avoid unnecessary refetches when the user navigates back and forth between the list and detail screens. The RSVP mutation always fires fresh since it is a user-initiated write action.

```
// Additional proxy case for RSVP mutation
// Add this inside the meetupProxy function's action router

const RSVP_MUTATION = `
  mutation CreateRsvp($input: CreateRsvpInput!) {
    createRsvp(input: $input) {
      rsvp {
        id
        status
      }
    }
  }
`;

// In the action router, add:
if (action === 'rsvp') {
  const { eventId, rsvpStatus } = req.body;
  gqlBody = {
    query: RSVP_MUTATION,
    variables: {
      input: {
        eventId,
        response: rsvpStatus === 'yes' ? 'yes' : 'no'
      }
    }
  };

  const rsvpResponse = await fetch(MEETUP_GQL, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${MEETUP_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(gqlBody)
  });

  const rsvpData = await rsvpResponse.json();
  res.status(rsvpResponse.status).json(rsvpData?.data?.createRsvp || rsvpData);
  return;
}
```

**Expected result:** Tapping the RSVP button on an event detail screen sends the mutation through the proxy and returns a success status. The events list refreshes after a successful RSVP and shows an updated going count.

## Best practices

- Always route Meetup API calls through a Firebase Cloud Function or Supabase Edge Function — the OAuth Bearer token grants access to event management and member data and must never ship in the compiled app bundle.
- Flatten deeply nested GraphQL responses in the proxy before returning them to FlutterFlow. Binding `$.data.groupByUrlname.events.edges[0].node.title` is fragile and verbose — return `$.events[0].title` from a flattened proxy response instead.
- Use an organizer or co-organizer account for the OAuth token if you need RSVP lists, attendance data, or event management features. A plain member token returns limited data and will silently omit fields you expect.
- Request only the OAuth scopes you need: `event:read` for reading events, `rsvp:write` for RSVP mutations. Over-scoped tokens are a security risk if the token is ever compromised.
- Cache the events list in App State with a 2-3 minute TTL. Upcoming events change infrequently — refetching on every screen navigation wastes rate limit budget and adds latency.
- Test GraphQL queries directly against `api.meetup.com/gql` using a tool like Postman before integrating the proxy. This confirms the query syntax and token scopes before adding FlutterFlow to the equation.
- Handle GraphQL errors separately from HTTP errors. A successful GraphQL request returns HTTP 200 even if the query has an error — check for an `errors` array in the GraphQL response body and surface the first error message to the user.
- Add CORS headers to the proxy for web compatibility. Native mobile builds are not affected, but FlutterFlow web previews and web-deployed apps require `Access-Control-Allow-Origin` to be set on the proxy response.

## Use cases

### Community group event discovery and RSVP app

Members of a local tech or hobby group use the app to see upcoming Meetup events, read descriptions, and tap to RSVP directly from their phone. The FlutterFlow app sends GraphQL queries for upcoming events through the proxy, displays them as a ListView with date, title, and venue, and triggers an RSVP mutation when the member taps Join.

Prompt example:

```
Build an events list screen that shows upcoming events for a specific Meetup group — event title, date, time, venue name, and a count of RSVPs — with a Join button on each event that RSVPs the current user via the Meetup API.
```

### Organizer attendance dashboard

A Meetup group organizer uses the app before and during an event to check who has RSVP'd, mark members as attended, and see a live headcount. The FlutterFlow app queries the RSVP list for a specific event through the proxy and displays member names, photos, and RSVP status in a searchable List.

Prompt example:

```
Create an organizer dashboard screen that lists all RSVPs for a selected upcoming event, showing member name, profile photo, RSVP status (Yes/No/Waitlist), and a checkbox to mark them as attended when they arrive at the venue.
```

### Multi-group event aggregator for a network of communities

A community platform that manages several Meetup groups uses the app to show all upcoming events across all groups in a unified calendar view. The FlutterFlow app sends multiple GraphQL queries (one per group URL name) and merges the results into a single chronological list, giving administrators a single-screen view of their entire event calendar.

Prompt example:

```
Design a unified events screen that fetches upcoming events from three different Meetup group URL names and displays all events merged and sorted by date, with a color-coded badge indicating which group each event belongs to.
```

## Troubleshooting

### GraphQL query returns null data or an empty events array even though the group has upcoming events

Cause: The group URL name passed in the `groupUrlname` variable does not match the exact slug in the Meetup URL, or the OAuth token does not have the `event:read` scope, or the authenticated account is not a member of the group.

Solution: Confirm the group URL name by looking at the full Meetup group URL — it is the slug after `/groups/` or directly in the path. Check the OAuth scopes on the token (regenerate with `event:read` if needed). Ensure the authenticated account is a member or organizer of the target group. If the group is private, a non-member token returns no data.

### RSVP mutations return a 401 or a GraphQL error saying 'Not authorized'

Cause: The OAuth token was generated without the `rsvp:write` scope, or the Meetup account does not have permission to RSVP for the event (event may be full, waitlisted, or member-only).

Solution: Regenerate the OAuth token and ensure `rsvp:write` is included in the requested scopes during the authorization flow. Check the event's RSVP settings in the Meetup organizer panel — some events have RSVP caps, waitlists, or member-only restrictions that block non-member RSVPs at the API level.

### XMLHttpRequest error in FlutterFlow web preview when calling the proxy

Cause: The Firebase Cloud Function or Supabase Edge Function is not returning CORS headers, so the browser blocks the response in FlutterFlow's web Run mode.

Solution: Add `res.set('Access-Control-Allow-Origin', '*')` before any response in the proxy function and handle OPTIONS preflight requests by returning 204 with the appropriate Allow headers. The example proxy code in Step 2 includes this CORS handling. Native Android and iOS builds are not affected by CORS — this only affects FlutterFlow web previews and web-deployed apps.

### Treating Meetup like a REST API — sending GET requests to /events — returns 404 or method errors

Cause: Meetup's current API is GraphQL, not REST. All queries must be POST requests to the single endpoint `api.meetup.com/gql` with a `query` field in the JSON body. There are no REST /events, /rsvps, or /groups endpoints to GET.

Solution: Configure all FlutterFlow API Calls as POST requests to the proxy URL, not to individual resource endpoints. The query or mutation you want to execute goes in the JSON body as the `query` field (handled by the proxy's action router). The Response & Test tab in FlutterFlow should show the flattened proxy response, not a raw GraphQL structure.

## Frequently asked questions

### Why is Meetup's API GraphQL instead of REST?

Meetup migrated to GraphQL to give clients the ability to request exactly the fields they need in a single query, reducing over-fetching. From a FlutterFlow perspective, this means every feature uses one POST endpoint and one type of API Call — but the query string in the body changes for each feature. The practical impact is that you configure fewer API Calls in FlutterFlow (one per logical action rather than one per resource endpoint), but each call needs the correct GraphQL query string in its body.

### Can I place the Meetup OAuth token directly in a FlutterFlow API Call header?

No. FlutterFlow compiles to a Flutter client app. Any value in an API Call header ships inside the compiled binary where it can be extracted by anyone who downloads the app. The Meetup OAuth token grants access to event management, RSVP data, and member information — it must stay in a server-side proxy (Firebase Cloud Function or Supabase Edge Function) where only your backend code can use it.

### Do I need a paid Meetup plan or Pro subscription to use the API?

Access to full event and RSVP data via the API generally requires an organizer account with an active group. Some data may require a Meetup Pro or organizer subscription for full API access. Verify the current requirements in Meetup's developer documentation, as access tiers and subscription requirements can change. A plain free member account typically returns limited data.

### How do I find my Meetup group's URL name for the API query?

The URL name is the slug in the Meetup group URL. For example, in `https://www.meetup.com/groups/my-tech-community/`, the URL name is `my-tech-community`. It is case-sensitive and must match exactly in your GraphQL query variables. If the URL contains encoded characters or uppercase letters, check the group's settings page in the Meetup organizer dashboard to find the exact canonical URL name.

### Can FlutterFlow receive Meetup webhook notifications for new RSVPs or event changes?

FlutterFlow cannot receive webhooks directly — it has no server runtime. If you need real-time RSVP or event-change notifications, configure Meetup webhooks (if available for your account) to point at your Firebase Cloud Function or Supabase Edge Function. The function verifies the notification, writes the update to Firestore or Supabase, and the FlutterFlow app reads the change via a Firestore real-time stream or by polling on a short interval.

---

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