---
title: "Key types & schema — Credits for Laravel | Roundly"
description: "The credits and credit_locks tables, and how key_type and primary_key_type type the morph column and the ledger’s own id."
url: https://roundly-consulting.com/open-source/docs/credits-for-laravel/key-types
language: en
---

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

[Credits for Laravel](https://roundly-consulting.com/open-source/docs/credits-for-laravel.md)

# Key types & schema

One table, credits, holds every ledger row for every owner. Its shape:

| Column | Type | Notes |
| --- | --- | --- |
| `id` | bigint / uuid / ulid | Per primary\_key\_type — auto-incrementing bigint by default. |
| `creditable_type` | string | The owning model’s morph class. |
| `creditable_id` | bigint / uuid / ulid | Per key\_type. Indexed together with creditable\_type. |
| `bucket` | string | Default 'default', indexed. |
| `amount` | bigInteger | Signed: positive grants, negative deducts. Cast to int. |
| `description` | string, nullable | The optional human-readable note. |
| `meta` | json(b), nullable | Arbitrary structured context, cast to array. |
| `created_at / updated_at` | timestamps | created\_at drives point-in-time balances. |
| `deleted_at` | soft deletes | Soft-deleted rows no longer count toward any balance. |

A composite index on (creditable\_type, creditable\_id, bucket) backs every balance read.

## The credit\_locks table

The same migration creates credit\_locks — one small row per owner that every change bumps, so racing changes of one owner serialise. It holds a version counter only; the ledger stays the single source of truth:

| Column | Type | Notes |
| --- | --- | --- |
| `creditable_type` | string | The owner’s morph class. Primary key together with creditable\_id. |
| `creditable_id` | bigint / uuid / ulid | Per key\_type — the same type as in credits. |
| `version` | unsignedBigInteger | Default 0. Bumped by every change; never read for a balance. |

## Two independent key types

Two settings type two different columns. They are independent — uuid users holding credits under a bigint credits id is perfectly ordinary:

| Setting | Types | Choose |
| --- | --- | --- |
| `key_type` | creditable\_id — the morph column pointing at your models, in credits and credit\_locks | Your creditable models’ primary key: bigint (default), uuid (HasUuids) or ulid (HasUlids). |
| `primary_key_type` | id — the credits table’s own key | What other packages’ polymorphic columns point at. Keep bigint unless every morph target in your app shares one key type. |

Both are fixed when the migration first runs, so set them before you publish and migrate:

```bash
# Decide before publishing and running the migration — both are fixed at migrate time.
CREDITS_KEY_TYPE=uuid             # your creditable models use HasUuids
CREDITS_PRIMARY_KEY_TYPE=bigint   # the credits table's own id — keep the default
```

## Matching key\_type to your models

key\_type must match your creditable models’ primary key: keep the default bigint for auto-incrementing ids, and set uuid or ulid when those models use HasUuids or HasUlids:

```bash
CREDITS_KEY_TYPE=uuid
```

Leave it at bigint with uuid-keyed users and PostgreSQL rejects the first change:

```
SQLSTATE[22P02]: invalid input syntax for type bigint: "01a0e9ca-cf2c-7..."
```

Every creditable model in the application must share that one key type.

## Why primary\_key\_type defaults to bigint

A Credit is something other packages point at polymorphically, and a Laravel morph column ($table->morphs('subject')) is an unsigned bigint. On a strict engine such as PostgreSQL, a uuid credit id will not go into one:

```
SQLSTATE[22P02]: invalid input syntax for type bigint: "019f6f33-22b8-737f-a581-849e7cdc517a"
```

SQLite will not warn you about this — its type affinity silently stores the string in an integer column, so a green SQLite suite proves nothing here.

The constraint: this assumes every morph target in your application shares one key type. If you set CREDITS\_PRIMARY\_KEY\_TYPE=uuid, the models on the other end of your polymorphic relations need to be uuid-keyed too, and the packages owning those columns need to agree. A mixed application — a uuid Credit and a bigint Comment pointed at by the same morph column — is not supported by this package, by Laravel’s own morphs()/uuidMorphs() split, or by anything else. Pick one key type per application.

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