---
title: "Configuration — Tags for Laravel | Roundly"
description: "Every config key with its env variable and default — key types, tables, types, names and slugs, limits, API aliases, abilities and routes."
url: https://roundly-consulting.com/open-source/docs/tags-for-laravel/configuration
language: en
---

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

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

# Configuration

The package works with zero configuration. Publish config/tags.php only to override:

```bash
php artisan vendor:publish --tag="tags-config"
```

Every key is read strictly: a value that is not set — absent, null or blank — takes its default, and a bad one throws an InvalidTagsConfigurationException (or the package toolkit’s InvalidConfigurationException) naming the key, never a silent fallback.

- Booleans read true/false, 1/0, on/off or yes/no; anything else throws.
- Integers accept an int or a plain integer string and must sit in the range each row gives — 5.5 or five throws rather than becoming 0.
- Table names are letters, digits and underscores; type names are lowercase with an optional package prefix; aliases are snake\_case.

## The config file

```php
use RoundlyConsulting\Tags\Http\Resources\TagScopeResource;
use RoundlyConsulting\Tags\Models\Tag;

return [
    'primary_key_type' => env('TAGS_PRIMARY_KEY_TYPE', 'bigint'),
    'key_type' => env('TAGS_KEY_TYPE', 'bigint'),

    'tables' => [
        'tags' => 'tags',
        'taggables' => 'taggables',
        'locks' => 'tag_locks',
    ],

    'models' => [
        'tag' => Tag::class,
    ],

    'default_type' => 'tag',
    'types' => [],
    'overrides' => [],

    'names' => [
        'max_length' => 64,
    ],

    'descriptions' => [
        'max_length' => 500,
    ],

    'slugs' => [
        'max_length' => 80,
        'separator' => '-',
        'unicode' => false,
        'dictionary' => ['+' => 'plus', '#' => 'sharp', '&' => 'and', '@' => 'at'],
    ],

    'hierarchy' => [
        'max_depth' => 5,
    ],

    'suggest' => [
        'limit' => 10,
        'max_limit' => 50,
    ],

    'popular' => [
        'limit' => 10,
        'max_limit' => 100,
    ],

    'scopes' => [],
    'taggables' => [],

    'resources' => [
        'scope' => TagScopeResource::class,
    ],

    'authorization' => [
        'abilities' => [
            'view' => null,
            'manage' => 'tags.manage',
            'scope' => 'tags.scope',
        ],
    ],

    'routes' => [
        'enabled' => env('TAGS_ROUTES') ?: 'off',
        'prefix' => 'api',
        'middleware' => ['api', 'auth', 'throttle:tags-api'],
        'rate_limit' => 120,
        'suggest_rate_limit' => 120,
        'max_per_page' => 100,
        'max_tree_nodes' => 1000,
    ],
];
```

## Key types

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `primary_key_type` | `TAGS_PRIMARY_KEY_TYPE` | bigint | The package’s own keys — tags.id, tags.parent\_id and taggables.tag\_id: bigint, uuid or ulid. Read by the migrations, so set it before migrating; frozen afterwards. |
| `key_type` | `TAGS_KEY_TYPE` | bigint | The keys the package points at: every taggable model (taggable\_id) and every scope model (scope\_id) must use it. A model keyed otherwise throws InvalidTagsConfigurationException when it is tagged or used as a scope; tags:doctor reports it. |

## Tables and the tag model

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `tables.tags` | — | tags | The tag table. Letters, digits and underscores; rename it before migrating when the app already owns a tags table. |
| `tables.taggables` | — | taggables | The pivot — one row per tag on a model. |
| `tables.locks` | — | tag\_locks | One lock row per tree (type and scope). Keep every table name at most 30 characters: MySQL index names are built from it. |
| `models.tag` | — | Models\\Tag | The tag model; a replacement must be a subclass of the packaged one. Every query, relation and the factory resolve the configured class. |

## Tag types

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `default_type` | — | tag | The type a plain string names when a call gives no type. Unless something defines it, it is flat, free-form and unlimited. Blank or null = no default: every string must then name its type. |
| `types` | — | \[\] | The app’s own types: name => rules (see Tag types). |
| `overrides` | — | \[\] | Rules applied over any type — a package’s included — after its definition and every Tags::extend() callback. |

