Laravel package that serves robots.txt, sitemap.xml, and llms.txt from imported pSEO campaign content — with a built-in password-protected upload panel.
Zero project coupling. Install via Composer, set two .env values, done.
composer require pseo/laravel-pseoService provider is auto-discovered. No manual registration needed.
# Optionally publish views to customize them:
php artisan vendor:publish --tag=pseo-viewsPSEO_SITE_URL=https://yoursite.com
PSEO_UPLOAD_PASSWORD=your_secret_passwordThat's it. The panel is live at /pseo-upload and all three endpoints are registered.
# ── Required ───────────────────────────────────────────────────────────────────
# Absolute URL of your site — used in sitemap.xml, llms.txt, robots.txt Sitemap directive
PSEO_SITE_URL=https://yoursite.com
# Password for the upload panel at /pseo-upload
# Accepts plain text OR a bcrypt hash:
# php artisan tinker --execute="echo Hash::make('secret');"
PSEO_UPLOAD_PASSWORD=your_secret_password
# ── Optional (defaults shown) ──────────────────────────────────────────────────
# URL path for the upload panel (default: /pseo-upload)
# PSEO_UPLOAD_PATH=/pseo-upload
# Storage disk — any disk in config/filesystems.php (default: app default disk)
# PSEO_DISK=local
# Relative path on disk for campaign pages (default: pseo)
# Layout: {disk_root}/{PSEO_CONTENT_PATH}/pages/{pillar,supporting,research}/
# Empty string = use disk root directly
# PSEO_CONTENT_PATH=pseo
# URL prefix for imported page slugs (default: /learn)
# PSEO_LINK_PREFIX=/learn
# Cache TTL in seconds for generated files (default: 3600, 0 = disabled)
# PSEO_CACHE_TTL=3600
# llms.txt header (defaults: APP_NAME / empty)
# PSEO_LLMS_NAME=
# PSEO_LLMS_DESCRIPTION=
# Disable if you want to serve article pages yourself
# PSEO_CONTENT_ROUTES=true
# Layout component for article pages (default: layouts.app)
# Publish views to customize: php artisan vendor:publish --tag=pseo-views
# PSEO_VIEW_LAYOUT=layouts.app
# Route names (defaults derived from PSEO_LINK_PREFIX: learn.index, learn.show)
# PSEO_ROUTE_NAME_INDEX=learn.index
# PSEO_ROUTE_NAME_SHOW=learn.showAfter install the package automatically registers:
| Route | Description |
|---|---|
GET /robots.txt |
Generated robots.txt |
GET /sitemap.xml |
Generated from imported markdown pages |
GET /llms.txt |
Generated from imported markdown pages |
GET /pseo-upload |
Password-protected upload panel |
GET /learn |
Article index page |
GET /learn/{slug} |
Single article page |
- Open
/pseo-uploadin your browser. - Enter the password.
- Upload a campaign ZIP and click Import.
php artisan pseo:import /path/to/campaign.zip
php artisan pseo:import /path/to/campaign.zip --force # overwrite existing files- Copies
pillar/*.md,supporting/*.md,research/*.md→{disk}/{content_path}/pages/{type}/ - Copies
schema/*.json→{disk}/{content_path}/schema/ - Rewrites internal links from
/slugto{link_prefix}/slug - Merges
sitemap.xmlentries (deduped by<loc>) - Merges
llms.txtlinks under## Imported Pages(deduped by URL) - Writes
robots.txtonly if it doesn't already exist - Fixes malformed YAML front matter automatically
Pre-generate files for direct web-server serving (bypasses PHP routing):
php artisan pseo:build # all three
php artisan pseo:build sitemap llms # subsetFor custom robots.txt rules, sitemap priorities, or llms.txt sections, publish the config:
php artisan vendor:publish --tag=pseo-configThis creates config/pseo.php in your project. Edit as needed — .env values always take precedence.
Note: If you set
PSEO_UPLOAD_PASSWORDafter runningphp artisan route:cache, runphp artisan route:clearfirst — upload routes are only registered when the password is set.
Clear after importing:
php artisan cache:forget pseo.robots
php artisan cache:forget pseo.sitemap
php artisan cache:forget pseo.llms- PHP 8.2+
- Laravel 10, 11, or 12
ext-zip
MIT