Overview
The Members Portal exposes three injection points that let operators add custom code without modifying the portal source:
| Injection point | File in Web Template Editor | What it does |
|---|
| Custom CSS | styles.css | Injected as a <style> tag on every page |
| Custom JS | app.js | Executed once when the portal first mounts |
| Custom head | head.js | JSON 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
Open the Web Template Editor
Select a file
Click app.js, styles.css, or head.js depending on which injection point you want to customise.
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 / Method | Type | Description |
|---|
all | Record<string, string> | All business settings as a flat name → value map. |
getSetting(name) | (name: string) => string | undefined | Look up a single setting by name (case-insensitive). Returns undefined if not found. |
getBoolSetting(name, defaultValue?) | (name: string, defaultValue?: string) => boolean | Returns 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) => boolean | Returns 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. |
business | Business | The current business / location object (see fields below). |
checkoutTypes | CheckoutTypes | Available 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:
| Value | Constant | Who can access |
|---|
1 | Everyone | Always returns true, including unauthenticated users. |
2 | LoggedInUsers | Any signed-in user. |
3 | OnlyMembers | Signed-in customers with an active membership contract (coworker.IsMember). |
4 | OnlyContacts | Signed-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
| Field | Type | Description |
|---|
Id | number | Unique identifier for the business. |
Name | string | Business display name. |
WebAddress | string | Portal web address. |
Address | string | Physical address. |
TownCity | string | City name. |
Country | Country | Country object (Name, Id, UniqueId). |
Currency | Currency | Currency object (Name, Code, Format). |
SimpleTimeZone | SimpleTimeZone | Time zone details (Iana, OffsetInMinutes, UsesSummerTime). |
Longitude | number | Longitude coordinate. |
Latitude | number | Latitude coordinate. |
HasLogo | boolean | Whether a logo has been uploaded. |
UniqueId | string | Globally unique identifier (GUID). |
checkoutTypes fields
| Field | Description |
|---|
ResourceTypes | All resource types active at this location. |
ResourceTypesMembers | Resource types available to members with an active contract. |
ResourceTypesContacts | Resource types available to contacts (no active contract). |
TariffTypes | Plan / membership types available at this location. |
ProductTypes | All product types active at this location. |
ProductTypesMembers | Product types available to members. |
ProductTypesContacts | Product types available to contacts. |
__nexudus.auth
Contains the current authentication state and the signed-in customer’s data.
| Property | Type | Description |
|---|
isAuthenticated | boolean | true if the user is signed in. |
coworker | Coworker | null | Full profile of the signed-in customer, or null if unauthenticated. |
user | UserProfile | null | System user record (email, full name, access token). |
isAdmin | boolean | true if the signed-in user has administrator access. |
profiles | CoworkerProfiles | null | All profiles linked to this user account, including the default business. |
impersonating | boolean | true when an admin is viewing the portal as another customer. |
defaultBusiness | ProfileBusiness | undefined | The default business associated with this user account (Id, Name, WebAddress). |
Commonly used coworker fields
| Field | Type | Description |
|---|
FullName | string | Customer’s full name. |
Email | string | Customer’s email address. |
IsMember | boolean | true if the customer has an active membership contract. |
IsContact | boolean | true if the customer has no active contract. |
IsAdmin | boolean | true if the customer has admin-level access. |
IsTeamAdministrator | boolean | true if the customer manages a team. |
AvatarUrl | string | URL of the customer’s profile picture. |
CompanyName | string | Company name if the profile is a company type. |
CoworkerType | string | 'Individual' or 'Company'. |
CanMakeBookings | boolean | Whether this customer is allowed to make bookings. |
CanPurchaseProducts | boolean | Whether this customer is allowed to purchase products. |
CheckedIn | boolean | Whether the customer is currently checked in. |
ActiveContracts | CoworkerContract[] | List of currently active membership contracts. |
HomeSpaceName | string | Name 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.
| Property | Type | Description |
|---|
location | Location | Current React Router location. Includes pathname, search, hash, and state. |
navigate | NavigateFunction | Programmatic 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 onRouteChange — window.__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);
}