## Names, descriptions and slugs

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `names.max_length` | — | 64 | Characters per locale, 1–255. |
| `descriptions.max_length` | — | 500 | Characters per locale, 1–10000. Descriptions are plain text. |
| `slugs.max_length` | — | 80 | Characters per slug, 1–255. |
| `slugs.separator` | — | \- | One to three of - . \_ ~. |
| `slugs.unicode` | — | false | Keep non-ASCII letters (CJK, Cyrillic, …) in slugs. Off, a name that leaves no ASCII letter or digit is refused as unsluggable. |
| `slugs.dictionary` | — | \+ plus, # sharp, & and, @ at | Characters spelled out in slugs, so C, C++ and C# stay three tags (c, c-plus-plus, c-sharp). |

## Hierarchy, suggestions and popularity

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `hierarchy.max_depth` | — | 5 | The deepest tree, 1–10 levels (a root is level 1). A type asking for more is clamped to it. |
| `suggest.limit` | — | 10 | Suggestions when a call gives no limit, 1 up to suggest.max\_limit. |
| `suggest.max_limit` | — | 50 | The most suggestions one call returns, 1–1000. |
| `popular.limit` | — | 10 | Popular tags when a call gives no limit, 1 up to popular.max\_limit. |
| `popular.max_limit` | — | 100 | The most popular tags one call returns, 1–1000. |

## API aliases and resources

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `scopes` | — | \[\] | API alias => scope model class, for ?scope=alias:key. Aliases are snake\_case (^\[a-z\]\[a-z0-9\_\]{0,63}$); a class name is never accepted. |
| `taggables` | — | \[\] | API alias => taggable model class, for popular?on=alias. |
| `resources.scope` | — | TagScopeResource::class | The JsonResource a scoped tag’s owner renders through; it receives the scope model. Blank or null = the packaged {id, type}; anything that is not an instantiable JsonResource subclass throws, naming the key. |

## Authorization

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `authorization.abilities.view` | — | null | Gate ability asked before every API read, with the type name and the scope. null = no check. |
| `authorization.abilities.manage` | — | tags.manage | Gate ability asked before every API write, with the type name and the target scope (null = a global write). It also decides who sees private types and — in each tag’s own scope, so a global tag asks for global management — usage counts, translations and deleted tags. Blank = tags.manage. |
| `authorization.abilities.scope` | — | tags.scope | Gate ability asked with the scope model whenever a request passes ?scope=. A denied scope answers 422 unknown\_alias, exactly like a scope that does not exist, so a reference reveals no tenant ids. Blank = tags.scope. |

## JSON API

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `routes.enabled` | `TAGS_ROUTES` | off | Serve the JSON API — off unless you switch it on. Written env('TAGS\_ROUTES') ?: 'off', and a switch that is not set (missing from a published config whose routes array overrides only some keys, null or blank) is off too. |
| `routes.prefix` | — | api | URI prefix; surrounding slashes are trimmed, null = none. |
| `routes.middleware` | — | \['api', 'auth', 'throttle:tags-api'\] | Middleware of every endpoint; with Sanctum use auth:sanctum. tags:doctor warns when no auth middleware is listed. |
| `routes.rate_limit` | — | 120 | Requests a minute per user (per IP for an anonymous caller) on the tags-api limiter, 1–100000. |
| `routes.suggest_rate_limit` | — | 120 | Requests a minute per user (else per IP) on the suggest endpoint’s own tags-suggest limiter. |
| `routes.max_per_page` | — | 100 | The largest list page, 1–1000; the default page size is 20. |
| `routes.max_tree_nodes` | — | 1000 | The largest ?tree=1 answer, 1–100000; a bigger tree answers 422 tree\_too\_large. |

Changing a slug setting later changes how new strings match, not the slugs already stored — run sluggable:regenerate on the tag model afterwards (see Installation).

## Environment

Only the key types and the API switch are env-driven:

```bash
TAGS_PRIMARY_KEY_TYPE=bigint
TAGS_KEY_TYPE=bigint
TAGS_ROUTES=on
```

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