Skip to main content

Overview

The Members Portal exposes three injection points that let operators add custom code without modifying the portal source:
Injection pointFile in Web Template EditorWhat it does
Custom CSSstyles.cssInjected as a <style> tag on every page
Custom JSapp.jsExecuted once when the portal first mounts
Custom headhead.jsJSON array of <script>, <meta>, <link>, and similar elements added to <head>
All three files are edited from the Web Template Editor in your Nexudus dashboard at Settings > Website > Web Template Editor > Built-in Files.
If you are migrating from a previous version of the Members Portal, each file must be saved at least once in the Web Template Editor before the setting becomes active in the new portal.

Editing the files

1

Open the Web Template Editor

Go to Settings > Website > Web Template Editor and select the Built-in Files tab.
2

Select a file

Click app.js, styles.css, or head.js depending on which injection point you want to customise.
3

Edit and save

Make your changes in the code editor and click Save. Changes take effect immediately on the next portal page load — no rebuild required.

styles.css — Custom CSS

Anything you write in styles.css is injected verbatim inside a <style data-nx-custom="css"> tag appended to <head>. Use it to override default portal styles or add entirely new rules.
/* Hide the default footer */
footer {
  display: none;
}

/* Apply a custom font */
body {
  font-family: 'Inter', sans-serif;
}

Use the browser DevTools inspector to find the exact class names and element selectors used in the portal before writing overrides.

app.js — Custom JavaScript

Code in app.js runs exactly once, after the portal mounts. It executes in the context of the portal page, so it has full access to the DOM and to the window.__nexudus object (see below).
// Redirect unauthenticated visitors to your marketing site
if (!window.__nexudus.auth.isAuthenticated) {
  window.__nexudus.router.navigate('/sign-in')
}

// Inject a third-party live chat widget only for authenticated members
if (window.__nexudus.auth.isAuthenticated) {
  const script = document.createElement('script')
  script.src = 'https://cdn.example.com/livechat.js'
  document.head.appendChild(script)
}

// Read a custom business setting
const primaryColor = window.__nexudus.settings.getSetting('Website.PrimaryColour')
console.log('Portal primary colour:', primaryColor)
Custom JS runs once on mount. It does not re-run on client-side navigation between portal pages. If you need code to respond to navigation, use the router.navigate method or attach a popstate listener.

head.js — Custom head elements

head.js must contain a JSON array of element descriptors. Each object must have a type field (case-insensitive) set to one of:
  • script
  • meta
  • link
  • style
  • noscript
All other fields on the object are applied as HTML attributes on the element.
[
  {
    "type": "meta",
    "name": "theme-color",
    "content": "#ff5100"
  },
  {
    "type": "link",
    "rel": "preconnect",
    "href": "https://fonts.googleapis.com"
  },
  {
    "type": "script",
    "src": "https://cdn.example.com/analytics.js",
    "defer": "true"
  }
]
Elements are injected into <head> in the order they appear in the array. If the JSON is invalid or an element uses a disallowed type, that element is silently skipped.

The window.__nexudus object

Before app.js executes, the portal populates window.__nexudus with live application state. The object is kept up to date on every render, so values read at any time reflect the current portal state.
window.__nexudus: {
  settings: { ... }
  auth:     { ... }
  router:   { ... }
}

__nexudus.settings

Provides access to the current location’s configuration.
Property / MethodTypeDescription
allRecord<string, string>All business settings as a flat name → value map.
getSetting(name)(name: string) => string | undefinedLook up a single setting by name (case-insensitive). Returns undefined if not found.
getBoolSetting(name, defaultValue?)(name: string, defaultValue?: string) => booleanReturns true if the setting value is the string "true" (case-insensitive, whitespace trimmed). If the setting does not exist, defaultValue is used before comparison; returns false if both are absent.
canAccessSection(accessSettingName)(accessSettingName: string | undefined) => booleanReturns true if the currently signed-in user meets the access level stored in the named setting. Pass undefined to always allow access. See access levels below.
businessBusinessThe current business / location object (see fields below).
checkoutTypesCheckoutTypesAvailable resource, tariff, and product types for the current location (see below).

canAccessSection access levels

