Installation Guide
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 |
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
Register a user through your app's registration flow, then create the super-admin role (if it does not already exist) and assign it:
use Medialight\Textstem\Models\User;
use Spatie\Permission\Models\Role;
$role = Role::firstOrCreate(['name' => 'super-admin']);
$user = User::find(1);
$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.
Step 12 -- Optional: Set Up a React/Inertia Site
Public pages render via Blade/Livewire by default. The package also ships an optional Inertia/React rendering mode for public pages -- the admin panel stays Blade/Livewire either way; this only changes how visitors see public pages rendered. Skip this step entirely for a Blade/Livewire-only site.
Prerequisites
An Inertia + React frontend. Jetstream's own "Jetstream with Inertia" option in Step 3
installs a Vue-based Inertia stack (@inertiajs/vue3) -- it is not a path to
React, and choosing it does not help here. Instead:
- New app: start with Laravel's official React starter kit
(
laravel new your-app --react, orcomposer create-project laravel/react-starter-kit) before adding this package, then use the "Vanilla - no Jetstream" option in Step 3 -- the starter kit's own auth scaffolding replaces Jetstream's. - Existing app: add Inertia/React by hand --
@inertiajs/react,react,react-dom, and@vitejs/plugin-reactinpackage.json,inertiajs/inertia-laravelvia Composer, and the React plugin wired intovite.config.js. Follow Inertia's own React setup guide for the exact steps -- there's no artisan command that does this the wayjetstream:installdoes for Vue. laravel/fortify-- used for a role-aware post-login redirect; already present via either path above (the React starter kit depends on it directly; Jetstream, if installed for its Livewire/API/teams support, pulls it in too).
laravel/jetstream itself is always present regardless -- it's a hard Composer
dependency of this package (its default User model uses Jetstream's
HasProfilePhoto trait), independent of whether you ever run jetstream:install.
Need team management? The React starter kit's auth scaffolding is single-user only -- no team switching, no invitations. Jetstream's Vue stack is the only ready-made implementation of that feature in this ecosystem; there's no React version of it. If your React site needs teams, either run
php artisan jetstream:install inertia --teamsand port its team-switching UI to React by hand, or build teams yourself. There's no shortcut here -- factor this in before committing to a React frontend if teams are a hard requirement.
Install
php artisan textstem:install-react
This publishes the React source under resources/js/ -- the rendering engine, a
Prose component and default page template matching the package's one bundled
Blade component and default template, the page shell, and TypeScript types -- and, if
you confirm, appends TEXTSTEM_REACT_RENDERING=true to your .env.
Two things it can't do for you, since they touch files you own and customize:
-
In
resources/js/app.tsx, route pages namedtextstem/*to the published layout:import PublicLayout from '@/layouts/public-layout'; // ... layout: (name) => { if (name.startsWith('textstem/')) { return PublicLayout; } // ... your existing cases }, -
In your own
resources/js/types/global.d.ts, add thetextstemprop to yoursharedPagePropsdeclaration (this can't be a drop-in augmentation file -- TypeScript doesn't merge two separate inline shapes assigned to the same property across files):import type { TextstemSharedProps } from '@/types/textstem-shared'; // ... declare module '@inertiajs/core' { export interface InertiaConfig { sharedPageProps: { // ... your existing keys textstem: TextstemSharedProps; [key: string]: unknown; }; } }
Then confirm @inertiajs/react, react, and react-dom are in package.json (see
Prerequisites above) and build:
npm run build
# or, for development:
npm run dev
What changes
| Concern | Blade/Livewire (default) | React/Inertia (react_rendering: true) |
|---|---|---|
| Public page rendering | WranglerPageController |
ReactPageController |
| Admin panel | Blade/Livewire | Unchanged -- Blade/Livewire |
| Post-login redirect | config('fortify.home') for everyone |
Role-aware: the dashboard route for a user who can access-textstem-admin, / for everyone else |
| Admin bar (edit-this-page overlay) | <x-textstem::adminbar/>, only reflects the page you loaded, not later client-side navigation |
React AdminBar, driven by a shared Inertia prop -- stays correct across client-side navigation |
| Page-props cache | The package's own PageCache (raw HTML) |
A separate cache keyed on path + query, reusing the same textstemapp.cache.* config |
Adding your own components and page templates
Add components and templates the same way you would for Blade: create the PHP class
(App\Wrangler\Components\{Name}), then add a matching React component
(resources/js/wrangler/components/{Name}.tsx) and register it in
components/index.ts. ActiveComponent::build() (not render()) is what the React
path calls -- a component that doesn't override it already gets a safe default (its
raw config as data), the same as it does today. See docs/react-rendering.md for
full details, and docs/Notes-wranglercomponents.md for the PHP side.
Configuration
config/textstemapp.php:
'react_rendering' => env('TEXTSTEM_REACT_RENDERING', false),
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
Updating the Package
composer update medialight/textstem-laravel
php artisan view:clear
php artisan migrate
php artisan textstem:refresh-assets
view:clear matters even when nothing looks wrong: Laravel's compiled Blade cache is keyed by file path, not content hash, so an updated package template can silently keep serving the pre-update compiled version until the cache is cleared -- this has caused Alpine/Livewire components to reference stores or handlers that exist in the new source but not in the stale compiled output.
textstem:refresh-assets is also required for any update that touches package CSS/JS (e.g. new Tailwind utility classes) -- public/vendor/medialight/textstem/style.css and textstem.js are prebuilt and committed to the package, not regenerated by the host app's own asset build.
If you completed Step 12, an update can also touch the published React source. Re-publish it and rebuild:
php artisan vendor:publish --tag=textstem-react --force
npm run build
Review any files you've since customized (components/index.ts, page-templates/index.ts,
public-layout.tsx) against the freshly-published versions before overwriting -- --force
does not merge.
Review config/textstem.php in the package against your published config/textstemapp.php after each update and merge in any new keys.
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.