May 8th, 2026

Laravel AI: Configurable Conversation and Message Table Names

Laravel AI: Configurable Conversation and Message Table Names
Sponsored by
Table of Contents

@timmcleod merged PR #484 into laravel/ai, making the two database table names used by DatabaseConversationStore configurable via config/ai.php. The change resolves issue #483.


The Problem

DatabaseConversationStore hardcoded agent_conversations and agent_conversation_messages as string literals in five separate places. Any project that needed different names, to avoid collisions with another package, or to follow a naming convention like module_* or tenant_*, had to reimplement the entire ConversationStore interface from scratch. That meant inheriting none of the future improvements made to the built-in implementation.


The New Config Keys

A storage.tables array now lives in config/ai.php. Both keys fall back to the original literals, so no existing install needs any changes.

1'storage' => [
2 'tables' => [
3 'conversations' => env('AI_TABLE_CONVERSATIONS', 'agent_conversations'),
4 'messages' => env('AI_TABLE_MESSAGES', 'agent_conversation_messages'),
5 ],
6],

Override via .env if the defaults conflict with anything on the same database:

1AI_TABLE_CONVERSATIONS=acme_ai_conversations
2AI_TABLE_MESSAGES=acme_ai_conversation_messages

How DatabaseConversationStore Reads Them

Two protected accessors replace the five scattered string literals inside DatabaseConversationStore:

1protected function conversationsTable(): string
2{
3 return config('ai.storage.tables.conversations', 'agent_conversations');
4}
5 
6protected function messagesTable(): string
7{
8 return config('ai.storage.tables.messages', 'agent_conversation_messages');
9}

Every query method now calls one of those accessors rather than referencing a string directly:

1public function storeConversation(string|int|null $userId, string $title): string
2{
3 // ...
4 DB::table($this->conversationsTable())->insert([ /* ... */ ]);
5 // ...
6}

Migration Consistency

The published migration reads from the same config keys, so publishing both the config and the migration gives consistent table names with no manual edits required:

1Schema::create(config('ai.storage.tables.conversations'), function (Blueprint $table) {
2 // ...
3});
4 
5Schema::create(config('ai.storage.tables.messages'), function (Blueprint $table) {
6 // ...
7});

Projects that have already run the original migration and want to rename their tables need to write a short rename migration alongside the config change. The package does not ship one automatically.


Test Coverage

@timmcleod added tests/Feature/Storage/DatabaseConversationStoreTest.php with four cases: default-config sanity, the default-tables write path, an overridden-tables write path that asserts the default table names are not created, and migration correctness under configured names. The full Pest suite passes 985 of 985 tests.


Who Should Care

Any project sharing a database with another package that also uses agent_conversations, or following strict internal table-naming conventions, can now set two environment variables and move on. No custom store implementation needed.

If you enjoyed this article, please consider supporting our work for as low as $5 / month.

Sponsor
Marian Pop

Written by

Marian Pop

Writing and maintaining @LaravelMagazine. Host of "The Laravel Magazine Podcast". Pronouns: vi/vim.

Comments

Stay Updated

Subscribe to our newsletter

Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.

Weekly articles
We send a new issue of the newsletter every week on Friday.
No spam
We'll never share your email address and you can opt out at any time.