The setting value is a numeric code that maps to one of these access levels:
ValueConstantWho can access
1EveryoneAlways returns true, including unauthenticated users.
2LoggedInUsersAny signed-in user.
3OnlyMembersSigned-in customers with an active membership contract (coworker.IsMember).
4OnlyContactsSigned-in customers without an active contract (coworker.IsContact).
Returns false for any other value or when the user does not meet the required level.
// Show a section only to members
if (window.__nexudus.settings.canAccessSection('Website.ShowMembersOnlyBanner')) {
  document.getElementById('members-banner').style.display = 'block'
}

// Read a boolean feature flag
const chatEnabled = window.__nexudus.settings.getBoolSetting('Website.LiveChatEnabled')
if (chatEnabled) {
  loadLiveChatWidget()
}

business fields

FieldTypeDescription
IdnumberUnique identifier for the business.
NamestringBusiness display name.
WebAddressstringPortal web address.
AddressstringPhysical address.
TownCitystringCity name.
CountryCountryCountry object (Name, Id, UniqueId).
CurrencyCurrencyCurrency object (Name, Code, Format).
SimpleTimeZoneSimpleTimeZoneTime zone details (Iana, OffsetInMinutes, UsesSummerTime).
LongitudenumberLongitude coordinate.
LatitudenumberLatitude coordinate.
HasLogobooleanWhether a logo has been uploaded.
UniqueIdstringGlobally unique identifier (GUID).

checkoutTypes fields

FieldDescription
ResourceTypesAll resource types active at this location.
ResourceTypesMembersResource types available to members with an active contract.
ResourceTypesContactsResource types available to contacts (no active contract).
TariffTypesPlan / membership types available at this location.
ProductTypesAll product types active at this location.
ProductTypesMembersProduct types available to members.
ProductTypesContactsProduct types available to contacts.

__nexudus.auth

Contains the current authentication state and the signed-in customer’s data.
PropertyTypeDescription
isAuthenticatedbooleantrue if the user is signed in.
coworkerCoworker | nullFull profile of the signed-in customer, or null if unauthenticated.
userUserProfile | nullSystem user record (email, full name, access token).
isAdminbooleantrue if the signed-in user has administrator access.
profilesCoworkerProfiles | nullAll profiles linked to this user account, including the default business.
impersonatingbooleantrue when an admin is viewing the portal as another customer.
defaultBusinessProfileBusiness | undefinedThe default business associated with this user account (Id, Name, WebAddress).

Commonly used coworker fields

FieldTypeDescription
FullNamestringCustomer’s full name.
EmailstringCustomer’s email address.
IsMemberbooleantrue if the customer has an active membership contract.
IsContactbooleantrue if the customer has no active contract.
IsAdminbooleantrue if the customer has admin-level access.
IsTeamAdministratorbooleantrue if the customer manages a team.
AvatarUrlstringURL of the customer’s profile picture.
CompanyNamestringCompany name if the profile is a company type.
CoworkerTypestring'Individual' or 'Company'.
CanMakeBookingsbooleanWhether this customer is allowed to make bookings.
CanPurchaseProductsbooleanWhether this customer is allowed to purchase products.
CheckedInbooleanWhether the customer is currently checked in.
ActiveContractsCoworkerContract[]List of currently active membership contracts.
HomeSpaceNamestringName of the customer’s home location.

__nexudus.router

Provides access to the portal’s client-side router so custom code can read the current URL or navigate programmatically.
PropertyTypeDescription
locationLocationCurrent React Router location. Includes pathname, search, hash, and state.
navigateNavigateFunctionProgrammatic navigation function. Accepts a path string or a delta number for history.
// Read the current path
console.log(window.__nexudus.router.location.pathname)

// Navigate to a different page
window.__nexudus.router.navigate('/bookings/meeting-rooms/list')

// Go back one step in browser history
window.__nexudus.router.navigate(-1)

Examples

Add Google Tag Manager

In head.js:
[
  {
    "type": "script",
    "src": "https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX",
    "async": "true"
  }
]

Show a banner only to customers without an active plan

In app.js:
const { isAuthenticated, coworker } = window.__nexudus.auth

