Operate
Subfolder install
Pitchbar can run under a subfolder of your domain rather than a
dedicated subdomain โ useful when you're sharing a domain with
other services (marketing site at the root, Pitchbar at
/app, status page at /status). Laravel
+ Vite handle this with config only; no code changes.
The walkthrough below assumes you want Pitchbar at
https://aichat.com/app. Substitute your own host +
path everywhere you see those values.
1. Set APP_URL
Open .env on the production server. Set:
APP_URL=https://aichat.com/app
# Session + Sanctum need the host (without the path).
SESSION_DOMAIN=aichat.com
SANCTUM_STATEFUL_DOMAINS=aichat.com
# Reverb websocket origin โ same host:port as APP_URL.
REVERB_HOST=aichat.com
REVERB_PORT=443
REVERB_SCHEME=https
Run php artisan config:clear + php artisan
route:clear after editing.
2. Point the docroot at public/
The Pitchbar repo is a Laravel application; the public docroot
must be public/, not the repo root. With a subfolder
install your web server needs to map the subpath to that
directory.
Apache (.htaccess)
# /var/www/aichat.com/app -> Pitchbar repo
# /var/www/aichat.com/app/public is the docroot for /app.
Alias /app /var/www/aichat.com/pitchbar/public
<Directory /var/www/aichat.com/pitchbar/public>
AllowOverride All
Require all granted
</Directory>
Nginx
server {
listen 443 ssl http2;
server_name aichat.com;
root /var/www/aichat.com/marketing-site; # your root site
# Pitchbar subfolder.
location ^~ /app/ {
alias /var/www/aichat.com/pitchbar/public/;
try_files $uri $uri/ @pitchbar;
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_param SCRIPT_NAME /app/index.php;
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
}
}
location @pitchbar {
rewrite ^/app(/.*)$ /app/index.php?$1 last;
}
}
3. Vite assets
Vite reads APP_URL at build time to write the
correct manifest paths, so the admin SPA + widget bundle resolve
under the subfolder automatically. After deploy run:
npm ci
npm run build
npm run build:widget
Check public/build/manifest.json โ the entries
should resolve under /app/build/... when served.
4. Storage symlink
php artisan storage:link
Creates public/storage โ storage/app/public.
Uploaded branding logos, public exports, and avatar files land
there; without the symlink they 404.
5. Widget embed snippet
Customers embed the widget via a <script> tag
on their own site. The URL must point at your subfolder:
<script
src="https://aichat.com/app/widget/widget.js"
data-agent-id="agent_..."
defer
></script>
The admin's "Copy embed snippet" button on
/app/agents/{id} derives the URL from
APP_URL, so as long as step 1 is correct the snippet
your customers copy will already include the subfolder.
6. Queue worker + scheduler
Both still run from the repo root, unaffected by the subfolder:
php artisan queue:work
php artisan schedule:work
The Cloudflare Worker cron driver (production default) hits
https://aichat.com/app/api/v1/internal/queue-tick โ
update the worker's QUEUE_TICK_URL binding accordingly
when you deploy it from /admin/integrations/cron-worker.
Troubleshooting
Admin SPA 404s on every route except /app
The web-server rewrite isn't sending Laravel's URLs back to
index.php. Confirm the Apache .htaccess
inside public/ is being read (Apache:
AllowOverride All) or the Nginx
@pitchbar fallback fires (Nginx:
try_files ordering).
Widget bundle 404
The widget post-build emits a hashed filename
(widget.<hash>.js). The
data-agent-id snippet points at the unhashed
widget.js which is also published. If that 404s,
you skipped npm run build:widget after deploy.
Session cookie not setting on subdomain hosts
If you serve the marketing site at aichat.com and
Pitchbar at aichat.com/app, both share the same
cookie host but different paths. Set SESSION_PATH=/app
in .env so the Laravel session cookie is scoped to
the subfolder โ without this the cookie set by Pitchbar may be
overwritten by the parent site's session library.
CSP / mixed content
The default CSP in AddSecurityHeaders middleware
allows self only for scripts. If you host static
assets on a CDN, add the CDN host to script-src
via the app_security.csp_extra_script_src config
key.
Reverb websocket fails to upgrade
Reverb listens on its own port (default 8080). Your reverse proxy must proxy the WebSocket upgrade โ for Nginx that's:
location /reverb/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
What this does NOT change
- Webhook endpoints (Stripe, PayPal, Razorpay, WordPress plugin)
derive their URL from
APP_URL, so they automatically include the subfolder. Re-register them in the respective dashboards if you migrated from a different host. - The widget JWT still binds to
allowed_originson the agent โ that's the origin of the customer's site, NOT your Pitchbar host. Subfolder install doesn't affect which sites can embed. - Multi-tenant scoping, billing gates, BYOK resolution, and SSE hot-path latency are all unaffected.