FlutterFlow Streaming API Null in On Message: Fix It
You turned on Process Streaming Response for your chatbot API call. The API test panel shows tokens arriving in the raw body. Then you wire up the On Message action, bind the incoming value to page state, and every message comes through as null.
The stream works fine. The problem is that you picked the wrong field from OnMessageInput, or you picked the right one and some events in the stream hold nothing it can read. This post shows how to read the raw test body to choose between Server Sent Event Data Text and Server Sent Event Data JSON, how to write a JSON path that actually matches, and how to skip the empty and [DONE] events that turn your value into null. The working example is the WidgetChat /v1/chat/stream endpoint, wired with native FlutterFlow actions only.
Where the value comes from
When Process Streaming Response is on (API call > Advanced Settings), the action flow gives you three triggers: onMessage, onError and onClose. Inside onMessage you read each event from Set Variable > Action Parameters > OnMessageInput, then choose one of the Response Stream Message options:
- Server Sent Event Data Text (String): the raw text after
data:. Severaldata:lines in one event are joined with newlines. - Server Sent Event Data JSON (JSON): the
data:text parsed as JSON. You add a JSON path to pull out one field. - Server Sent Event Name, Server Sent Event ID, Server Sent Event Retry: the other SSE fields.
- Message Text: the whole raw message, all fields included.
FlutterFlow's docs say it plainly: the null value appears in Server Sent Event Data JSON when the data is not in JSON format. That one sentence explains most "flutterflow streaming api null" reports.
Step 1: read the raw body before you pick a field
You can look at the raw stream in the API call's Response & Test tab, but a terminal makes the event boundaries easier to see. Copy the headers and request body for your project from the WidgetChat dashboard, then run:
curl -N -X POST https://api.widgetchat.app/v1/chat/stream \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-H "<auth header from your WidgetChat dashboard>" \
-d @body.json
-N turns off curl's buffering so each event shows up as it arrives. Now look at what follows each data:. There are two shapes you will see from AI chat backends.
Shape A, plain text tokens:
data: You
data: can
data: reset
data: it
Shape B, a JSON object per event:
data: {"choices":[{"delta":{"content":"You"}}]}
data: {"choices":[{"delta":{"content":" can"}}]}
data: [DONE]
Shape B is the OpenAI chat completions format, which many proxies and no-code backends pass through unchanged. It ends with a literal data: [DONE] line.
Step 2: match the field to the shape
This is the side-by-side fix.
If the text after data: is plain text (Shape A), use Server Sent Event Data Text. No JSON path. The value is the token itself, leading space included, so do not trim it or your words run together. Choosing Data JSON here is the classic mistake: You is not valid JSON, so the parser returns null on every single message.
If the text after data: starts with { (Shape B), use Server Sent Event Data JSON with a JSON path. Write the path against one event's payload, not against the whole body. For the example above:
$['choices'][0]['delta']['content']
That is the path FlutterFlow's own docs use for an OpenAI-shaped stream. If your backend wraps tokens differently, say {"token":"You"}, the path is $.token. A path that points at a key the event does not have also gives you null, so copy the key names character for character from the curl output.
Whatever WidgetChat returns for your project, let the curl output decide. The rule never changes: plain text after data: means Data Text, a JSON object means Data JSON plus a path.
Step 3: guard the events that carry no token
Even with the right field, some events are not tokens. They are the second source of nulls in a flutterflow sse on message action.
[DONE]: the end marker in OpenAI-style streams. It is not JSON, so Data JSON returnsnull. With Data Text you get the literal string[DONE], which ends up appended to your reply.- Keep-alive and empty events: some servers send comment lines (starting with
:) or events with an emptydata:to keep the connection open during a slow first token. The SSE spec says browsers drop comments and do not dispatch events with no data, but FlutterFlow's parser is its own code, so check how it behaves in your test before you count on that. - Metadata events: some streams send a final event with usage or an ID, which has no token at your JSON path.
Add a Conditional Action as the first step inside onMessage, then only update state in the TRUE branch.
For Data Text, the condition is two checks joined with AND:
- Server Sent Event Data Text Is Set and Not Empty
- Server Sent Event Data Text Not Equal To
[DONE]
For Data JSON, check that the JSON path value Is Set and Not Empty. If you would rather fall back than branch, the FlutterFlow docs suggest a code expression like responseData ?? '' so a missing value becomes an empty string instead of null.
One more check helps here. If your stream sets an event: field (for example event: token vs event: done), add a condition on Server Sent Event Name so only token events reach your state update.
Step 4: put it together in the action flow
The whole onMessage flow for a flutterflow streaming chatbot looks like this:
- Conditional Action: the guard from Step 3.
- TRUE: Update Page State on your reply string, using Combine Text with the current reply as the first part and the token (Data Text, or the Data JSON path value) as the second.
- FALSE: nothing.
Then in onClose, flip a isStreaming boolean to false so your UI can hide the typing indicator and re-enable the send button. In onError, set an error message so the user is not stuck looking at an empty bubble.
Step 2 of that flow is where the next bug lives. If you set the reply to the token instead of appending it, you go from null to seeing a single word. That fix is in FlutterFlow Streaming Shows One Token? Append, Don't Set.
Quick diagnosis table in words
Test body shows tokens, On Message is always null: you chose Data JSON for a plain text stream. Switch to Data Text.
On Message is null for every event, and the payload is JSON: your JSON path does not match. Rebuild it from one event in the curl output.
Most tokens arrive but there is a null or a stray [DONE] at the end: add the Step 3 guard.
The value is null and the raw body has no data: lines at all: your backend is not sending SSE. It may be sending JSONL (one JSON object per line), which Process Streaming Response does not parse, and there is an open FlutterFlow community feature request about exactly that. Point the call at an endpoint that speaks SSE, such as WidgetChat's /v1/chat/stream.
Works in the app but not in Test Mode or the web preview: that is usually CORS on the browser build, a separate issue covered in our streaming CORS fix.
Why WidgetChat fits this flow
WidgetChat's POST https://api.widgetchat.app/v1/chat/stream returns token-by-token data: Server-Sent Events, so FlutterFlow's built-in streaming API call can parse it directly. No proxy that rewrites JSONL, no custom Dart action, no SDK to install. You configure one API call, add one guarded onMessage flow, and the bot answers your users from your own content.
Try WidgetChat free
If you want an AI support chatbot in your FlutterFlow or Flutter app that streams replies with native FlutterFlow actions, try WidgetChat free. Create a project, copy the stream endpoint headers from the dashboard, and run the curl check above to see your first tokens.




Comments
Comments are coming soon. We'd love to hear your thoughts!