if (isAuthenticated && coworker && !coworker.IsMember) {
  const banner = document.createElement('div')
  banner.style.cssText = 'background:#ff5100;color:#fff;text-align:center;padding:10px;'
  banner.textContent = 'You do not have an active plan. Browse our plans to get started.'
  document.body.prepend(banner)
}

Redirect a specific setting-based feature flag

In app.js:
const featureEnabled = window.__nexudus.settings.getSetting('Website.EnableCustomFeature')

if (featureEnabled !== 'true') {
  window.__nexudus.router.navigate('/home')
}

Run code on specific pages and react to navigation

The portal is a single-page application. app.js runs once on initial mount — it does not re-execute when members navigate between pages. To run code whenever the route changes, patch history.pushState and history.replaceState and listen for the popstate event (which fires on browser back/forward). In app.js:
function onRouteChange(pathname) {
  // Runs on every client-side navigation and on initial load.

  if (pathname.includes('/meeting-rooms/')) {
    console.log('User is on a bookings page for meeting rooms')
  }
}

// Intercept pushState (links, router.navigate calls)
const _push = history.pushState.bind(history)
history.pushState = function (...args) {
  _push(...args)
  onRouteChange(window.location.pathname)
}

// Intercept replaceState (redirects, query-string updates)
const _replace = history.replaceState.bind(history)
history.replaceState = function (...args) {
  _replace(...args)
  onRouteChange(window.location.pathname)
}

// Handle browser back / forward
window.addEventListener('popstate', () => {
  onRouteChange(window.location.pathname)
})

// Run immediately for the page the user landed on
onRouteChange(window.location.pathname)
For query-string or hash information, read directly from window.location inside onRouteChangewindow.__nexudus.router.location is a snapshot from portal mount and is not updated by client-side navigation.
function onRouteChange(pathname) {
  if (pathname.startsWith('/bookings') && window.location.search.includes('type=meeting-rooms')) {
    console.log('User is browsing meeting rooms')
  }
}
Patching history.pushState affects the entire page. Keep the patched functions lightweight and always call the original (_push / _replace) before your own logic to avoid breaking portal navigation.

React to changes in a __nexudus value

window.__nexudus is a snapshot from portal mount — its plain value properties (auth.*, router.location, etc.) are not automatically updated as the portal state changes. To react when a value changes, poll it with setInterval and compare against the previous reading. The helper below wraps that pattern and returns a cancel function so you can stop watching when it is no longer needed. In app.js:
// Watch a __nexudus property and call onChange whenever its value changes.
// Returns a cancel function.
function watchNexudus(getValue, onChange, intervalMs) {
  var prev = getValue()
  var id = setInterval(function () {
    var next = getValue()
    if (next !== prev) {
      onChange(next, prev)
      prev = next
    }
  }, intervalMs || 300)
  return function () { clearInterval(id) }
}

// React when the signed-in customer switches (e.g. an admin impersonates another profile)
var stopWatchingCustomer = watchNexudus(
  function () { return window.__nexudus.auth.coworker?.Email },
  function (newEmail, prevEmail) {
    console.log('Active customer changed from', prevEmail, 'to', newEmail)
    // update any third-party widgets that display the customer's identity
  }
)

// React when authentication state changes (e.g. token expires mid-session)
var stopWatchingAuth = watchNexudus(
  function () { return window.__nexudus.auth.isAuthenticated },
  function (isAuthenticated) {
    if (!isAuthenticated) {
      console.log('Session ended — redirecting to sign-in')
      window.__nexudus.router.navigate('/sign-in')
    }
  }
)

// Call the returned functions to stop polling when no longer needed.
// stopWatchingCustomer()
// stopWatchingAuth()
router.navigate is a stable function and always works correctly regardless of when you call it. auth.* and router.location are snapshots, so use polling (above) or the history.pushState approach for navigation to observe their changes.

Apply a CSS custom property from a business setting

In app.js:
const accent = window.__nexudus.settings.getSetting('PrimaryWebColor')
if (accent) {
  document.documentElement.style.setProperty('--nx-accent', accent)
}
In styles.css:
.button {
  background-color: var(--nx-accent, #ff5100);
}