# OpenAI GPT

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

## TL;DR

Connect FlutterFlow to OpenAI GPT using a FlutterFlow API Group pointed at a Cloud Function proxy that holds your OpenAI sk-... secret key and forwards requests to the Chat Completions endpoint at api.openai.com/v1/chat/completions. Placing the key directly in a FlutterFlow API Call header ships it inside the compiled APK where it can be extracted and used to drain your billing — the Cloud Function proxy is not optional.

## OpenAI GPT in FlutterFlow: Why the Proxy Is Not Optional

OpenAI's Chat Completions API is the most popular AI integration in the FlutterFlow community — and also the one with the most critical security mistake. The API uses a Bearer secret key that starts with `sk-`. Because FlutterFlow compiles to a Flutter app that runs directly on the user's device, any value you put in an API Call header is embedded in the compiled APK or web bundle. APK files can be unpacked with basic tools available online, and the OpenAI key is visible in plain text inside the binary. With an extracted key, anyone can make requests to OpenAI and bill charges directly to your account — OpenAI keys have no domain or app restriction and do not expire until you revoke them manually.

The correct architecture for FlutterFlow + OpenAI is always a Cloud Function proxy. Your FlutterFlow API Group calls your Cloud Function URL, not `api.openai.com` directly. The function reads the `sk-...` key from its own environment configuration (not from the incoming request) and adds it to the Authorization header before forwarding the request to OpenAI. FlutterFlow never touches the key. This pattern also fixes the CORS issue that prevents direct OpenAI calls from working in FlutterFlow web builds — when published to the web, browsers block requests to OpenAI's API with a `No 'Access-Control-Allow-Origin'` error, but your Cloud Function can call OpenAI without any browser CORS restriction and return the response with the correct headers.

Once the proxy is in place, the actual FlutterFlow wiring is straightforward: an API Group with one POST call, a messages variable, and a JSON path pointing to `choices[0].message.content`. You can build a full working chat UI in FlutterFlow in about 30 minutes using this pattern.

## Before you start

- An OpenAI account with a valid API key (sk-...) at platform.openai.com — note that OpenAI no longer offers a free tier; you need to add a payment method and purchase credits
- A Firebase project with Cloud Functions enabled on the Blaze plan (outbound network calls from Cloud Functions require the paid Blaze plan)
- A FlutterFlow project on a paid plan (API Calls are available on all paid FlutterFlow tiers)
- Basic familiarity with FlutterFlow's API Calls panel and App State variables
- Node.js knowledge for writing and deploying the Cloud Function (or willingness to copy the provided snippet)

## Step-by-step guide

### 1. Get your OpenAI API key and understand the cost model

Before building anything, go to platform.openai.com and sign in or create an account. Navigate to API Keys in the left sidebar and click 'Create new secret key'. Give it a name like 'FlutterFlow App' and copy the key immediately — OpenAI shows the full key only once at creation time. Store it securely (for example in a password manager) because you will paste it into your Cloud Function's environment configuration in the next step.

Understand the pricing before you build: OpenAI charges per token — roughly per word — for both the input (your prompt and conversation history) and the output (the GPT reply). As of mid-2026, GPT-4o is the most capable model and GPT-4o-mini is significantly cheaper for lighter use cases like simple Q&A or classification. Check the current pricing at openai.com/pricing because rates change frequently. For a typical chat message (500 input tokens + 200 output tokens), the cost is a fraction of a cent per message on most models, but long conversations with large system prompts and multiple turns add up quickly.

Also check your current rate limits at platform.openai.com/account/rate-limits. New accounts have lower rate limits (requests per minute and tokens per minute) that increase as you add credit usage. If your app gets heavy traffic before your limits increase, users will see 429 errors. Understanding these limits now helps you build appropriate retry logic in the next steps rather than debugging mysterious failures after launch.

**Expected result:** You have an OpenAI API key stored securely and understand which model and pricing tier to use. You know your current rate limits.

### 2. Deploy the Cloud Function proxy

The Cloud Function is the most important piece of this integration because it keeps the OpenAI key off the client. Open your Firebase project's functions directory and create (or add to) `index.js`. The function accepts a POST request with a JSON body containing a `messages` array and an optional `model` field. It adds the `Authorization: Bearer sk-...` header from its own environment config and forwards the request to `https://api.openai.com/v1/chat/completions`.

The function must set CORS headers (`Access-Control-Allow-Origin: *`) before processing so that FlutterFlow web builds can receive the response. It must return a 204 immediately for OPTIONS preflight requests — without this, web builds will fail before the actual POST is even sent.

