A Agentbar docs

Run your workspace

Pluggable marketing themes

Pluggable marketing themes

The whole marketing site โ€” home, pricing, how-it-works, integrations, privacy, terms, changelog โ€” is a swappable theme. Operators add a new theme by dropping a folder; switching is one click in Settings → System → Marketing.

How it works

Every marketing controller looks up its Inertia component through App\Support\MarketingTheme::component($page) instead of hardcoding a string. The resolver reads app_settings.marketing_theme, falls back to harvest (the built-in theme) when the column is empty or points at a slug that isn't installed, and returns either:

  • The legacy component path for harvest (welcome, marketing/pricing, marketing/how-it-works, etc.) โ€” so existing installs keep rendering the original layout.
  • marketing-themes/{slug}/{page} for any other theme โ€” points Inertia at a self-contained per-theme folder.

Add a new theme

  1. Create a folder under resources/js/pages/marketing-themes/ named after the theme's slug (kebab-case, no spaces). For example, resources/js/pages/marketing-themes/meadow/.

  2. Add a theme.json manifest at the root of that folder. Minimum shape:

    {
        "name": "Meadow",
        "description": "Calm, editorial layout with green accents."
    }

    Themes without a theme.json are ignored โ€” the manifest is what makes a folder a theme.

    If a theme only ships a subset of the seven pages, add a pages whitelist so the resolver knows which keys the theme provides โ€” every other page silently falls back to the Harvest legacy component:

    {
        "name": "Aurora",
        "description": "Editorial brutalist โ€” home page only.",
        "pages": ["home"]
    }

    Omitting pages means the theme is assumed to provide all seven; useful when you're shipping a full bundle.

  3. Drop the seven page components inside the folder, each receiving the same props the matching controller passes today. Names must match exactly:

    • home.tsx
    • pricing.tsx
    • how-it-works.tsx
    • integrations.tsx
    • privacy.tsx
    • terms.tsx
    • changelog.tsx

    Use resources/js/layouts/marketing-shell.tsx as the shared shell, or ship your own per-theme shell inside the folder.

  4. Run npm run build (or keep npm run dev running while you iterate) so Vite picks up the new files.

  5. Open Settings → System → Marketing, pick the new theme from the dropdown, and save. Visit /, /pricing, etc. โ€” they now render from your folder.

Props your theme components receive

The controllers pass the same props regardless of theme. Build your page components against this shape and a theme swap is a pure visual change:

PageNotable props
homecanRegister, demoAgentId, content, seo
pricingplans, lifetime_plans, currency, currencies, matrix, faqs, shell, brand, seo
how-it-workssteps, latency, shell, brand, seo
integrationsnative, data_sources, roadmap, shell, brand, seo
privacycontent, shell, brand, seo
termsintro, sections, effective_date, contact_email, shell, brand, seo
changelogentries, shell, brand, seo

Built-in: the Harvest theme

The shipped theme is called harvest. For back-compat its files live where they always did (resources/js/pages/welcome.tsx + resources/js/pages/marketing/*.tsx) rather than under marketing-themes/harvest/. The resolver maps to those legacy paths so existing installs upgrade with no rendering difference.

Shipped extra themes

Two additional themes ship in the box. Both are full bundles covering all seven pages and use the live demo agent for the hero chat preview.

  • Aurora (slug aurora) โ€” editorial brutalist with a paper/ink palette and an electric-lime signal accent. Lives at resources/js/pages/marketing-themes/aurora/. Ships auth-shell.tsx so login, register, and password-reset flows render in the same paper/ink/lime palette as the marketing site.
  • Prism (slug prism) โ€” purple/coral gradient identity, Inter Tight body with Instrument Serif italic accents, glossy hero mockup with floating context cards, gradient-bar footer. Lives at resources/js/pages/marketing-themes/prism/. Also ships auth-shell.tsx for theme-matched sign-in.

Prism try-now demo (anonymous URL ingest)

Prism's hero ships an interactive Try it form. A visitor pastes a URL, the server fetches the page synchronously (POST /api/v1/widget/try-now), extracts readable text via HtmlExtractor, chunks it, and stashes the chunks under a short-lived cache token (1h TTL). The hero chat then switches to that cached context โ€” every visitor message routes through POST /api/v1/widget/try-now/stream, which streams an LLM reply grounded in the cached chunks via <source> tags.

Lives at app/Services/TryNow/TryNowSession.php and app/Http/Controllers/Widget/TryNowController.php. No agent, no workspace, no DB writes โ€” it can't pollute tenant data. Rate-limited per IP via the try-now-start and try-now-stream limiters defined in AppServiceProvider::configureRateLimiting.

Theme-matched auth shells

Both Aurora and Prism ship an auth-shell.tsx alongside their seven marketing pages. The dispatcher at resources/js/layouts/auth-layout.tsx picks the right shell based on marketingTheme (the shared Inertia prop). When the active theme doesn't ship a shell, the dispatcher falls back to the default Harvest two-panel layout. To add a new theme's auth shell:

  1. Create resources/js/pages/marketing-themes/<slug>/auth-shell.tsx exporting a component with { title, description, children } props.
  2. Add a branch in auth-layout.tsx: if (marketingTheme === '<slug>').
  3. Add a Pest test under tests/Feature/Marketing/ that hits /login and /register with the theme active.

Flip between them under Settings → System → Marketing, or via tinker:

php artisan tinker --execute 'App\Models\AppSetting::singleton()->forceFill(["marketing_theme" => "prism"])->save();'

Resetting if a theme breaks

If a theme's folder is deleted, its manifest becomes invalid, or the slug stored in app_settings.marketing_theme doesn't match any installed theme, the resolver silently falls back to harvest. The marketing site can't be blanked by a stale setting. To reset explicitly, run:

php artisan tinker --execute 'App\Models\AppSetting::singleton()->forceFill(["marketing_theme" => "harvest"])->save();'