Packages & Add-ons (Admin)
Audience: Ops · Super-admins · Billing/Finance · Support · Where in app: /admin/packages, /admin/addons, /admin/agency-packages, /admin/agency-addons, /admin/agency-invites · Access: Super-admin only (verify — admin-portal routes behind the admin layout).
Packages are the subscription plans a company can buy; per-package feature flags and the AI-credit allotment are what gate what each tenant can actually do. Add-ons are extras stacked on top of a subscription (more AI credits, more users, extra agents). The agency variants are the equivalent catalog for white-label agencies: agency packages/add-ons size what an agency resells, and agency invites bundle starter packages + credits for onboarding new agencies. This page documents every field, because editing them changes billing and entitlements.
What it does
- Packages (
/admin/packages) — create/edit/delete the company-facing plans: pricing, trial terms, limits, AI credits, and the full per-package feature matrix. - Add-ons (
/admin/addons) — create/edit/delete subscription extras, typedai_credits/additional_users/outbound_agent/inbound_agent, billed one-time or recurring; optionally tailored to a single agency. - Agency Packages (
/admin/agency-packages) — plans sold to agencies (priced bysub_accounts). - Agency Add-ons (
/admin/agency-addons) — sub-account add-ons for agencies. - Agency Invites (
/admin/agency-invites) — onboarding bundles: starter AI credits + a set of agency packages/add-ons + optional trial.
How it works
Frontend repos under frontend/src/repositories/admin/ (packages.js, addons.js, agency-invites.js, …) call the Node backend. Packages map to Package (collection packages), add-ons to Addon (addons), and the agency variants to AgencyPackage (agency_packages), AgencyAddon (agency_addons), AgencyInvite (agency_invites). Backend controllers: AgencyPackageController, AgencyAddonController, AgencyInviteController (+ the package/addon controllers).
Pricing rows hold Stripe ids: packages carry monthly_stripe_id / yearly_stripe_id; add-ons and agency entities carry stripe_product_id / stripe_price_id (stripe_id on Addon). The list/edit views are the source of truth that downstream subscription, entitlement, and AI-credit logic reads from.
Visibility quirk (packages): the list query sends
v=yfor private andv=nfor public (getPackagesinpackages.js), mapping to the packagevisibilityenum.
Screens & fields
Packages — /admin/packages (list), …/create, …/:id/edit
Source: packages/List.vue, packages/Edit.vue → frontend/src/repositories/admin/packages.js (/package routes). Backing model backend/app/models/PackageModel.js.
Plan / pricing / limits (top-level package fields):
| Field | Type / default | Notes |
|---|---|---|
title, sub_title | string | plan name |
visibility | private / public (default public) | controls who sees the plan |
base_price | number, default 0 | |
currency | string, default NZD | |
description, package_details | string | |
agency_id | ObjectId → agencies | set = agency-scoped plan |
prices[] | {name, amount, type: amount|percentage, country} | per-country pricing rows |
onetime_charges[] | {name, amount} | |
annual_discount | number, default 0 | |
ai_credits | number, default 1000 | monthly AI-credit allotment for the plan |
number_of_websites | number, default 1 | |
default_users / max_users / additional_user_price | numbers | seat sizing; max_users 0 = unlimited |
monthly_visitors | number, default 100 | |
visitor_data | number, default 1000 | visitor insights allotment |
recording | number, default 1 | recordings (GB) |
call_duration | number, default 0 | minutes |
free_trial | yes / no (default no) | |
trial_limit | number, default 1 | trial days |
trial_without_card | bool, default false | |
expiry_days | number, default 7 | |
status | active / inactive (default active) | |
mobile_app / desktop_app / affiliate_program | bool | |
popular | bool | "most popular" badge |
monthly_stripe_id / yearly_stripe_id | string | Stripe price ids |
user_emails[] | string[] | |
| Pricing-card style fields | bg_color, *_txt_color, button colors, free_trial_button_text, buy_now_button_text, buy_now_button_visiblility | cosmetic for the public pricing widget |
Feature flags (features.*) — the entitlement matrix. Numeric flags use -1 to mean unlimited. Booleans default to plan-tier values:
| Flag | Default | Meaning |
|---|---|---|
live_sessions, recorded_sessions | true | |
audio_calls, video_calls | true | with audio_call_duration_per_call / video_call_duration_per_call (-1 = unlimited) |
call_forwarding, call_transfer, call_recording | true | |
chat_transfer, ai_chatbot, ai_conversations | true | |
chat_auto_triggers | 5 | |
ai_message_credits_per_month | -1 | |
auto_connect | -1 | |
multi_widget_styles, widget_styling, high_intent_widget, teams | true | |
lead_intelligence | 20 | quota (was 0/-1); lead_intelligence_allowed (true) gates access; extended_lead_intelligence (false) |
live_session_views | -1 | |
custom_integrations, dedicated_account_manager, priority_support | false | |
call_visitor_audio, call_visitor_video | false | |
webhooks | false | |
inbound_call_agent, outbound_call_agent | true | with number_of_inbound_agents (2) / number_of_outbound_agents (2) — were 1/0 |
number_of_whatsapp_agents | 2 | max WhatsApp agents (was 0); read into whatsapp_agents_allowed; must stay declared — schema is strict |
whitelabel_widget | false |
The Edit form surfaces the common subset (AI Credits, Number of Websites, Lead Intelligence allowed/number, Default/Max Users, Webhooks, Inbound/Outbound Call Agent + counts, Whitelabel Widget, Monthly Visitors, Visitor Data, Recordings GB, Call Duration, trial terms, status, visibility, expiry).
Add-ons — /admin/addons (list), …/create, …/:id/edit
Source: addons/List.vue, addons/Edit.vue → frontend/src/repositories/admin/addons.js (/addons). Backing model AddonModel.js.
| Field | Type / default | Notes |
|---|---|---|
title, sub_title, description | required strings | |
type | required | UI options: ai_credits / additional_users / outbound_agent / inbound_agent (see ADDON_TYPES) |
quantity | number, default 1 | e.g. credits or seats granted |
billing_type | one_time / recurring (default one_time) | recurring attaches to the Stripe subscription and rebills; one_time is a single charge |
stripe_id | required | Stripe product id |
stripe_price_id | string | |
agency_id | ObjectId → agencies, default null | set ("Tailored for Agency") = only that agency sees it; null = global add-on |
sort_order | number, default 0 | |
status | active / inactive (required) |
Agency Packages — /admin/agency-packages
Model AgencyPackageModel.js (agency_packages). Plans for agencies, priced by sub-accounts.
| Field | Notes |
|---|---|
name, description | required |
amount | required number |
currency | default usd |
sub_accounts | required — how many sub-accounts the plan grants |
trial | bool, default false |
stripe_product_id / stripe_price_id | string |
status | active / inactive (default active) |
package_type | private / public (default private) |
sort_order | default 1 |
Agency Add-ons — /admin/agency-addons
Model AgencyAddonModel.js (agency_addons).
| Field | Notes |
|---|---|
name, description | required |
amount | required |
currency | default usd |
type | enum sub_account (default) |
billing_type | one_time / recurring (default recurring) |
visibility | private / public (default public) |
status | active / inactive (default active) |
sort_order | default 1 |
Agency Invites — /admin/agency-invites
Source: frontend/src/repositories/admin/agency-invites.js (/agency-invites). Model AgencyInviteModel.js (agency_invites). Onboarding bundle for new agencies.
| Field | Notes |
|---|---|
name, description | required |
initial_ai_credits | default 0 — starter AI credits granted on accept |
agency_packages[] | refs → agency_packages bundled into the invite |
agency_addons[] | refs → agency_addons |
give_trial | bool, default false |
trial_duration | number, default 0 |
sort_order | default 0 |
status | active / inactive (default active) |
Behaviors & edge cases
-1= unlimited for numeric feature flags (durations, quotas, agent counts). Setting a finite number imposes a cap;0typically means none allowed.- Defaults changed (2026-07): new packages/companies get inbound and outbound calling on by default with caps 2 outbound / 2 inbound / 2 WhatsApp agents (were 1/1/0), and lead intelligence enabled at 20 lookups (was off/0).
SubscriptionHelpermirrors the same fallbacks (?? 2,?? 20) when a package omits the field. - AI credits live in two places:
ai_creditson the package (recurring monthly allotment),quantityon anai_creditsadd-on (top-up), andinitial_ai_creditson an agency invite (starter grant). Editing the package value changes ongoing entitlement. - Agency scoping: a package with
agency_idset, or an add-on withagency_idset ("Tailored for Agency"), is only visible to that agency. Null = global/visible to everyone. - Billing type matters for Stripe:
recurringadd-ons/agency-add-ons attach to the subscription and rebill each cycle;one_timeis a single immediate charge. Agency add-ons default torecurring; platform add-ons default toone_time. - Currencies differ by catalog: packages default
NZD; add-ons/agency entities defaultusd. Mismatches surface in revenue grouping — confirm currency on create. - Packages visibility filter is inverted in the query (
v=y= private,v=n= public) — a UI/API mapping detail, not a data field. - Stripe ids are not auto-created here — the fields hold ids that must correspond to real Stripe products/prices for checkout to work.
Technical implementation
- Models:
backend/app/models/PackageModel.js,AddonModel.js,AgencyPackageModel.js,AgencyAddonModel.js,AgencyInviteModel.js. - Controllers:
backend/app/controllers/AgencyPackageController.js,AgencyAddonController.js,AgencyInviteController.js(+ package/addon controllers); routesagencyPackageRoutes.js,agencyAddonRoutes.js,agencyInviteRoutes.js. - Frontend: repos in
frontend/src/repositories/admin/(packages.js,addons.js,agency-invites.js); views underfrontend/src/views/admin/{packages,addons,agency-packages,agency-addons,agency-invites}/.