Store the OpenAI key as a Firebase environment variable using `firebase functions:config:set openai.key="sk-..."` and access it inside the function via `functions.config().openai.key`. For Cloud Functions 2nd generation (recommended for new projects), use Google Secret Manager or process.env with the key set in the Cloud Run configuration instead.

After deploying with `firebase deploy --only functions`, test the function directly by sending it a POST request with a sample messages array. Use curl or Postman. Confirm you receive a valid OpenAI response with the `choices[0].message.content` field before moving to FlutterFlow. If you see a 401 from OpenAI, the key is wrong or expired. If you see a 429, the key has insufficient quota. Neither is a FlutterFlow issue — fix the key or add credits first.

```
// Cloud Function: OpenAI Chat Completions proxy
const functions = require('firebase-functions');
const fetch = require('node-fetch');

const OPENAI_KEY = functions.config().openai.key; // set via firebase functions:config:set

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

  const { messages, model } = req.body;
  if (!messages || !Array.isArray(messages)) {
    res.status(400).json({ error: 'messages array required' });
    return;
  }

  try {
    const openaiRes = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${OPENAI_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        model: model || 'gpt-4o-mini',
        messages
      })
    });

    const data = await openaiRes.json();
    res.status(openaiRes.status).json(data);
  } catch (err) {
    res.status(500).json({ error: 'Proxy error', detail: err.message });
  }
});
```

**Expected result:** Calling the Cloud Function URL with a POST body of `{"messages":[{"role":"user","content":"Hello"}]}` returns a valid OpenAI response JSON including choices[0].message.content. The function URL is ready to use in FlutterFlow.

### 3. Create the OpenAI API Group in FlutterFlow

Open your FlutterFlow project and click API Calls in the left navigation panel. Click + Add and choose Create API Group. Name the group 'OpenAI' and set the Base URL to your Cloud Function's HTTPS URL (e.g. `https://us-central1-your-project.cloudfunctions.net/chatProxy`). You do not need to add any headers to the group — the Cloud Function handles the OpenAI Authorization header internally.

Click the + icon next to the group name and choose Add API Call. Name it 'ChatCompletion' and set the method to POST. In the Variables tab, add a variable named `messages` of type String. FlutterFlow's variable types are limited, so you will pass the messages array as a JSON string and the Cloud Function will receive it as a parsed array in `req.body.messages` (since the Content-Type is application/json, the body is automatically parsed). Optionally add a second variable named `model` of type String with a default value of `gpt-4o-mini`.

In the Request Body tab, make sure JSON is selected. Enter the body as: `{"messages": {{ messages }}, "model": "{{ model }}"}`. The `{{ messages }}` token will be replaced at runtime with the JSON string you pass from FlutterFlow. Since you are passing a pre-serialized JSON array string, and it is being embedded inside a JSON body, the Cloud Function's body parser handles the nested JSON correctly when the body is sent as `application/json`.

Go to the Response & Test tab. In the Sample Response field, paste a real OpenAI response (copy one from your earlier curl test). Click Generate JSON Paths and FlutterFlow will parse the response. Find and name the path `$.choices[0].message.content` as `replyText`. Also capture `$.usage.total_tokens` as `tokensUsed` if you want to monitor costs. Save the API Group.

```
// Sample API Call configuration reference
{
  "group_name": "OpenAI",
  "base_url": "https://us-central1-YOUR_PROJECT.cloudfunctions.net/chatProxy",
  "calls": [
    {
      "name": "ChatCompletion",
      "method": "POST",
      "endpoint": "",
      "headers": { "Content-Type": "application/json" },
      "body": { "messages": "{{ messages }}", "model": "{{ model }}" },
      "variables": [
        { "name": "messages", "type": "String" },
        { "name": "model",    "type": "String", "default": "gpt-4o-mini" }
      ],
      "json_paths": [
        { "name": "replyText",   "path": "$.choices[0].message.content" },
        { "name": "tokensUsed",  "path": "$.usage.total_tokens" },
        { "name": "finishReason","path": "$.choices[0].finish_reason" }
      ]
    }
  ]
}
```

**Expected result:** The API Calls panel shows an 'OpenAI' group with a 'ChatCompletion' POST call. Testing the call manually in FlutterFlow returns a real GPT reply and the replyText JSON path shows the assistant's message content.

### 4. Build the chat UI with a message list and send button

Add a new screen in FlutterFlow for the chat experience. The layout from top to bottom is: a ListView of chat messages, a Divider, a Row containing a TextField and a Send IconButton.

