Installation Guide
This guide covers installing medialight/textstem-laravel into a new or existing Laravel application.
Requirements
PHP and Laravel
| Requirement | Version |
|---|---|
| PHP | >= 8.3 |
| Laravel | 10, 11, 12, or 13 |
| Livewire | ^4.0 |
| Database | MySQL 8+ or PostgreSQL 13+ recommended |
PHP Extensions
The following PHP extensions must be enabled:
| Extension | Used for |
|---|---|
gd or imagick |
Image processing via Glide |
pdo |
Database access |
fileinfo |
File type detection on upload |
zip |
Export commands |
System Packages
| Package | Used for |
|---|---|
| Node.js + npm (or Bun) | Compiling Jetstream frontend assets |
Node.js 18+, puppeteer (npm), headless Chrome and its system libraries |
Optional: live page screenshots in the Pages admin (Step 13) |
Step 1 -- Add the Repository
The package is hosted on a private GitHub repository. Add it to the repositories array in your host app's composer.json before requiring it:
"repositories": [
{
"type": "vcs",
"url": "git@github.com:MeccaMedialight/textstem-laravel.git"
}
]
Ensure your machine or CI environment has SSH access to that repository via a deploy key or personal access token.
Step 2 -- Install via Composer
composer require medialight/textstem-laravel
Laravel's package auto-discovery registers all service providers and facade aliases automatically. No manual entry in config/app.php is needed.
Step 3 -- Run the Post-Install Wizard
php artisan textstem:post-install
The wizard prompts for an installation type and then publishes all required files:
| Option | What it does |
|---|---|
| Vanilla - no Jetstream | Skips Jetstream installation |
| Jetstream with Livewire | Runs jetstream:install livewire |
| Jetstream with Inertia | Runs jetstream:install inertia (Vue) |
If you want the package's own optional React rendering mode for public pages (see Step 12), none of these three options set that up on their own -- Step 12 explains what a React frontend actually requires.
After the stack choice is confirmed, the wizard publishes:
config/textstemapp.php,config/openai.php,config/chunk-upload.php(tag:textstem-config)- Wrangler views to
resources/views/wrangler/(tag:textstem-components) - Auth views to
resources/views/auth/(tag:textstem-components) - JS/CSS assets to
public/vendor/medialight/textstem/including TinyMCE (tag:public) - Jetstream views (tag:
jetstream-views)
Build Frontend Assets (Jetstream installs only)
If you chose a Jetstream stack, compile the frontend before continuing:
npm install && npm run build
# or, if you use Bun:
bun install && bun run build
Step 4 -- Configure Sanctum
The API uses Laravel Sanctum for token authentication. Jetstream installs Sanctum automatically. If you chose the Vanilla option, publish the Sanctum config and migrations manually:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
If you need cookie-based (stateful) API authentication, add your app's domain to the stateful array in config/sanctum.php:
'stateful' => [
'localhost',
'your-app.test',
env('APP_URL'),
],
Token-based (Bearer) API requests work without this change.
Step 5 -- Configure Environment
Ensure your .env contains a working database connection before running migrations:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database
DB_USERNAME=your_user
DB_PASSWORD=your_password
Then add the package-specific variables below. All have defaults in config/textstemapp.php; only set what your app needs to change.
Core
# Email address(es) that receive contact form notifications (comma-separated)
CONTACT_NOTIFICATIONS_EMAIL=you@example.com
# Set to true to redirect all outbound mail to CONTACT_NOTIFICATIONS_EMAIL instead of real recipients
EMAIL_TEST_MODE=false
# Items per page in admin list views
PAGINATION_ITEMS_PER_PAGE=10
# Storage disk for uploaded assets (matches a key in config/filesystems.php)
ASSET_DISK=public
Page Cache
WRANGLER_CACHE_ENABLE=false
WRANGLER_CACHE_TTL=3600
# HTTP cache-control header value: public or private
WRANGLER_CACHE_MODE=private
WRANGLER_CACHE_WITHQUERY=false
TinyMCE
# Leave blank to use the locally published TinyMCE build (no account required)
TINYMCE_API_KEY=
OpenAI / AI Features
OPENAI_API_KEY=sk-...
OPENAI_ENABLED=true
OPENAI_USE_QUEUE=true
OPENAI_MODEL=gpt-4o-mini
Google Cloud Vision
Image comparison and analysis features use Google Cloud Vision. Provide a service account credentials file:
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
If you do not use image AI features, set textstemapp.images.comparison_driver to fingerprint in config/textstemapp.php to skip Cloud Vision entirely.
Accessibility Analysis
ACCESSIBILITY_ENABLED=true
ACCESSIBILITY_USE_QUEUE=true
ACCESSIBILITY_WCAG_LEVEL=AA # A, AA, or AAA
ACCESSIBILITY_CHECK_ON_SAVE=false
ACCESSIBILITY_STORE_REPORTS=true
ACCESSIBILITY_REPORT_RETENTION_DAYS=30
SEO Analysis
SEO_ENABLED=true
SEO_USE_QUEUE=true
SEO_USE_AI=true
SEO_CHECK_ON_SAVE=false
SEO_STORE_REPORTS=true
SEO_REPORT_RETENTION_DAYS=30
File Uploads
TEXTSTEM_ALLOWED_EXTENSIONS=jpg,jpeg,png,gif,svg,webp,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip,mp3,wav,mp4,mov,avi,wmv
TEXTSTEM_MAX_FILE_SIZE=10240 # kilobytes; default is 10 MB
TEXTSTEM_STORAGE_PATH=public/uploads
TEXTSTEM_UPLOADS_USE_QUEUE=false
TEXTSTEM_UPLOADS_ENABLE_FINGERPRINT=true
TEXTSTEM_UPLOADS_GENERATE_THUMBNAILS=true
TEXTSTEM_UPLOADS_EXTRACT_METADATA=true
Redis (optional)
TEXTSTEM_USE_REDIS=false
Set to true if your environment has Redis available. The package will use it for caching and queues.
Admin UI
DARK_MODE=false
Step 6 -- Run Migrations
Installing into an existing app? If your app already has a
userstable, the package migration2014_10_12_000000_create_users_table.phpwill conflict and fail. Skip it by inserting the migration filename into themigrationstable manually, then runphp artisan migrate. Thetextstem:migrations:synccommand can do this in bulk -- see the artisan commands reference.
Run all outstanding migrations:
php artisan migrate
This creates the following tables (in addition to Laravel defaults):
| Table | Description |
|---|---|
users |
Extended user model with roles and Jetstream traits |
options |
Global site options |
events |
Event records |
previews |
Page preview snapshots |
categories |
Shared category model for pages, posts, and assets |
wrangler_pages |
Page-builder pages |
wrangler_components |
Page components |
assets |
Media asset library |
posts |
Blog/news posts |
post_collections |
Post groupings |
redirects |
URL redirect rules |
tags / taggables |
Spatie tag tables |
messages |
In-app messages |
roles / permissions |
Spatie permission tables |
personal_access_tokens |
Sanctum API tokens |
prompts |
AI prompt library |
seo_reports |
Stored SEO analysis results |
accessibility_reports |
Stored accessibility analysis results |
orchestration_* |
AI orchestration state tables |
Step 7 -- Set Up Asset Storage
Run Laravel's storage link command so uploaded files in the public disk are accessible via the web:
php artisan storage:link
For S3 storage, configure the s3 disk in config/filesystems.php and set ASSET_DISK=s3 in .env.
Step 8 -- Set Up Queues
AI generation, accessibility and SEO analysis, and asset processing jobs are dispatched to a queue. Configure a real queue driver for production:
QUEUE_CONNECTION=redis # or database, sqs, etc.
Start a worker:
php artisan queue:work --queue=default --tries=3
For production, manage the worker process with Supervisor or Laravel Horizon.
Step 9 -- Register the Scheduler
The package registers scheduled commands automatically via the service provider. Add the scheduler call to your server crontab so they execute:
* * * * * cd /path-to-your-app && php artisan schedule:run >> /dev/null 2>&1
Commands the package schedules:
| Command | Schedule | Description |
|---|---|---|
textstem:prune-reports |
Daily at 03:00 | Removes old SEO and accessibility reports |
textstem:health-check |
Daily at 01:00 | Checks application health |
cache:manage clear --warm-up |
Mondays at 01:30 | Clears and re-warms the cache |
Step 10 -- Create a Super Admin User
Do not skip this step. Registering a user through your app's normal sign-up flow does not make them an admin. Without a role that passes the
access-textstem-admingate, every/textstemroute returns 403 -- even immediately after a successful login. This is the single most common cause of "I logged in but everything 403s."
Register a user through your app's registration flow, then create the super-admin role (if it does not already exist) and assign it -- by email, not by ID, since find(1) can silently grab the wrong user if more than one already exists:
use Medialight\Textstem\Models\User;
use Spatie\Permission\Models\Role;
$role = Role::firstOrCreate(['name' => 'super-admin']);
$user = User::where('email', 'you@example.com')->firstOrFail();
$user->assignRole($role);
This can be run in Tinker (php artisan tinker) or placed in a seeder.
Super-admin users bypass all gate checks and have unrestricted access to the admin panel.
Step 11 -- Seed Sample Data (optional)
php artisan textstem:dbseed
Creates sample pages, posts, categories, and assets to give the admin panel something to work with immediately.
Verification
After completing the steps above, confirm the following:
public/vendor/medialight/textstem/exists and contains JS/CSS filesresources/views/wrangler/exists withcomponents/,pages/,posts/, andtemplate-commands/subdirectoriesconfig/textstemapp.phpexists in the host appphp artisan migrate:statusshows all package migrations asRan- The admin panel loads at
/textstemwithout errors /textstem/dashboardis accessible after logging in as the super-admin user
If you completed Step 12, also confirm:
resources/js/wrangler/,resources/js/layouts/public-layout.tsx, andresources/js/pages/textstem/page.tsxexistTEXTSTEM_REACT_RENDERING=trueis set in.env- A public page loads through Inertia (check the response for
X-Inertiaon a client-side navigation, or that the page renders via a.tsxcomponent in your browser's dev tools) rather than a server-rendered Blade page - The admin panel at
/textstemis unaffected -- still Blade/Livewire
If you completed Step 13, also confirm:
php artisan textstem:page-screenshots --checkends with "Test screenshot taken"- After
--all, the Pages admin's Cards view shows screenshots of your pages
Notes
User Model
The service provider sets auth.providers.users.model to Medialight\Textstem\Models\User
by default (Laravel's standard user model plus Spatie role/permission support and
Jetstream traits).
If your app needs its own user model instead -- for example, to attach Spatie roles
directly to App\Models\User -- set user_model in config/textstemapp.php:
'user_model' => \App\Models\User::class,
No changes to App\Models\User itself or to AppServiceProvider are needed; your
own model does not need to extend the package's.
Localization Middleware
The Localization middleware is appended to the web group automatically by the service provider. It reads a locale from the session and calls app()->setLocale(). No manual registration is needed.
Re-publishing Assets
If you update the package or suspect published files are out of date, re-run:
php artisan textstem:refresh-assets
This clears public/vendor/medialight/textstem/, re-publishes the JS/CSS and TinyMCE build, and ensures the required Wrangler view directories exist.
Next steps
- Optional Features — Install the optional extras: live page screenshots, React rendering for public pages and Form Data.
- Deploying on Forge — Deploy a Textstem site on Laravel Forge: PHP versions, the deploy script, queue workers, the scheduler and live page screenshots.
- Troubleshooting — Fixes for common problems after installing Textstem, from admin 403s to screenshot errors.
- Updating the Package — Update the Textstem package and the four commands to run after every update.