Foreign key columns must be unsigned — they can’t hold negative values, and MySQL enforces type parity between referenced and referencing columns. Laravel gives you several ways to declare a column as unsigned. The right approach depends on how explicit you want to be and which Laravel version you’re on.
:::note[TL;DR]
$table->foreignId('user_id')is the recommended approach in Laravel 11+$table->unsignedBigInteger('user_id')is explicit and valid, but more verbose$table->integer('user_id')->unsigned()uses a modifier — works but deprecated in some driversforeignId()automatically creates an unsigned BIGINT and can chain->constrained()- All approaches work in Laravel 12 :::
:::warning
Laravel 11+ recommendation: foreignId() is now the preferred method for foreign key columns. It automatically creates an unsigned BIGINT, matches the default id() column type, and chains directly into ->constrained() for full foreign key constraint setup. Manually declaring unsignedBigInteger() is still valid but verbose by comparison — and ->unsigned() as a modifier is a compatibility shim you should avoid in new migrations.
:::
What is the recommended way to declare a foreign key column in Laravel 12?
Use foreignId(). It creates an unsigned BIGINT with one method and lets you attach the foreign key constraint in the same chain:
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained(); // references users.id
$table->foreignId('post_id')->constrained('posts'); // explicit table name
$table->timestamps();
});
foreignId('user_id') infers the referenced table as users (strips the _id suffix). constrained() adds the actual FOREIGN KEY constraint. If you want unsigned without the constraint, just omit constrained():
$table->foreignId('user_id'); // unsigned BIGINT, no FK constraint
The scenario: You’re writing a migration for a
commentstable that belongs to users and posts. Instead of four lines per foreign key (declare column, mark unsigned, add index, add constraint),foreignId()->constrained()does it in one. That’s the practical reason it exists.
When would you still use unsignedBigInteger()?
When you need explicit control over column definition and want to be unambiguous in a code review. It’s also useful in legacy codebases where foreignId() wasn’t available when the original migrations were written:
Schema::create('comments', function (Blueprint $table) {
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('post_id');
});
This is identical to what foreignId() creates under the hood — an unsigned BIGINT. It just doesn’t attach a foreign key constraint automatically. Add one explicitly if you need it:
$table->foreign('user_id')->references('id')->on('users');
See Create Composite Indexes with Migration if you also need compound indexes on these columns.
What are the other ways to mark a column as unsigned?
Laravel provides several unsigned column types and a modifier. These are all valid in Laravel 12:
Using unsignedInteger (and its variants)
$table->unsignedInteger('created_by');
Use this when the referenced column is an INT, not a BIGINT. Mismatching signed/unsigned types between related columns causes a MySQL constraint error.
Using integer() with three parameters
The third parameter on integer() is the unsigned flag:
$table->integer('post_id', false, true); // (name, autoIncrement, unsigned)
This is the lowest-level form. It works, but it’s harder to read at a glance. The false is autoIncrement and true is unsigned — easy to transpose.
Using the unsigned() modifier
$table->integer('reply_to_comment_id')->unsigned();
The ->unsigned() modifier applied to integer() also works. It’s more readable than the three-parameter form, but still verbose compared to foreignId() for a foreign key column.
Full comparison in a single migration
This migration shows all four approaches applied to the same table, with comments explaining when each makes sense:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('comments', function (Blueprint $table) {
$table->id();
// Recommended: foreignId for FK columns in Laravel 11+
$table->foreignId('user_id')->constrained();
// Explicit: matches foreignId() output without the constraint
$table->unsignedBigInteger('post_id');
// Verbose: three-param form (avoid in new code)
$table->integer('approved_by', false, true);
// Modifier form: valid but wordy
$table->integer('reply_to_comment_id')->unsigned();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('comments');
}
};
Note the anonymous class syntax (return new class extends Migration). This is the standard format in Laravel 9+ and is required in Laravel 12. Named migration classes still work but won’t be generated by php artisan make:migration.
Column type reference
| Column method | SQL type | Notes |
|---|---|---|
foreignId('col') | UNSIGNED BIGINT | Recommended for FK columns in Laravel 11+ |
unsignedBigInteger('col') | UNSIGNED BIGINT | Same type as foreignId(), no auto constraint |
unsignedInteger('col') | UNSIGNED INT | Use when referencing an INT primary key |
unsignedMediumInteger('col') | UNSIGNED MEDIUMINT | Smaller range, rarely needed |
unsignedSmallInteger('col') | UNSIGNED SMALLINT | Even smaller — for lookup tables with few rows |
unsignedTinyInteger('col') | UNSIGNED TINYINT | 0–255, good for status flags |
unsignedDecimal('col', 8, 2) | UNSIGNED DECIMAL | For non-negative monetary values |
For the full list, see the Laravel 12 Migration documentation.
Summary
foreignId()is the right default for FK columns in Laravel 11 and 12. It’s concise and self-documenting.unsignedBigInteger()is the explicit alternative — same result, more typing.->unsigned()as a modifier still works but is the most verbose form and should be avoided in new migrations.- Always match the unsigned type of the referencing column to the type of the referenced column, or MySQL will reject the constraint.
- Use anonymous class syntax (
return new class extends Migration) in all new migrations.
FAQ
Does foreignId() add a foreign key constraint automatically?
Not by default. foreignId('user_id') creates the unsigned BIGINT column. Call ->constrained() after it to add the actual FK constraint.
What happens if I use unsignedInteger() to reference an id() column?
You’ll get a MySQL error when adding the constraint. Laravel’s id() creates a BIGINT. unsignedInteger() creates an INT. The types must match — use unsignedBigInteger() or foreignId() instead.
Can I add a foreign key constraint to an existing column without recreating it?
Yes. Use Schema::table() (not Schema::create()) and call $table->foreign('column_name')->references('id')->on('table_name').
Do I need to use unsigned columns for non-FK integer fields?
Only if the value will never be negative. For things like likes_count, view_count, or position that are always non-negative, using unsigned prevents negative values at the database level — a useful constraint.
Is the unsigned() modifier deprecated?
Not deprecated, but foreignId() makes it unnecessary for FK columns. For non-FK unsigned columns, unsignedInteger() and its typed variants are clearer than integer()->unsigned().
What to Read Next
- Create Composite Indexes with Migration in Laravel — add indexes to the same migration that declares your unsigned columns.
- Drop Composite Indexes in Laravel Migrations — the rollback-side operations for index management.
- Create Indexes with Migration in Laravel — single-column index patterns for foreign key columns.