Create a custom Data Type called `ChatMessage` with two fields: `role` (String, either 'user' or 'assistant') and `content` (String). In App State, create a variable `conversationMessages` of type `List<ChatMessage>` — this holds the full conversation history that you send to OpenAI with each request. Also create a String App State variable `currentInput` to track the text the user is typing, and a Boolean variable `isLoading` to control a loading state.

For the ListView, bind it to `conversationMessages`. Inside each list item, add a conditional Row: if `role == 'user'`, show the message aligned right with a blue bubble; if `role == 'assistant'`, show it aligned left with a gray bubble. Use FlutterFlow's conditional visibility to show each variant only for the correct role.

For the Send button's action flow in the Actions panel: first add a Set App State action that sets `isLoading` to true. Then add another Set App State action that appends a new ChatMessage with role='user' and content from the TextField to `conversationMessages`. Then clear the TextField. Then add an API Request action that calls the ChatCompletion API call, passing `conversationMessages` serialized to a JSON string as the messages variable. After the API Request, add a Set App State action that appends a new ChatMessage with role='assistant' and content from the `replyText` JSON path to `conversationMessages`. Finally set `isLoading` back to false.

Add a Visibility widget wrapping a CircularProgressIndicator that is visible only when `isLoading` is true. This gives users visual feedback during the API call.

**Expected result:** The chat screen displays a scrollable list of user and assistant messages with different bubble styles. Typing a message and tapping Send adds it to the list, shows a loading indicator, calls OpenAI via the proxy, and appends the GPT reply to the conversation — all within a few seconds.

### 5. Add a system prompt and handle errors and rate limits

A system prompt defines the AI's persona, tone, and scope for your app. Before the user's first message, you should prepend a system message to the conversationMessages list. The best place to do this is in the Page On Load action: add a Set App State action on the page's initState that initializes `conversationMessages` with a single ChatMessage where `role = 'system'` and `content` = your system prompt text, like 'You are a friendly assistant for [Your App Name]. Help users with [specific use case]. Keep responses concise and focused on [topic].'

For error handling, add conditional logic in the Action Flow Editor after the API Request action. Check if the HTTP response status code is 429 — show a Snackbar widget with 'You have reached the usage limit. Please try again in a moment.' Check if the status is 500 or the response body contains `insufficient_quota` — show 'Our AI service is temporarily unavailable.' For any other non-200 status, show a generic 'Something went wrong. Please try again.' message.

For long conversations, be aware that OpenAI has context window limits — the total tokens in the messages array (including the system prompt) cannot exceed the model's maximum. GPT-4o-mini supports 128K context tokens which is very large, but if your app allows very long sessions, consider trimming the oldest messages from the conversationMessages list when it exceeds a certain length (e.g. keep only the last 20 messages plus the system prompt).

If you would rather have the RapidDev team set up the Cloud Function proxy, configure the system prompt, and wire up the chat UI for your specific app use case, they build FlutterFlow + OpenAI integrations like this every week — reach out for a free scoping call at rapidevelopers.com/contact.

```
// Dart Custom Action: serialize ChatMessage list to JSON string
// Add this as a Custom Action in FlutterFlow → Custom Code → + Add → Action
// Arguments: messages (List<ChatMessageStruct>)
// Return type: String

import 'dart:convert';

Future<String> serializeMessages(List<ChatMessageStruct> messages) async {
  final list = messages.map((m) => {
    'role': m.role,
    'content': m.content,
  }).toList();
  return jsonEncode(list);
}
```

**Expected result:** The system prompt is injected before the first user message, so GPT responds in the persona and scope you defined. Rate limit and quota errors show friendly Snackbar messages. Long conversations are managed by trimming old messages when needed.

### 6. Test the full flow and verify web and mobile behavior

Run a complete end-to-end test before publishing. In FlutterFlow's Run Mode (the play button in the top navigation bar), open the chat screen and send a few messages. Watch the conversation build up with your messages and GPT replies alternating in the ListView. Verify that the system prompt is working by checking that GPT's replies stay on topic and match the persona you defined.

For web testing specifically: since you are routing through a Cloud Function rather than calling OpenAI directly, CORS is handled by the Cloud Function's response headers. You should not see any browser console errors related to Access-Control-Allow-Origin. If you do see CORS errors, verify that the Cloud Function code sets the Access-Control-Allow-Origin header before anything else in the handler and returns 204 for OPTIONS requests.

