APP_VAR Dynamic Substitution V2 (User + Developer Guide)
Document Scope
- Feature: Dynamic APP_VAR substitution for variables/content
- Version: Consolidated and updated on 2026-02-11
- Audience: Users + Developers (single source of truth)
1) User Guide
What APP_VAR does
APP_VAR_* tokens are placeholders replaced with live values.
Example:
- Input:
Hello APP_VAR_USER_EMAIL - Output (saved or rendered):
Hello user@example.com
Where replacement happens
- Backend replacement (save-time): value is replaced before persistence when backend flag is ON.
- Frontend replacement (view-time): value is replaced in UI render (Peek Drawer/chat render), not persisted.
Current tokens you can use
Backend tokens (save-time):
APP_VAR_CURRENT_CHANNELAPP_VAR_CURRENT_WORKSPACEAPP_VAR_USER_IDAPP_VAR_USER_EMAILAPP_VAR_USER_EMAIL_ACCOUNTAPP_VAR_USER_NAME(user name from latestmm_user_data/ auth fallback)APP_VAR_NAME(current variable name/path where token is rendered)
Frontend tokens (view-time):
APP_VAR_BASE_URLAPP_VAR_BROWSER_LOCALEAPP_VAR_CURRENT_TIMEZONEAPP_VAR_CURRENT_TIME
Control/negative-test token:
APP_VAR_BAD_PIPELINE(intentionally invalid; expected to remain unresolved)
Practical examples
Deep-link template (dynamic domain + channel/workspace):
APP_VAR_BASE_URL/app/channel/APP_VAR_CURRENT_CHANNEL?newSession=true&agent=Master%20Teresa%2C%20Fast%20Solution%20Discovery&import=APP_VAR_CURRENT_CHANNEL&workspace=APP_VAR_CURRENT_WORKSPACE
Deep-link template (canonical, agent acronym):
APP_VAR_BASE_URL/app/channel/APP_VAR_CURRENT_CHANNEL?newSession=true&agent=trinity&import=APP_VAR_CURRENT_CHANNEL&workspace=APP_VAR_CURRENT_WORKSPACE
External deep-link (create new session using agent name, no UUID):
https://SEU_DOMINIO/app?newSession=true&agent=Master%20Teresa%2C%20Fast%20Solution%20Discovery&workspace=WORKSPACE_UUID
External deep-link (local example using agent name):
http://localhost:8080/app?newSession=true&agent=Master%20Teresa%2C%20Fast%20Solution%20Discovery&workspace=2b3ef051-5a7a-4a41-8f34-07e5f4c537ae
External deep-link (without workspace param):
https://SEU_DOMINIO/app?newSession=true&agent=Master%20Teresa%2C%20Fast%20Solution%20Discovery
Without workspace, app fallback is:
- use saved/selected workspace for that user;
- if no saved state, use first available workspace;
- fail only when user has no accessible workspace.
Identity template:
Email=APP_VAR_USER_EMAIL Account=APP_VAR_USER_EMAIL_ACCOUNT UserName=APP_VAR_USER_NAME VariableName=APP_VAR_NAME
Troubleshooting (User)
- If backend tokens are not replaced on save:
- Verify backend flag is ON (
app_var_substitution_backend_enabled). - Confirm variable creation path goes through Add Agent Context / head function.
- Verify backend flag is ON (
- If frontend tokens are visible in UI:
- Hard refresh browser.
- Clear cache key
app_var_registry_cache_v2and reload. - Check token in rendered view (Peek Drawer/chat), not raw editor text.
APP_VAR_BAD_PIPELINEunresolved is expected behavior.
2) Developer Guide
High-level architecture
Processing order:
- Include expansion (
<include />) - Context substitution (
APP_VAR_CURRENT_CHANNEL, etc.) - Backend APP_VAR substitution (if backend flag ON)
- Persist to
mvpl_variables - Frontend APP_VAR substitution at render-time
Registry model
Table: public.mvpl_variables_dynamic
Relevant fields:
var_name(PK)var_category(backend|frontend)resolver_pipeline(jsonb, required)active(bool)description,example_value
V2 note:
- Legacy
resolver_typeandresolver_configare deprecated/removed for V2 path. - Resolver is pipeline-based only.
Pipeline step types
context: read from structured context (user,channel,workspace,message,browser,time)db: whitelisted table/columns, context-bound filters onlyvariable: read variable from scoped channel/workspacetransform: safe ops (split,lower,upper,substring,json_get,coalesce)
Security model
- Fail-closed: unresolved/invalid pipeline keeps placeholder.
- DB step whitelist + context anchoring enforced.
- User-scoped constraints for sensitive lookups.
- Registry write access restricted by RLS/admin policy; read allowed for authenticated users.
Feature flags
Table: public.mvpl_parameters
app_var_substitution_backend_enabled(effective backend gate)app_var_substitution_frontend_enabled(reserved/optional gate depending on UI policy)
Expected config shape:
{ "type": "boolean", "value": true }
Frontend reliability behavior (critical)
For core frontend tokens (BASE_URL, BROWSER_LOCALE, CURRENT_TIMEZONE, CURRENT_TIME):
- UI first loads registry from
get-app-var-registry. - If cached registry is missing core vars, it force-refreshes.
- If registry fetch fails, UI applies local runtime fallback so core tokens still resolve.
- This guarantees
APP_VAR_BASE_URLresolves towindow.location.originat render-time.
Key implementation files
Backend:
supabase/functions/_shared/app-var-substitution/backend-resolver.tssupabase/functions/_shared/app-var-substitution/backend-substitution.tssupabase/functions/_shared/app-var-substitution/pipeline.tssupabase/functions/channel-variables/index.tssupabase/functions/get-app-var-registry/index.ts
Frontend:
src/hooks/useAppVarSubstitution.tssrc/lib/app-var-substitution/frontend-pipeline.tssrc/lib/app-var-substitution/frontend-resolver.tssrc/lib/api/appVarRegistry.tssrc/lib/api/variables.tssrc/components/journey/AddContentModal.tsxsrc/components/canvas/PeekDrawer.tsx
Migrations relevant to this feature
supabase/migrations/20260128094000_seed_app_var_substitution_parameters.sqlsupabase/migrations/20260205164907_add_app_var_base_url_frontend.sqlsupabase/migrations/20260211154500_reassign_app_var_name_user_name.sql
Deep link recipe (new channel modal + import current channel)
Path/params are handled in:
src/components/layout/MainLayout.tsxsrc/components/layout/LeftPanel.tsxsrc/lib/deep-link-utils.ts
Canonical template:
APP_VAR_BASE_URL/app/channel/APP_VAR_CURRENT_CHANNEL?newSession=true&agent=trinity&import=APP_VAR_CURRENT_CHANNEL&workspace=APP_VAR_CURRENT_WORKSPACE
Notes:
newSession=trueopens Create Session modal.agentaccepts UUID, acronym (recommended), or name token.importaccepts channel UUID or channel name; usingAPP_VAR_CURRENT_CHANNELresolves to current channel UUID.workspaceaccepts workspace UUID or name; usingAPP_VAR_CURRENT_WORKSPACEresolves to current workspace UUID.
Validation commands
Focused checks:
npm run test -- src/lib/__tests__/variables-api.test.ts src/lib/app-var-substitution/frontend-resolver.test.ts src/lib/__tests__/app-var-substitution.test.ts tests/unit/edge-functions/app-var-context-builder.test.ts tests/unit/edge-functions/app-var-resolver-pipeline.test.ts
npm run integration:local -- tests/integration/edge-functions/app-var-substitution.integration.test.ts tests/integration/frontend/app-var-frontend-substitution.test.ts
npx env-cmd -f .env.local playwright test tests/e2e/app-var-dynamic-links.spec.ts tests/e2e/app-var-deeplink-integration.spec.ts tests/e2e/app-var-frontend-runtime.spec.ts
Known expected behavior
APP_VAR_BAD_PIPELINEremains unresolved by design (negative-path coverage).- Backend placeholders remain unchanged when backend flag is OFF.
- Frontend placeholders resolve only in rendered UI, not raw stored markdown.
3) Quick Reference
One variable to test everything
[Backend Vars]
channel: APP_VAR_CURRENT_CHANNEL
workspace: APP_VAR_CURRENT_WORKSPACE
user_id: APP_VAR_USER_ID
user_email: APP_VAR_USER_EMAIL
user_email_account: APP_VAR_USER_EMAIL_ACCOUNT
user_name: APP_VAR_USER_NAME
variable_name_path: APP_VAR_NAME
bad_pipeline_control: APP_VAR_BAD_PIPELINE
[Frontend Vars]
base_url: APP_VAR_BASE_URL
locale: APP_VAR_BROWSER_LOCALE
timezone: APP_VAR_CURRENT_TIMEZONE
time_iso: APP_VAR_CURRENT_TIME
[Deep Link Template]
APP_VAR_BASE_URL/app/channel/APP_VAR_CURRENT_CHANNEL?newSession=true&agent=Master%20Teresa%2C%20Fast%20Solution%20Discovery&import=APP_VAR_CURRENT_CHANNEL&workspace=APP_VAR_CURRENT_WORKSPACE