A Agentbar docs

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_origins on 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.