Hi folks!
As an engineer, I don’t trust things sent from the client-side; they must be validated and sanitized thoroughly. In this case, I’m referring to the user message ID.
Problem
From the info that I gathered so far, it seems in AI SDK 6, it is recommended (or implied) to trust the frontend generated ID for user messages. I know in the documentation for AI SDK UI: Chatbot Message Persistence, it says if you want to persist chat messages you should generate IDs on the server-side, but the example is only for assistant messages. We are still trusting the frontend generated IDs for user messages.
Potential Risks
This could result in three scenarios:
- Invalid IDs: User message has a weird ID like
a, abc, or a very large one. This can be prevented by adding validation.
- ID Collisions: Multiple user messages share the same ID. In the UI, the latest message that shares the same ID will replace the older ones. On the backend, if messages are persisted using their IDs, this could overwrite the old messages depending on the implementation.
- ID Hijacking: User message reuses the ID of an assistant message. This could make the assistant message disappear.
Actually, since AI SDK doesn’t provide a persistence solution, I tested it with Mastra. For scenarios 2 and 3, they did overwrite the old messages in the database. I didn’t dig deeper, but I believe Mastra’s data is scoped using Resource ID, maybe even Thread ID. However, if developers roll their own solution, this could be a risk because one user could overwrite another user’s messages if message IDs are leaked, or perform attacks like deliberately generating IDs to overload one shard of the database.
Current Workaround
Currently, I overwrite the ID of the user message sent to the server. It feels really hacky, like an anti-pattern, and creates inconsistency if the client-side doesn’t reload; the ID in the UIMessage will still be the one generated by the frontend.
I’m out of options, so I’d like to discuss with anyone who’s interested in this topic. Maybe you have better solutions. Thanks!
A client generated UUID (or similar) is common for local-first and apps with similar optimistic performance requirements. If you create a message and then want to edit it, you need access to the message’s ID. If you only allow the server to create IDs, that means you’re waiting on a server round trip before you can do anything with the message.
So the key is to have two IDs: one created by the client using a collision-resistant algorithm like a UUID, which is only trusted by this device, and one created on the server/DB which is trusted by every user and device.
- risks of collisions are basically zero in normal use. Someone could intercept the request and send a known client ID instead, but you can just hard block any client ID collisions and assume they’re malicious
- the client ID should be indexed in your DB but does not need to be the primary key. You can still have a server-side primary key
- if you want you can refuse to let users query documents by each other’s client IDs
This allows a user to do multiple operations in sequence
- create message ABCD with text “Hello world”
- edit message ABCD to say “Goodbye world”
- server receives create event, creates message ID 1 with client ID ABCD and text “Hello world”
- server receives edit event, looks up client ID and sees it maps to message ID 1 and can perform the edit
For optimistic UI, it means you can create a message and immediately display it in optimistic state. When the server revalidates, one of two problems usually happens
- either all the optimistic messages are removed, which is bad if the user sent multiple messages but the server revalidated before they all completed
- or they are not removed, and the user sees both the optimistic message and the new one coming in from the server
Keeping the Client ID on your message document permanently means you can filter the optimistic messages to remove any client IDs that are present in the real server data, and then there is zero chance of being out of sync
Thanks your detailed reply! 
With your explanation, this makes sense to me now, it’s just how AI SDK was designed for, building local-first apps, not something you can change or opt-out. Although I didn’t want to wait until the server-side returns an ID then show the user message, just wondering if there’s a good solution that is immutate to the issues I mentioned.
After posting this thread, I’ve being thinking and researching. Like you said, dual-ID (using another ID in database) is the solution I ended up using too (had to roll my own “harness“ in the end instead of using an existing solution), I still don’t recommend anyone to use the message.id as the primary key if you want to store messages separately (I think in a lot of agentic product this is very common), even if you have authentication check in place.
Cheers, loving AI SDK!