Laravel error
Laravel: Specified key was too long, 767 bytes
The error
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytesAn index on a string column exceeded what the storage engine allows. The real fix is to size the column for its contents rather than to shorten every string in the application.
SQLSTATE[42000]: Syntax error or access violation: 1071
Specified key was too long; max key length is 767 bytes
An index was asked for over more bytes than the storage engine will key. It appears on the first migration run against MySQL or MariaDB, usually on users.email or a unique() somewhere in a package's migration.
Why the number is bytes
The arithmetic is the whole explanation:
| Charset | Bytes per character | 255-char index asks for |
|---|---|---|
utf8 (3-byte) | 3 | 765 bytes - fits |
utf8mb4 | 4 | 1020 bytes - refused |
utf8mb4 is the one that can store an emoji, a full range of CJK characters, and anything else outside the basic plane. It is the correct default and it is why the same migration that worked for years stopped.
Size the column, not the application
The usual advice is to add this to AppServiceProvider:
use Illuminate\Support\Facades\Schema;
public function boot(): void
{
Schema::defaultStringLength(191);
}It works. It also silently makes every string() column in the application 191 characters, indexed or not, forever. A column holding a product title now truncates at 191 because a column holding an email address needed an index.
Prefer sizing the column that is actually indexed:
$table->string('email', 191)->unique();One column, one deliberate limit, and the rest of the schema keeps a sensible default.
On a current database, check before working around it
The 767-byte limit belongs to InnoDB's older COMPACT and REDUNDANT row formats. With DYNAMIC - the default on MySQL 5.7 and later, and on current MariaDB - the limit is 3072 bytes, and a 255-character utf8mb4 column indexes without complaint.
So if you are seeing this on a modern server, something is forcing an old row format or an old innodb_large_prefix setting, and the honest fix is there rather than in your migrations:
SHOW VARIABLES LIKE 'innodb_default_row_format';A shared host pinned to an old configuration is a real constraint and worth knowing about explicitly, rather than accommodating it in application code without a note saying why.
When the value is genuinely long
A URL, a file path, a composite natural key. A prefix index over the first 191 characters of a URL is close to useless, because the distinguishing part is at the end.
Index a hash instead:
$table->string('url', 2048);
$table->char('url_hash', 64)->unique(); // sha256 of the normalised urlFixed width, cheap to index, and uniqueness that means what you wanted it to mean. The cost is remembering to write the hash on every insert, which belongs in the model rather than in each call site.
Schema decisions like this one are hard to reverse once a table has rows in it, which is why they are worth an argument early rather than a workaround now.
