---
title: "Database schema — Refresh Tokens for Laravel | Roundly"
description: "The refresh_tokens columns, plus RefreshTokenBlueprint for reproducing or adopting the schema in your own migration."
url: https://roundly-consulting.com/open-source/docs/refresh-tokens-for-laravel/database-schema
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Refresh Tokens for Laravel](https://roundly-consulting.com/open-source/docs/refresh-tokens-for-laravel.md)

# Database schema

One publish-only migration creates the table (refresh\_tokens by default). There are no foreign keys:

| Columns | Type | Notes |
| --- | --- | --- |
| `id` | bigint | Row id — changes on every rotation; address sessions by family\_id instead. |
| `owner_type, owner_id` | string + bigint/uuid/ulid | Polymorphic owner with a composite index; owner\_id follows key\_type. |
| `token_hash` | string(128), unique | At-rest digest; fits sha512 hex. Hidden from serialization. |
| `family_id` | uuid, indexed | The stable session id, canonicalised to lowercase. |
| `access_reference` | string(64), nullable, indexed | Opaque link to the access token; a longer reference is refused before any query. Hidden from serialization. |
| `user_agent, browser, browser_version, os, os_version, device_type, is_bot` | nullable | Device data — host-supplied, carried forward on rotation. |
| `country, city, country_code, ip_address` | nullable | Geo data — host-supplied, carried forward on rotation. |
| `family_started_at, absolute_expires_at, meta` | nullable; meta is jsonb | Session columns every row of a family inherits. |
| `revoked_reason, expires_at, revoked_at` | lifecycle | expires\_at and revoked\_at are indexed. |
| `created_at, updated_at, deleted_at` | timestamps | Standard timestamps plus soft deletes. |

## Adopting an existing tokens table

RefreshTokenBlueprint reproduces the package shape from your own migration: columns() emits the full table, and addSessionColumns() adds only the three session columns — all nullable, so they fit a populated table:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use RoundlyConsulting\PackageToolkit\Enums\KeyType;
use RoundlyConsulting\RefreshTokens\Support\RefreshTokenBlueprint;

// Reproduce the package table in your own migration:
Schema::create('refresh_tokens', function (Blueprint $table): void {
    RefreshTokenBlueprint::columns($table, KeyType::Ulid);
});

// Or add only the session columns to an existing tokens table:
Schema::table('refresh_tokens', function (Blueprint $table): void {
    RefreshTokenBlueprint::addSessionColumns($table); // family_started_at, absolute_expires_at, meta
});
```

Backfill owner\_type, family\_started\_at and absolute\_expires\_at in the same migration. Use the family’s first row as its start:

```php
use App\Models\User;
use Illuminate\Support\Facades\DB;

DB::table('refresh_tokens')->update(['owner_type' => (new User)->getMorphClass()]);

// family_started_at = the family's FIRST row, not each row's own created_at.
DB::table('refresh_tokens')
    ->select('family_id', DB::raw('min(created_at) as started'))
    ->groupBy('family_id')
    ->orderBy('family_id')
    ->each(function (object $family): void {
        $started = \Carbon\CarbonImmutable::parse($family->started);

        DB::table('refresh_tokens')->where('family_id', $family->family_id)->update([
            'family_started_at' => $started,
            'absolute_expires_at' => $started->addSeconds(7_776_000), // omit if absolute_ttl = 0
        ]);
    });
```

Without the backfill, legacy rows still work: sessionStartedAt() falls back to created\_at, and a family whose rows have no absolute\_expires\_at is uncapped until it is re-rooted.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/refresh-tokens-for-laravel.md)
