In Laravel 13, change an existing column by redefining the desired type and modifiers and calling change(). The syntax is short, but the production risk is not: Laravel requires you to repeat modifiers you want to preserve, database engines may rewrite or lock the table, and narrowing or incompatible conversions can reject or transform existing data. Back up recoverable data, inspect real values, and run the exact migration against representative staging data before production.
Current syntax: ->change()
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->text('description')
->nullable()
->change();
});
}
You are declaring the column's new state, not merely its new base type.
Repeat every modifier you want to keep
Schema::table('users', function (Blueprint $table) {
$table->integer('votes')
->unsigned()
->default(1)
->comment('my comment')
->change();
});
Laravel 13 explicitly documents that omitted modifiers are dropped during a column change. Preserve nullability, defaults, unsigned state, comments, and other modifiers deliberately.
Indexes are a separate decision
change() does not automatically change the column's indexes. If the new type requires a different index, add/drop it explicitly and verify that the target database supports the resulting key definition and length.
Inspect real data before narrowing or converting
Text length
SELECT
MAX(CHAR_LENGTH(description)) AS max_length,
SUM(CHAR_LENGTH(description) > 255) AS rows_over_255
FROM products;
Signed to unsigned
SELECT
MIN(quantity) AS min_quantity,
MAX(quantity) AS max_quantity,
SUM(quantity < 0) AS negative_rows
FROM inventory_items;
For string-to-date or string-to-number changes, profile invalid formats instead of trusting implicit conversion to make a domain decision.
PostgreSQL can need an explicit USING expression
Schema::table('users', function (Blueprint $table) {
$table->date('birthday')
->using('birthday::date')
->change();
});
Laravel 13 documents using() specifically for PostgreSQL type changes. That expression is PostgreSQL SQL, not a portable migration expression.
MySQL: a real data-type change can require a table copy
MySQL 8.4's InnoDB online-DDL documentation lists changing a column's data type as an operation that uses ALGORITHM=COPY. Some related operations—such as certain VARCHAR length increases—have less disruptive algorithms, but that does not make arbitrary type changes metadata-only.
For a large table, test the exact old and new definitions on the actual MySQL major/minor version. Measure runtime, metadata locks, blocked writes, temporary disk demand, and replication impact rather than assuming the framework migration is “instant.”
Backup, staging, and deploy sequencing
- Backup: keep a recovery path before narrowing/destructive conversion.
- Staging: test against representative volume and value distribution.
- Mixed app versions: ensure old/new releases can coexist with the schema during rolling deploys.
- Long DDL: plan for locks, replica lag, deploy timeouts, and operational monitoring.
Write a realistic down() migration
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->string('description', 255)
->nullable()
->change();
});
}
This rollback is valid only if every current value still fits in 255 characters. A syntactically reversible migration is not necessarily data-reversible after new code has written values the old type cannot represent.
For risky changes: expand, backfill, switch, contract
- Add a new nullable column with the target type.
- Backfill in controlled batches and record conversion failures.
- Deploy code that reads/writes the new representation.
- Verify counts, nulls, ranges, and application behavior.
- Remove or rename the old column in a later deployment.
This is a production strategy, not a Laravel requirement. It is useful when a direct conversion is expensive or hard to recover from.
What about doctrine/dbal?
Older Laravel tutorials often say doctrine/dbal is required to modify existing columns. Treat that as version-specific historical advice. Current Laravel 13 migration documentation supports change() directly and does not instruct modern projects to install DBAL for ordinary column modification. For an older application, consult that Laravel version's documentation.
Pre-deploy checklist
- Confirm production Laravel and DBMS versions.
- Inspect the current column definition, modifiers, indexes, and constraints.
- Profile values that may not survive the target type.
- Repeat every modifier that must remain.
- Test the exact migration on realistic data and measure DDL behavior.
- Define recovery based on data reality, not just the existence of
down().
Related guides
Sources and further reading
- Laravel 13.x: Migrations
- MySQL 8.4: InnoDB Online DDL Operations
- MySQL 8.4: ALTER TABLE
- PostgreSQL: ALTER TABLE