For mobile testing: FlutterFlow's Run Mode runs in a web browser even when testing mobile layouts. For a true mobile test, export the app (Basic plan) or use FlutterFlow's Test on Device feature to run on a physical Android or iOS device. OpenAI API Calls work identically on mobile because mobile builds do not enforce CORS.

Check the Firebase Functions logs (Firebase Console → Functions → Logs) after testing to see the requests going through and to catch any server-side errors. Also check the OpenAI dashboard (platform.openai.com/usage) to confirm tokens are being consumed — if usage shows zero after your test, the calls are not reaching OpenAI and something is wrong in the proxy.

**Expected result:** The chat UI works end to end: user messages appear immediately, a loading spinner shows during the API call, and GPT replies appear within 2-5 seconds. The Firebase Functions logs show successful proxy calls. OpenAI usage dashboard shows token consumption matching the test messages sent.

## Best practices

- Never put the OpenAI sk-... key in a FlutterFlow API Call header, App Constant, or App Value — it ships in the compiled app and can be extracted from the APK in minutes
- Use GPT-4o-mini as the default model for cost efficiency and switch to GPT-4o only for tasks that genuinely need more capability — this can reduce per-conversation costs by 10x
- Set a spending cap in the OpenAI billing dashboard and monitor usage weekly during development to avoid surprise charges from buggy loops or unexpected traffic
- Add Firebase ID token verification to the Cloud Function so only authenticated users of your app can make GPT calls — without auth, anyone who finds your Cloud Function URL can use your OpenAI quota
- Keep system prompts short and specific — every token in the system prompt is included in every API call, so verbose system prompts multiply costs across every conversation
- Trim conversation history to the last 20 messages plus the system prompt to prevent context window costs from growing without bound in long sessions
- Always disable the Send button while a request is in progress (using an isLoading App State boolean) to prevent users from triggering multiple concurrent API calls with rapid taps
- Log OpenAI usage (token counts and model) to Firestore per user session during beta so you can identify which user flows consume the most tokens and optimize your prompts accordingly

## Use cases

### In-app AI assistant that answers questions about your product

A FlutterFlow app with a chat screen where users ask questions about your service, product features, or documentation. Each message is sent to the Cloud Function proxy with a system prompt that grounds GPT in your product context. The reply is displayed in a chat bubble ListView using a custom Data Type that tracks sender (user or AI) and message text. Conversation history is maintained in App State as a list and sent with each request to preserve context.

Prompt example:

```
Build a FlutterFlow chat screen with a message input field and a send button. When the user sends a message, add it to a List App State variable, call the OpenAI Cloud Function proxy with the full messages list, and append the GPT reply to the same list. Display the messages in a ListView with different bubble colors for user and AI.
```

### Content summarizer that condenses long articles for mobile reading

A FlutterFlow reading app where users paste or share a long article URL, the app fetches the text via another API call, and then sends it to OpenAI with a summarization prompt. The GPT summary appears in a Card widget below the original content. Users can adjust the summary length by choosing 'Short', 'Medium', or 'Detailed' options that modify the system prompt sent with each request.

Prompt example:

```
Build a FlutterFlow screen with a text input for pasting article content and a row of chips for summary length. On submit, call the OpenAI proxy with a system prompt asking for a summary at the selected length and display the result in a Card widget.
```

### AI-powered onboarding wizard that collects user preferences through conversation

A FlutterFlow onboarding flow where GPT plays the role of a friendly setup wizard, asking the user a series of questions through a conversational interface. As the user responds, their preferences are extracted from the conversation using structured output (a JSON mode prompt) and saved to Firestore. The onboarding feels natural and adaptive instead of using static form fields, and the backend stores only the structured preference data rather than the raw conversation.

Prompt example:

```
Build a FlutterFlow onboarding chat screen. Send user messages to the OpenAI proxy with a system prompt asking GPT to collect their name, goals, and preferences in a friendly conversational format. After 3 exchanges, ask GPT to return a JSON summary of the collected preferences and save it to the user's Firestore profile.
```

## Troubleshooting

### OpenAI returns 401 Unauthorized even though the key is set in the Cloud Function

Cause: The OpenAI key is invalid, expired, or was copied incorrectly. A common mistake is copying the key with a leading or trailing space when setting the Firebase environment variable. Another cause is using a project API key from a different OpenAI organization than the one you expect.

Solution: Go to platform.openai.com/api-keys and verify the key exists and has not been revoked. Run `firebase functions:config:get` in your terminal and confirm the openai.key value is exactly correct with no extra spaces or characters. If in doubt, revoke the old key, create a new one, and re-set the config with `firebase functions:config:set openai.key="sk-NEW_KEY"`, then re-deploy the function.

