---
title: "Configuration — Posts for Laravel | Roundly"
description: "Every config key and its default — key types, tables, author, locales, slugs, SEO, JSON-LD, media and moderation."
url: https://roundly-consulting.com/open-source/docs/posts-for-laravel/configuration
language: en
---

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

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

# Configuration

Every key has a default and the common ones are env-backed, so the package works without a published config. The published config/posts.php in full:

```php
return [
    'model' => RoundlyConsulting\Posts\Models\Post::class,

    'key_type' => env('POSTS_KEY_TYPE', 'bigint'),                 // your author model's key
    'primary_key_type' => env('POSTS_PRIMARY_KEY_TYPE', 'bigint'), // the posts tables' own ids

    'tables' => [
        'posts' => 'posts',
        'categories' => 'post_categories',
        'category_post' => 'category_post',
        'tags' => 'post_tags',
        'tag_post' => 'post_tag',
    ],

    'author' => [
        'morph-name' => 'author',
        'nullable' => true,
    ],

    'locales' => [
        'fallback' => env('POSTS_FALLBACK_LOCALE', config('app.fallback_locale', 'en')),
    ],

    'slugs' => [
        'source' => 'title',
        'separator' => '-',
        'unique' => true,
        'route-binding' => true,
        'history' => env('POSTS_SLUG_HISTORY', false),
        'lock-when-published' => false,
    ],

    'seo' => [
        'site-name' => env('POSTS_SITE_NAME'),
        'twitter-site' => env('POSTS_TWITTER_SITE'),
        'default-card' => 'summary_large_image', // 'summary' | 'summary_large_image' | 'app' | 'player'
        'default-robots' => 'index,follow',
    ],

    'json-ld' => [
        'type' => 'BlogPosting', // 'BlogPosting' | 'Article' (anything else throws)
        'author-attribute' => 'name',
        'publisher' => [
            'name' => env('POSTS_PUBLISHER_NAME'),
            'logo' => env('POSTS_PUBLISHER_LOGO'),
        ],
    ],

    'media' => [
        'featured_bucket' => 'featured',
        'gallery_bucket' => 'gallery',
        'content_bucket' => 'content',
        'disk' => env('POSTS_MEDIA_DISK'),
        'featured_fallback_url' => env('POSTS_MEDIA_FEATURED_FALLBACK'),
        'responsive_widths' => null,
        'seo_og_image' => true,
        'og_variant' => '',
        'warm_on_publish' => true,
        'inline' => [
            'enabled' => true,
            'default_variant' => '',
            'on_missing' => 'strip', // 'strip' | 'keep' (anything else throws)
        ],
    ],

    'moderation' => [
        'on_resolved' => 'archive', // 'archive' | 'draft' | null — anything else throws
        'auto_unpublish' => true,
    ],
];
```

## Every key

