Table of Contents
You write a raw query in Laravel. It works in staging. You push to production, and suddenly: Table ‘database.products’ doesn’t exist.
Plot twist: The table is called product (singular). Welcome to the world of legacy databases.
Laravel’s Plural Assumption
Laravel’s Eloquent ORM follows a convention: table names are plural, model names are singular.
// Model: Product
// Expected table: products (plural)
class Product extends Model
{
// Laravel auto-assumes 'products' table
}
This works great… until you inherit a database where someone used singular table names.
The Problem with Raw Queries
When you use Eloquent methods, Laravel uses $model->getTable(), which you can override:
class Product extends Model
{
protected $table = 'product'; // Override to singular
}
// Eloquent queries work fine
Product::where('status', 'active')->get();
But raw queries don’t use getTable():
// ❌ Breaks if table is actually 'product' (singular)
DB::table('products')->where('status', 'active')->get();
// ❌ Also breaks
DB::select("SELECT * FROM products WHERE status = ?", ['active']);
The Fix: Use Model Table Names
Always reference the model’s table name dynamically:
// ✅ Uses the model's $table property
$table = (new Product)->getTable();
DB::table($table)->where('status', 'active')->get();
// ✅ Or inline
DB::table((new Product)->getTable())
->where('status', 'active')
->get();
For raw SQL strings:
$table = (new Product)->getTable();
DB::select("SELECT * FROM {$table} WHERE status = ?", ['active']);
Now if the table name changes (migration, refactor, database merge), you update it once in the model.
Why This Happens
Common scenarios where table names don’t match Laravel conventions:
- Legacy databases: Built before Laravel, different naming standards
- Multi-framework codebases: Database shared between Laravel and another app
- Database naming policies: Company standards enforce singular table names
- Third-party integrations: External systems dictate schema
Bonus: Check All Your Models
Want to see which models override table names?
grep -r "protected \$table" app/Models/
Or in Tinker:
collect(get_declared_classes())
->filter(fn($c) => is_subclass_of($c, Model::class))
->mapWithKeys(fn($c) => [$c => (new $c)->getTable()])
->toArray();
The Takeaway
Never hardcode table names in queries. Use getTable() to respect model configuration. When the table name changes, your queries won’t break.
Future-you debugging at 2 AM will thank you.

Leave a Reply