### Cloud Function call succeeds but replyText JSON path is empty or null in FlutterFlow

Cause: The JSON path `$.choices[0].message.content` does not match the actual response structure. This can happen if OpenAI returns a different response shape (for example, if finish_reason is 'content_filter' and the content field is empty), or if FlutterFlow's JSON path was generated from a different sample response and does not match what the current model returns.

Solution: In the FlutterFlow API Call Response & Test tab, click Test again to get a fresh response from your current Cloud Function. Paste that fresh response into the Sample Response field and click Generate JSON Paths again to regenerate the paths from the actual current response. Check that choices[0].message.content is present in the raw response JSON before relying on the generated path.

### 429 Too Many Requests error from OpenAI during testing

Cause: You have exceeded the rate limit for your OpenAI account tier — either requests per minute or tokens per minute. New accounts with minimal credit usage have the lowest rate limits. Rapid testing (sending many messages quickly) can trigger this even in development.

Solution: Check your current rate limits at platform.openai.com/account/rate-limits. Add a spending limit to your account to prevent runaway costs. In your FlutterFlow action flow, disable the Send button while a request is in progress (using the isLoading App State variable) to prevent rapid double-clicks from firing multiple concurrent requests. For production apps, add exponential backoff retry logic in the Cloud Function for 429 responses.

### XMLHttpRequest error in FlutterFlow web preview — No Access-Control-Allow-Origin header

Cause: The Cloud Function is not setting CORS headers correctly, or you accidentally configured the FlutterFlow API Group to point directly at api.openai.com instead of your Cloud Function URL. OpenAI's API does not include CORS headers that allow browser-origin requests, so any direct call from a web app fails with this error.

Solution: First, confirm your API Group Base URL is your Cloud Function URL, not api.openai.com. Second, confirm the Cloud Function sets `res.set('Access-Control-Allow-Origin', '*')` as the very first line inside the handler function, before any async calls or conditional checks. Third, confirm the function returns `res.status(204).send('')` when `req.method === 'OPTIONS'` before doing anything else.

```
// Place these lines FIRST in the Cloud Function handler:
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') { res.status(204).send(''); return; }
```

## Frequently asked questions

### Can I put the OpenAI key in a FlutterFlow App Value so it's not visible in the UI?

No. App Values (App Constants) in FlutterFlow are compiled into the app binary just like hardcoded strings. They are not encrypted and are visible to anyone who decompiles the APK. The only safe place for the OpenAI key is server-side — in a Cloud Function's environment configuration or Secret Manager, where the compiled Flutter code never touches it.

### Can I get a streaming typewriter effect where GPT replies appear word by word?

Not natively with a standard FlutterFlow API Call, because API Calls wait for the full response before returning. To get a streaming effect, the Cloud Function needs to receive OpenAI's server-sent events (SSE) stream, buffer the tokens, and relay them to the client via a Firestore document that updates incrementally as tokens arrive. The FlutterFlow app listens to that Firestore document in real time and updates the Text widget as new tokens appear. This requires additional custom code and is more complex than the basic non-streaming pattern described in this guide.

### Does this integration work for GPT-4 Vision (image inputs)?

Yes. OpenAI's Chat Completions API supports image inputs by including a message content array with both text and image_url objects. You would upload the image to Firebase Storage, get the download URL, and include it in the messages array as `{"type":"image_url","image_url":{"url":"https://..."}`. The Cloud Function proxy handles this transparently — the messages format change is the only difference, not the proxy architecture.

### What happens if my Cloud Function is cold and takes 2-3 seconds to start?

Firebase Cloud Functions go cold after periods of inactivity and the first invocation after a cold period takes 2-5 seconds to start before processing the request. For chat apps, this cold-start delay is noticeable. You can reduce it by setting a minimum instance count of 1 in the Cloud Function configuration (this keeps one instance warm at all times but incurs a small continuous cost), or by using Cloud Functions 2nd generation which has faster cold starts. Show a loading indicator in FlutterFlow during the request so users know it is working.

### How do I use OpenAI's JSON mode to get structured output instead of plain text?

Add `"response_format": {"type": "json_object"}` to the Cloud Function's request body when calling OpenAI, and instruct GPT in the system prompt to always respond with valid JSON. The Cloud Function can either return the raw JSON string to FlutterFlow (parse it with a JSON path in the API Call), or parse it server-side and return only the specific fields your app needs. JSON mode requires GPT-4o or GPT-4o-mini — it is not available on older models.

---

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