| Key | Default | Env | Purpose |
| --- | --- | --- | --- |
| `model` | Post::class | — | The post model — point it at your subclass to extend it. |
| `key_type` | bigint | `POSTS_KEY_TYPE` | Outbound: your author model’s key type (bigint, uuid or ulid); types author\_id. Anything else throws InvalidConfigurationException. |
| `primary_key_type` | bigint | `POSTS_PRIMARY_KEY_TYPE` | Inbound: the key type of the posts, categories and tags tables and their pivots (bigint, uuid or ulid). Anything else throws InvalidConfigurationException. |
| `tables.posts` | posts | — | Posts table. |
| `tables.categories` | post\_categories | — | Categories table. |
| `tables.category_post` | category\_post | — | Category ⇄ post pivot. |
| `tables.tags` | post\_tags | — | Tags table. |
| `tables.tag_post` | post\_tag | — | Tag ⇄ post pivot. |
| `author.morph-name` | author | — | Morph relation name and column prefix (author\_type / author\_id). |
| `author.nullable` | true | — | Whether a post may have no author. |
| `locales.fallback` | app.fallback\_locale | `POSTS_FALLBACK_LOCALE` | Fallback locale for translation reads and the slug chain. |
| `slugs.source` | title | — | Attribute post slugs are generated from (categories and tags always use name). |
| `slugs.separator` | \- | — | Slug word separator. |
| `slugs.unique` | true | — | Unique per locale (-2, -3, …, trashed rows included); at migrate time also builds the per-locale unique indexes. |
| `slugs.route-binding` | true | — | The slug is the post’s route key; {post} binds along the locale chain. |
| `slugs.history` | false | `POSTS_SLUG_HISTORY` | Remember retired post slugs and 301 old URLs (needs sluggable-migrations). |
| `slugs.lock-when-published` | false | — | Freeze a published post’s slugs; a manual change throws SlugLockedException. |
| `seo.site-name` | null | `POSTS_SITE_NAME` | Default og:site\_name. |
| `seo.twitter-site` | null | `POSTS_TWITTER_SITE` | Default twitter:site handle. |
| `seo.default-card` | summary\_large\_image | — | Default twitter:card type: summary, summary\_large\_image, app or player; anything else throws. |
| `seo.default-robots` | index,follow | — | Default robots directive. |
| `json-ld.type` | BlogPosting | — | schema.org @type: BlogPosting or Article; anything else throws. |
| `json-ld.author-attribute` | name | — | Author-model attribute used as the author name. |
| `json-ld.publisher.name` | null | `POSTS_PUBLISHER_NAME` | Publisher organisation name (publisher omitted when unset). |
| `json-ld.publisher.logo` | null | `POSTS_PUBLISHER_LOGO` | Publisher logo URL. |
| `media.featured_bucket` | featured | — | Single-file featured-image bucket. |
| `media.gallery_bucket` | gallery | — | Multi-file gallery bucket. |
| `media.content_bucket` | content | — | Bucket owning the media referenced inline by \[media:UUID\]. |
| `media.disk` | null | `POSTS_MEDIA_DISK` | Disk for post media (null = media-library default). |
| `media.featured_fallback_url` | null | `POSTS_MEDIA_FEATURED_FALLBACK` | URL featuredImageUrl() returns when no featured image is set (null = ''). |
| `media.responsive_widths` | null | — | Responsive width ladder of positive integers (null = media-library default, \[\] = no variants); any other entry throws. |
| `media.seo_og_image` | true | — | Fall back og:image and the JSON-LD image to the featured image. |
| `media.og_variant` | '' | — | Variant used for that fallback ('' = the original) — one media-library generates for the featured bucket, e.g. responsive-640; until it exists, or for an unknown name, the original’s URL is used. |
| `media.warm_on_publish` | true | — | Queue variant generation for the post’s media on publish. |
| `media.inline.enabled` | true | — | Expand \[media:UUID\] tokens in renderContent(). |
| `media.inline.default_variant` | '' | — | Variant for tokens without \|variant; like a token’s own, an unknown or not-yet-generated one renders the original image. |
| `media.inline.on_missing` | strip | — | strip or keep a token whose media the post does not own; anything else throws. |
| `moderation.on_resolved` | archive | — | Action when a report against a published or scheduled post is upheld: archive, draft, or null or blank (off). A typo throws instead of skipping the auto-unpublish. |
| `moderation.auto_unpublish` | true | — | Archive a published or scheduled post that crosses the global reports.threshold. |

## Environment

The site-specific values are env-driven, so most apps never publish the config:

```bash
POSTS_KEY_TYPE=bigint
POSTS_PRIMARY_KEY_TYPE=bigint
POSTS_FALLBACK_LOCALE=en
POSTS_SLUG_HISTORY=false
POSTS_SITE_NAME="Example Blog"
POSTS_TWITTER_SITE=@example
POSTS_PUBLISHER_NAME="Example Ltd"
POSTS_PUBLISHER_LOGO=https://example.com/logo.png
POSTS_MEDIA_DISK=public
POSTS_MEDIA_FEATURED_FALLBACK=https://example.com/images/placeholder.png
```

## Strict values

Every bool switch is read strictly: true/1/on/yes turn it on, false/0/off/no turn it off, a blank value (POSTS\_SLUG\_HISTORY=) is not set so the default applies, and anything else (say POSTS\_SLUG\_HISTORY=disabled) throws RoundlyConsulting\\PackageToolkit\\Exceptions\\InvalidConfigurationException instead of quietly reading as the default.

Every other setting is just as strict. A setting that is not set — absent, null, or blank like a host’s POSTS\_SITE\_NAME= — takes its default, and an optional one (the SEO / JSON-LD publisher values, the media disk or fallback URL) stays unset. A string setting — a table or bucket name, the morph name, the slug source or separator, a locale, an SEO or JSON-LD value, the media disk or fallback URL — must be a string when set: a non-string value throws rather than being cast to '' or replaced by the default. A seo.default-card, json-ld.type, media.inline.on\_missing or moderation.on\_resolved value outside its list throws, and so does a responsive width that isn’t a positive integer. The variant names — media.og\_variant and media.inline.default\_variant — take any string, '' meaning the original.

## Checking the setup

```bash
php artisan about
```

php artisan about gets a Posts section: the model’s base name, both key types, whether an author is optional, the table count (DEFAULT or CUSTOMISED — a blank table name counts as the default the models use, a junk one shows INVALID), route binding, unique slugs, slug history and lock, the media disk (SET or MEDIA DEFAULT), inline media, warm-on-publish, SEO and JSON-LD presence, and the moderation switches. It never prints a configured value such as a table name, disk, publisher or site name. A broken media disk, inline-media, SEO, JSON-LD or moderation setting shows as INVALID.

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