Skip to main content

Agency Dashboard

Audience: Agency-success / Sales / Support · Where in app: /agency (route name agency-dashboard) · Plan availability: Agency tier (verify specifics)

The dashboard is the landing screen of the agency portal. It answers two questions at a glance: how many client sub-accounts do I have left to install? and how do I add the next one? It is intentionally light on analytics — the heavy lifting (per-client metrics, revenue) lives in the companies, subscriptions, and admin revenue screens. Think of it as the agency owner's capacity gauge plus a launcher.

What it does

  • Shows the agency's sub-account capacity: total slots, used, and available.
  • Offers three setup paths for adding a client: install on the agency's own website (self-account), connect a GoHighLevel agency account, or create a standard client account.
  • Surfaces a welcome / empty state for brand-new agencies that have bought no sub-accounts yet.
  • Provides quick links to the full companies list and the create-company flow.
  • Handles the GHL OAuth return — when GoHighLevel redirects back after an install, the dashboard reads the callback query params and routes the user on.

How it works

The screen is driven entirely by the agency object in the app store (appStore.agency), which is loaded with the user session. There is no dedicated dashboard metrics endpoint — every number on this page is read from fields already on the agency record:

  • total_sub_accounts — slots granted by the agency's current package.
  • available_sub_accounts — slots not yet consumed by a client company.
  • Used is computed on the client as total − available.

If total_sub_accounts === 0, the page renders the empty "Welcome / buy sub accounts" state instead of the main content, with a single CTA to /agency/buy-sub-account (which redirects to the subscription plans tab — see Subscriptions).

The GHL connection state is read from agency.go_high_level.access_token (truthy = connected). The agency's own self-account state is read from appStore.company._id && appStore.company.agency_self_account.

Source: frontend/src/views/agency/Dashboard.vue.

Configuration & options

Sub-account capacity card

ElementSourceNotes
Used Accountstotal_sub_accounts − available_sub_accountsDark teal segment of the bar
Available Accountsagency.available_sub_accountsLight teal segment
Total Accountsagency.total_sub_accountsSum line
Progress barcomputed percentagesLabels hide below ~15–25% width

Setup options card

OptionVisible whenActionPermission gate
Install on your own websitealwaysOpens "Setup own sub-account" modal (email field) → setupOwnCompany({ email })POST /agencies/setup-company; on success swaps the session to the new company and pushes to onboardingUnlink button only for agency owner (appStore.isAgencyOwner)
Connect with your GHL Agency AccountagencyHasPermission('agency.manage_settings')Opens GhlInstallModalinstallGhl()POST /go-high-level/install-url/agency, then redirects to the returned GHL OAuth URLHidden for staff lacking agency.manage_settings
Create Standard Client AccountalwaysRouter link to /agency/companies/create

Each option shows a green check + an Unlink / Uninstall button once that path is already set up:

  • Unlink self accountunlinkSelfAccount(companyId)PUT /company/unlink/:id (owner only).
  • Uninstall GHLuninstallGhl(agency._id)POST /go-high-level/uninstall/agency (confirm dialog first).

Stats strip

Three cards: Total Sub-Accounts, Used Sub-Accounts, Available Sub-Accounts — all derived from the same three numbers above. No separate API call.

GHL callback handling (onMounted)

Query params on returnBehavior
integration=go-high-level&success=true&company_name=...Redirect to agency-companies carrying the query (company-level install completed)
integration=go-high-level&success=true (no company)Toast "GoHighLevel connected", clear query params (agency-level install completed)

Playground (public demo sandbox)

The agency portal grows two playground surfaces (routes /agency/playground, /agency/simple-playgroundfrontend/src/views/agency/{playground,simple-playground}/List.vue) for demoing the product to prospects with no login:

  • Anonymous playground — public /playground (views/playground/PlaygroundView.vue). POST /playground/init (rate-limited, fingerprinted, no auth) creates a temp user + company + subscription seeded with 100 AI credits and kicks off website scraping. Auto-expires: PlaygroundCompanyModel.expires_at = 24h.
  • Simple Playground (shareable) — setup wizard at /simple-playground (agency-authenticated via ?agency_create=1 / ?agency_edit=<id>), prospect link at /simple-playground/test?company_id=<id>. Never expires (expires_at holds a far-future sentinel). Share links are collaborative — anyone with the link can edit (GET|PUT /playground/shared-company). Renders white-labeled under the agency's branding/domain.

Mechanics (backend PlaygroundController + playgroundRoutes.js):

  • Every save path (owner, agency, shared link) goes through one whitelist — PLAYGROUND_SIMPLE_FIELDS / PLAYGROUND_NESTED_FIELDS (widget styling, ai_chat.* / ai_calling.* agent config, voice id/language, playground_redirect_url).
  • Welcome video: POST /playground/agency/:id/video (streamed compression, replace/remove), with presets from the admin Default Videos library.
  • Review: GET /playground/agency/:id/calls — paginated voice-call recordings + chat conversations per playground.
  • Convert to real account: POST /playground/convert.
  • Agency management: GET /playground/agency/list|stats, CRUD on /playground/agency/:id.
  • Super-admin: /admin/playground (views/admin/playground/List.vue) — list + stats with a Credits column (remaining plan + purchased credits per playground; GET /playground/admin/list|stats).

Behaviors & edge cases

  • Zero capacity = empty state. An agency that has subscribed but somehow has total_sub_accounts = 0 sees only the buy-sub-accounts CTA; the setup cards and stats are hidden.
  • Self-account setup re-authenticates. setupOwnCompany returns a fresh token, user, company, and subscription; the store is replaced in place and the user is pushed to onboarding. If the onboarding route is missing it stays on the dashboard and invalidates the agency query.
  • Staff visibility. Staff without agency.manage_settings never see the "Connect GHL" card. The "Unlink" self-account control is owner-only regardless of permissions.
  • Numbers can lag. Because the cards read the cached agency object, creating/removing a company elsewhere only updates here after loadUserInfo() / query invalidation. A hard refresh is the reliable resync.

Plan & limits

  • The dashboard is part of the agency portal, which is its own subscription tier (verify exact plan name/pricing).
  • total_sub_accounts is granted by the agency's package; see Subscriptions and Packages. The dashboard itself imposes no limits — it only reports them.

Technical implementation

  • View: frontend/src/views/agency/Dashboard.vue
  • Route: frontend/src/router/agency.js → name agency-dashboard, path /agency, layout AgencyLayout.vue, allowedUserType: [AGENCY, AGENCY_STAFF]
  • Store: frontend/src/store/app.js (agency, company, isAgencyOwner, agencyHasPermission)
  • Repositories: frontend/src/repositories/agency/index.js (setupOwnCompany, unlinkSelfAccount, uninstallGhl, installGhl)
  • Backend: AgencyController (setupCompany, unInstallGhl), GoHighLevelService for install URLs — see backend service and CRM integration subsystem
  • Model: AgencyModel (backend/app/models/AgencyModel.js)