---
title: "Key types & schema macros — Package Toolkit for Laravel | Roundly"
description: "One config-driven bigint/uuid/ulid key strategy with the ownerKey(), morphKey(), polymorphicSubject() and auditable() macros."
url: https://roundly-consulting.com/open-source/docs/package-toolkit-for-laravel/key-types
language: en
---

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

[Package Toolkit for Laravel](https://roundly-consulting.com/open-source/docs/package-toolkit-for-laravel.md)

# Key types & schema macros

A package’s schema freezes at its first tagged release, so the key type of the models it relates to is configured up front rather than fixed to bigint — UUID- and ULID-keyed models are supported alongside the auto-incrementing default. KeyType resolves the host’s choice from config:

```php
// config/comments.php
return [
    'key_type' => 'bigint',   // bigint | uuid | ulid (or a KeyType case) — match the host's models
];
```

```php
use RoundlyConsulting\PackageToolkit\Enums\KeyType;

KeyType::fromConfig('comments.key_type');                  // KeyType::BigInt | KeyType::Uuid | KeyType::Ulid; 'uiid' throws
KeyType::fromConfig('comments.key_type', KeyType::Ulid);   // a different default for a key that is not set (absent, null, blank)

// config('comments.key_type') === 'uiid' — a typo
// throws InvalidConfigurationException:
// Configuration value [comments.key_type] must be one of [bigint, uuid, ulid] (case-insensitive), [uiid] given.

KeyType::fromValue('UUID');   // KeyType::Uuid — trimmed, case-insensitive
KeyType::fromValue('nope');   // KeyType::BigInt — a raw string still falls back silently
```

| Config value | Case | Columns |
| --- | --- | --- |
| `'bigint'` | `KeyType::BigInt` | unsignedBigInteger / numericMorphs |
| `'uuid'` | `KeyType::Uuid` | uuid / uuidMorphs |
| `'ulid'` | `KeyType::Ulid` | ulid / ulidMorphs |
| A KeyType case | That case, as-is | — |
| Absent, null or blank ('' / whitespace) | The default (KeyType::BigInt unless you pass another) | — |
| Anything else ('uiid', false, an int) | Throws InvalidConfigurationException | — |

fromConfig() accepts a KeyType case or its string value — trimmed and matched case-insensitively. A key that is not set — absent, null or blank ('' or whitespace) — reads as the default (bigint, or the KeyType you pass as the second argument). Any other value, such as a typo’d 'uiid', throws InvalidConfigurationException instead of silently building bigint columns for a UUID-keyed host. fromValue() maps a raw string the same way but still falls back to its default for anything unrecognised — use it for values that are not config.

## Schema macros

Register the macros with RegistersBlueprintMacros (see Opt-in traits), then use them in migrations:

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

$type = KeyType::fromConfig('comments.key_type'); // KeyType::BigInt | KeyType::Uuid | KeyType::Ulid; 'uiid' throws

Schema::create('comments', function (Blueprint $table) use ($type): void {
    $table->id();
    $table->ownerKey('author_id', $type);              // FK column of the right type, indexed
    $table->morphKey('subject', $type);                // subject_type / subject_id morph pair (+ index)
    $table->polymorphicSubject('target', $type, true); // nullable morph pair
    $table->auditable();                               // timestamps() + softDeletes()
});
```

| Macro | Result |
| --- | --- |
| `ownerKey(string $name, KeyType $type, bool $nullable = false, bool $index = true)` | A single foreign-key column (unsignedBigInteger / uuid / ulid), optionally nullable and indexed. Returns the ColumnDefinition. |
| `morphKey(string $name, KeyType $type, bool $nullable = false)` | The correct numericMorphs / uuidMorphs / ulidMorphs pair (+ nullable variants) — <name>\_type and <name>\_id with a composite index. A host’s Schema::morphUsingUuids() or morphUsingUlids() never changes it. |
| `polymorphicSubject(string $name, KeyType $type, bool $nullable = false)` | A polymorphic subject column pair — the morph convention under a domain-friendly name. |
| `auditable()` | timestamps() + softDeletes(). |

ownerKey() uses $name as the column name as-is — pass author\_id for an author\_id column — and returns the ColumnDefinition, so further column modifiers chain onto it. morphKey() and polymorphicSubject() use $name as the prefix of the \_type / \_id pair; with KeyType::BigInt they always build the numeric pair, even when the host calls Schema::morphUsingUuids() or morphUsingUlids(). The macro logic also lives as plain static methods on RoundlyConsulting\\PackageToolkit\\Support\\BlueprintMacros, called with an explicit Blueprint.

## 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/package-toolkit-for-laravel.md)
