Nexus SaaS · version 1.0
Run Nexus for many companies at once.
Nexus SaaS is the multi-tenant edition of Nexus. Every customer gets a workspace — its own subdomain, its own database, its own people, files and settings — and you run all of them from one super-admin panel: plans, trials, payments, support, updates and backups. This guide covers installing the platform, operating it, and everything a workspace can do.
In a hurry? Point a wildcard DNS record at the server, upload the files, open your domain and follow the installer. Then read Your first hour — the shortest path from an empty platform to a paying customer signing themselves up.
Welcome
Two products live in this one codebase, and this guide is in two halves to match.
The platform
What you run, on the apex domain: a marketing site with pricing, self-serve sign-up, and a super-admin panel for workspaces, plans, subscriptions, gateways, support, a knowledge base, monitoring, updates and backups.
The workspace
What each customer runs, on their subdomain: the whole of Nexus — HR, CRM & Sales, Support and Projects — plus three screens that talk back to you: Plan & Billing, Platform Support and Help Center.
The first half of the sidebar — Getting started, Platform panel and Inside a workspace — is written for the operator. Everything from Foundations onward describes the product a workspace's own administrator sees, and is the same text as the single-company Nexus guide, because inside a workspace it is the same product. Hand that half to your customers as-is.
What is in the box
Workspaces
One row in the central database, one MySQL database, one subdomain. Created by a visitor in under a minute or by you from the panel; suspended, re-provisioned or moved to another database server without touching the others.
Plans & trials
Monthly and yearly prices, an employee limit and a staff limit per plan, a trial length, a feature list for the pricing table. Limits are enforced inside the workspace, not just printed.
Billing
Stripe, PayPal, Paystack, Flutterwave, Mollie and bank transfer, behind one interface. A workspace admin renews or upgrades from their own settings; the webhook and the return URL both complete the payment, whichever lands first.
Support & help
Tickets a workspace raises with you (not their own helpdesk), and a knowledge base you write that every workspace reads as its Help Center.
Marketing site
Hero, features, how-it-works, pricing, stats, testimonials, FAQ, footer, SEO, privacy policy and terms — every word editable in the panel.
Operations
Health checks and a structured log of provisioning, database and payment problems; a browser-driven updater that migrates every database in turn; one-click dumps of all of them.
Platform admins
Super admins see everything; admins see the sections you tick. These accounts are platform-side only and never sign in to a workspace.
The whole of Nexus
Everything in the second half of this guide, in every workspace, in eight languages with RTL, with a REST API on every subdomain.
How to read this guide
Sections follow the order you will need them: install the platform, set it up, then each panel screen in the order the sidebar lists it, then the workspace. Every screen named here exists in the product — where something is deliberately absent it is listed under Deliberately not here rather than described as if it were built.
How it is put together
Ten minutes here saves an hour later, because almost every operational question — where is this stored, which database, which domain — has the same answer.
| Central (the platform) | Workspace (a tenant) | |
|---|---|---|
| Host | The apex domain — yourplatform.com | A subdomain — acme.yourplatform.com |
| Database | One: the one the installer created | One per workspace: nexus_tenant_acme, created when the workspace is |
| Who signs in | Platform administrators | The company's staff, employees and portal clients |
| What it serves | Marketing site, sign-up, the super-admin panel | All of Nexus, plus Plan & Billing, Platform Support, Help Center |
| Uploads | public/uploads/ | public/uploads/t/{workspace}/ |
How a request finds its workspace
The host name is looked up in the central domains table. A match switches
the database connection to that workspace's database before any code runs; the apex domain
never matches and serves the platform; an unknown subdomain is a plain 404. After that, a
gate checks the workspace's state and shows one of three holding pages instead of the
app when it has to — see Trials, expiry & suspension.
What is shared and what is not
- Code and views are shared. An update is one file copy for everybody.
- Data is not. There is no
workspace_idcolumn inside a workspace database, because there is nothing to separate — it is the customer's whole database. A backup of one is a backup of one company. - Plans, subscriptions, payments, support tickets and the knowledge base live centrally, and a workspace reads them across the connection when it needs them (the Billing page, the seat-limit check, the Help Center).
- Settings exist on both sides. The platform's General Settings brand the panel and the marketing site and set the defaults a new workspace is seeded with; after that each workspace owns its own settings and yours never overwrite them.
Two things share a name and are not the same thing. A workspace's Tickets are its customers' tickets; Platform Support is the workspace talking to you. A workspace's Knowledge base is its public help for its clients; the Help Center is what you wrote for them.
Server requirements
The same modest footprint as Nexus: PHP and MySQL, nothing else. There is no Node on the server, no Redis, no websockets and no Supervisor. One thing is genuinely different from a single-company install: the database user must be allowed to create databases, because every new workspace gets one.
| What | Needs | Notes |
|---|---|---|
| PHP | 8.3 or newer | 8.3 and 8.4 are both tested. |
| Database | MySQL 8.0+ or MariaDB 10.6+ | The user needs CREATE on *.* (or on nexus\_tenant\_%) so provisioning can create workspace databases. See the note on shared hosting below. |
| Extensions | openssl, pdo, pdo_mysql, mbstring, tokenizer, xml, ctype, json, bcmath, fileinfo, curl, gd | All standard. The installer checks each one and names the missing ones. curl is what talks to the payment gateways. |
| Optional | zip, intl | Nice to have; nothing fails without them. |
| Writable | .env, storage/app, storage/framework, storage/logs, bootstrap/cache, public/uploads | Usually 755 on folders, 644 on files. |
| Web root | Point the domain and every subdomain at public/ | See Domains & DNS. |
mysqldump | On the PATH, or MYSQLDUMP_PATH in .env | Only for platform backups. Workspace-side backups are written in PHP and need nothing. |
Shared hosting and the one-database-per-customer model do not mix well. cPanel-style hosts usually let a database user reach only databases created in the hosting panel, with a prefix you do not control. The platform still works there — set Hosting mode to shared in Platform settings, create each workspace's database by hand in the hosting panel, and enter its credentials on the workspace page before re-provisioning. It is a workable path for a handful of customers and a miserable one for fifty. A VPS is the right home for this product.
What you do not need
No cron job and no queue worker. Provisioning a workspace runs after the response is sent in the same PHP process, so the visitor sees a progress page while their database is built without anything listening in the background. Emails go out inline. Payment confirmation arrives on a webhook, which is the gateway calling you, not a process of yours polling them.
Domains & DNS
This is the one part of the install that a single-company Nexus never asked of you, and the one that is most often wrong. Every workspace lives on a subdomain, so every subdomain of your platform domain has to reach this application.
-
A wildcard DNS record
Alongside the
Arecord foryourplatform.com, add one for*.yourplatform.compointing at the same server. Until it propagates, a new workspace's address will not resolve even though the panel says it is ready. -
A virtual host that accepts the wildcard
Apache:
ServerAlias *.yourplatform.comon the same vhost, document rootpublic/. Nginx:server_name yourplatform.com *.yourplatform.com;. One vhost, not one per customer. -
A wildcard certificate
Browsers will refuse
acme.yourplatform.comon a certificate issued for the apex alone. Let's Encrypt issues wildcard certificates through the DNS challenge (certbot --preferred-challenges dns -d yourplatform.com -d '*.yourplatform.com'); most managed hosts have a checkbox for it. -
Tell the platform which domain is the apex
The installer takes the domain you open it on and writes it to
APP_URL. The list of hosts treated as central — the ones that serve the panel rather than a workspace — iscentral_domainsinconfig/tenancy.php; the first entry is the platform domain that subdomains are appended to. Change it there if your platform ever moves.
A handful of subdomains can never be registered: www, app,
api, mail, admin, central,
assets, static and cdn. Add your own to the list
in Platform settings → Workspace defaults.
Installing
The platform installs itself in a browser. You do not need shell access, phpMyAdmin or a command line — only the DNS above done first.
-
Upload and unzip
Put the contents of the ZIP on your server and point the domain — with its wildcard — at the
public/folder. Everything is included; you do not need to runcomposer install. -
Open your domain
Every address redirects to
/setupon the platform domain until the install is finished. You land on the requirements check. -
Requirements
Every required row must be green — the PHP version, the twelve extensions and the six writable paths. If one is not, fix it on the server and press Re-check. Nothing has been written yet.
-
Company
The platform name, currency and timezone, plus the environment and the site URL. The currency is what you charge in — every plan price and every payment is in it — and it is also the default currency each new workspace is seeded with. An application key is generated for you. Leave Debug mode off on a live server.
-
Database
Host, port, database name, username and password for the central database. Press Test connection before continuing. This is the user that will later create every workspace database, so give it the privilege now.
-
Administrator
Your name, email and password. This is the first super admin — a platform account, not a workspace one. It reaches every section of the panel.
-
Review and install
Check the summary, then press Install now. This writes
.env, builds the central schema, seeds the three launch plans and the marketing site's starting copy, records your platform settings and creates your account. Under a minute; the log narrates each step and stops where a failure happened.
The install always starts from a clean central database. Existing tables in the database you name are dropped — the review step says so. It does not touch workspace databases, which are separate; but a re-install over a live platform loses the tenant list that points at them. Take a backup first if there is anything to lose.
The installer seals itself. Once it finishes, /setup
redirects to the site and cannot be re-run. The seal is a file:
storage/app/installed. Delete it to open the wizard again — and if
/setup is ever reachable when it should not be, that file is the first thing
to check.
Nothing is written until the last step. The wizard keeps your answers in the session, so
an abandoned install can never leave a deployment with a rewritten .env
pointing at a database that was never migrated. A clone straight out of git with an empty
APP_KEY is handled too: the wizard plants one on the way in.
If the install fails
| Message mentions | What to do |
|---|---|
| Access denied for user | Wrong database username or password. Go back a step and re-test. |
| Unknown database | Create an empty central database in your hosting panel and use that name. Then check that the same user can create more databases, or you will meet this again at the first workspace. |
| Permission denied / failed to open stream | .env or storage/ is not writable. Set folders to 755 and files to 644. |
| Migrations ran but the expected tables are missing | The connection landed in a different schema than the one you named — almost always a stale config cache. Run php artisan config:clear and try again. |
The last step never finishes under php artisan serve | The dev server restarts itself when .env changes, which is exactly what the last step does. Use Apache or Nginx for the install, or the command line. |
Your first hour
A fresh install already has three plans, a marketing site with sensible copy and your super-admin account. This is the shortest route from there to a platform a stranger can sign up to and pay for.
-
Brand the platform
Settings → General Settings. Logo and favicon, the platform name, the company details that appear on receipts, the footer line. The Workspace defaults tab is worth a look now too: the app name, currency and language every new workspace starts with.
-
Price the plans
Platform → Plans. Starter, Business and Enterprise ship as placeholders. Set the monthly and yearly prices in your currency, the employee and staff limits (blank for unlimited), the trial length, and the feature bullets the pricing table shows. Deactivate any you do not sell rather than deleting them.
-
Turn on a way to get paid
Payments → Payment Gateways. Paste the keys for at least one gateway and tick Active. If you invoice by bank transfer, activate Bank Transfer and fill in your account details under Settings → General Settings → Bank details — that is what workspaces are shown.
-
Register the webhooks
In each gateway's dashboard, point the webhook at
https://{workspace}.yourplatform.com/webhooks/subscriptions/{gateway}. Most gateways want one fixed URL, so use any real workspace's subdomain — the payment is matched by its own reference, not by the host. See Payment gateways. -
Write the marketing site
Platform → Landing CMS. Every section of the public page is a form: the hero, the feature grid, how it works, pricing, stats, testimonials, FAQ, footer, SEO tags, and the privacy policy and terms. The starting copy is generic on purpose.
-
Create your first workspace
Either sign up at
/registerlike a customer would, or Platform → Workspaces → New workspace. Watch it provision, open it, sign in as the admin you named. If the subdomain does not resolve, it is DNS, not the platform. -
Seed the Help Center
Support → Knowledge Base. A category or two and a few articles — how to add people, how to upgrade, who to contact. Every workspace sees them under Settings → Help Center the moment they are published.
-
Take a backup and see it work
Administration → Backups → Create backup. One
.sqlper database. Ifmysqldumpis not found, setMYSQLDUMP_PATHin.envnow rather than on the day you need it. -
Add a second administrator
Administration → Super Admins. A second super admin, or a limited admin who only sees Support Tickets and the logs. One account is one lost password away from a locked panel.
Command-line install
If you have shell access and would rather not use the wizard:
composer install --no-dev --optimize-autoloader
cp .env.example .env
php artisan key:generate
# edit .env: APP_URL (the platform domain), APP_TIMEZONE and the DB_ settings
# edit config/tenancy.php: central_domains — the first entry is your platform domain
php artisan migrate --force # the central schema only
php artisan db:seed --class=PlanSeeder # Starter / Business / Enterprise
php artisan db:seed --class=LandingCmsSeeder # the marketing site's starting copy
npm install && npm run build # only if you changed the CSS or JS
# tell the installer it has nothing left to do
echo OK > storage/app/installed
The command line does not create a super admin. Either open /setup before
sealing it and let the wizard's last step do it, or insert one with tinker:
php artisan tinker
>>> App\Models\User::create(['name' => 'You', 'email' => 'you@yourplatform.com',
... 'password' => 'a-long-password', 'role' => 'super_admin', 'email_verified_at' => now()]);
Then, for a workspace to look at:
php artisan saas:demo --subdomain=demo # provisions demo.yourplatform.com and fills it
# with the twelve-person demo company
# sign in: admin@demo.test / password
One thing to switch off before you go live. Delete any
saas:demo workspace — it has a published password.
Running it locally
Wildcard subdomains are the only awkward part on a laptop, because the hosts file cannot wildcard.
- Add
127.0.0.1 nexus_saas.testto the hosts file, plus one line per workspace you create (127.0.0.1 acme.nexus_saas.test). - Give the local vhost a
ServerAlias *.nexus_saas.testso any host you map reaches the app. - With
php artisan serveinstead of a vhost, send the host by hand:curl -H "Host: acme.nexus_saas.test" http://127.0.0.1:8000/login. 127.0.0.1andlocalhostare treated as central domains, so the panel is reachable on either without any hosts entry at all.
The test suite runs on in-memory SQLite and needs nothing: php artisan test.
Dashboard
The first screen after signing in on the platform domain, and the one screen every administrator can see whatever sections they hold.
Four numbers across the top: total workspaces and how many signed up this month; subscription revenue this month against last; active subscriptions including trials; and how many unresolved logs need attention. Below them, twelve months of revenue and twelve months of sign-ups, subscriptions by status, workspaces per plan, the six most recent workspaces and the five newest unresolved problems.
Revenue is the sum of paid subscription payments by the date they were paid, whichever gateway they came through and whether a customer paid online or you recorded a bank transfer by hand. Pending and failed checkouts are never counted.
Workspaces
Platform → Workspaces. Every customer, searchable by name, contact email or subdomain, filterable by status, provisioning state and plan.
Creating one from the panel
New workspace asks for the same things the public sign-up does — company name, subdomain, contact details, a plan, and the first administrator's name, email and password — and provisions it the same way. The difference is only who is typing. Use it for customers you onboard by hand, for migrations from another system, and for anyone who would rather not put a card in on day one.
Subdomains are 3–30 characters, lower-case letters, digits and hyphens, starting with a letter. The reserved list and any taken name are refused with a message.
The workspace page
Everything the platform knows about one customer, on one screen:
| Block | What it shows |
|---|---|
| Overview | Domain, contact email and phone, plan, registration date, trial end, the admin's email, and how many unresolved logs mention this workspace. The status badge is active or suspended; the provisioning badge is pending, provisioning, active or failed, with the last error under it. |
| Inside the workspace (live) | User accounts, employees and staff, counted right now from the workspace's own database, next to the plan's limits. Unavailable while it is not provisioned or its database cannot be reached. |
| Subscription | Status, plan, billing cycle, trial end and paid-until date, and the last five payments, with a link to the full subscription. |
| Database configuration | Where this workspace's database lives — the platform's server by default, or a host, port, database name and credentials you enter. See below. |
Four buttons act on the workspace. Open workspace goes to its sign-in page. Suspend turns every visitor away with a suspended page until Activate — the data is untouched, and the payment webhook still works so a renewal can land. Re-provision wipes and rebuilds the workspace database (see Provisioning). Delete removes the workspace and drops its database.
Delete and Re-provision both destroy the workspace's data. Delete drops the database; Re-provision wipes every table in it before migrating and seeding again. Neither can be undone. A customer who has stopped paying should be suspended, which costs nothing and keeps everything; delete only when you have a backup or a signed request.
Putting a workspace on another database server
By default every workspace database sits on the same MySQL server as the central one, created by the platform's own database user. The Database configuration block lets one workspace live elsewhere: a bigger customer on their own server, a customer whose contract requires a particular region, or every workspace on a shared host where the platform user may not create databases.
Enter the host, port, database name, username and password, then Test connection. If the server accepts the credentials but the database does not exist yet, Save & verify creates it. The password is stored encrypted. Then press Re-provision to build the schema there — the change of address does not move any data by itself. Reset to platform default drops the custom credentials and points the workspace back at the platform server (again, without moving data).
A failed test is written to Logs & System Health with a suggested cause: wrong credentials, nothing listening on that port, a host name that does not resolve, a firewall between the two servers.
Editing
Edit changes the name, contact details, plan and trial end date. Changing the plan here changes the limits the workspace is held to immediately; it does not create a payment or extend anything — that is the subscription.
Self-serve registration
/register on the platform domain, reached from the marketing site's
Start your free trial button. A visitor picks a plan, names their company, chooses
a subdomain, and gives the name, email and password of the first administrator. Nothing is
charged: every plan starts as a trial of the length set on the plan.
Pressing Create workspace does three things in the central database — the workspace row, its domain, and a trial subscription — and then hands the visitor a Preparing your workspace page that polls every couple of seconds while the database is built. When it is ready, the page shows the trial end date and a Go to your workspace button to the sign-in page on their new subdomain.
If provisioning fails, the visitor sees Setup hit a snag and is told your team will fix it — and you see the failure in Logs & System Health, with the workspace marked failed and a Re-provision button waiting on its page. The visitor's account details are kept on the workspace, so re-running provisioning gives them the password they chose.
Registration deliberately does not seed the demo company. A new customer gets
Nexus's reference data — a default location, departments, leave types, a pipeline,
ticket categories, task statuses — and one administrator, and nothing else. Demo data
is for saas:demo.
Provisioning
What happens between Create workspace and ready, and why it is built the way it is.
-
Create the database
CREATE DATABASE IF NOT EXISTS nexus_tenant_{subdomain}on the platform's server, with the central connection's charset and collation — or, if the workspace has custom credentials, a connection test against the database you named. -
Wipe and migrate
Every table in that database is dropped, then the workspace schema (
database/migrations/tenant— Nexus's own migrations) is run from scratch. The wipe is what makes a retry safe: a run that died half-way leaves nothing behind for the next one to trip over. -
Seed
Nexus's reference data, then the workspace's settings — the product defaults with your Workspace defaults laid over them and the company's own name as the display name — then the administrator from the sign-up form, with their password hash carried across. The default HQ location is renamed to the company.
-
Mark active
The workspace's provisioning status flips to active and the holding page gives way to the sign-in screen.
The whole run happens after the HTTP response has been sent, in the same PHP process that handled the sign-up. That is what lets the platform work without a queue worker, and it is also why a slow server shows Preparing… for a little longer rather than timing out: the browser was answered in milliseconds and is only polling.
A failure at any step marks the workspace failed, stores the message on it, and writes a provisioning log with a suggested cause. Fix the cause — nearly always a database privilege — and press Re-provision on the workspace page. Until then the subdomain shows a holding page with a 503, so nothing caches it.
Re-provision is a rebuild, not a repair. It runs the same wipe as a first provisioning. On a workspace that has been in use, that is the customer's data gone. It exists for the failed-first-run case and for moving an empty workspace to another database server; for a live workspace with a schema problem, use the updater, which migrates without wiping.
Plans & limits
Platform → Plans. What you sell. Each plan has:
| Field | What it does |
|---|---|
| Name and slug | The name is what the pricing table and the panel show; the slug identifies the plan in URLs and seeders and is derived from the name when left blank. |
| Monthly and yearly price | In the platform currency. A workspace picks the cycle when it pays; the yearly price is charged once for twelve months. |
| Employee limit | How many accounts with the employee role the workspace may hold. Blank is unlimited. |
| Staff limit | How many accounts with any other role — administrator, HR, manager, sales, project manager, accountant. Blank is unlimited. |
| Trial days | How long a new workspace on this plan runs before it must pay. 0 to 365. |
| Features | One bullet per line, shown on the pricing table and the sign-up page. Text only — they describe the plan, they do not switch modules on or off. |
| Active and sort order | Inactive plans disappear from sign-up and the pricing table but keep the workspaces already on them. Sort order is the column order. |
A plan with workspaces attached cannot be deleted — deactivate it instead. The plans list shows how many workspaces each one has.
How the limits are enforced
Nexus keeps employees and staff in one users table, told apart by role, so the two limits are two counts of the same table. They are checked at the only two places a workspace creates an account — Settings → Users & Roles and People → Employees — and again when a role change crosses the line, because promoting an employee to manager uses a staff seat. A workspace at its limit sees the form refused with Your plan allows up to N employees. Upgrade your plan to add more, with a link to Plan & Billing; the form also shows a banner before they start typing. Nothing else in the product counts seats: importing attendance, seeding, provisioning and the API's self-service endpoints are never blocked.
Lowering a limit below what a workspace already uses does not delete anyone. It stops them adding more until they are back under it.
The three shipped plans — Starter (25 employees / 5 staff), Business (100 / 20) and Enterprise (unlimited), all with a 14-day trial — are placeholders with placeholder prices. Their feature bullets mention modules, but every workspace has every module; the bullets are marketing copy, and the limits are the only thing a plan actually controls.
Subscriptions
Payments → Subscriptions. One row per workspace: its plan, billing cycle, and whether it is on trial, active, expired or cancelled. Filter by any of those, or by plan or cycle; search by workspace.
Status
A subscription is trial from registration until the trial end date, active from the first payment until the paid-until date, and expired once whichever of those dates applies is in the past. Cancelled is a state you set. Expired is never stored — it is computed from the dates every time it is read, which is why a payment recorded today makes an expired workspace active with no further step.
Editing one
Plan, billing cycle, trial end and paid-until date are all editable by hand, and changing the plan here also changes it on the workspace. This is how you comp a customer a month, extend a trial for a prospect who asked, or move somebody to a plan you have retired.
Recording a payment
Record payment & extend is for money that arrived outside the gateways — a bank transfer, a cheque, a card you took over the phone. Enter the amount, how many months it buys (1–36), the method, the date it was paid and a reference, and the paid-until date moves forward by that many months. The extension starts from the current paid-until date if that is in the future, otherwise from today — so paying early does not lose the days already bought.
The payment appears in Transactions, in the workspace's own Plan & Billing history, and in the dashboard's revenue.
Cancelling
Cancel subscription marks it cancelled with today's date. The workspace is locked after the grace period exactly as if it had expired; recording a payment or a gateway renewal reactivates it.
Transactions
Payments → Transactions. Every subscription payment across every workspace, newest first, with two totals above it: paid this month and paid all-time.
Each row is a workspace, a plan, an amount, a gateway, a status and — for gateway payments — the gateway's transaction id. Three statuses:
| Status | Means |
|---|---|
| paid | Confirmed. The subscription was extended when this was set. Counted in the totals. |
| pending | A checkout was started and the customer was sent to the gateway; neither the return nor the webhook has confirmed it yet. Abandoned checkouts stay pending — they are harmless, and useful evidence when a customer says "I tried to pay". |
| failed | The gateway declined it, the checkout could not be created, or the return URL found it unpaid. |
Search matches the transaction id, your reference, or the workspace's name or email. Filter by status, gateway or plan.
Payment gateways
Payments → Payment Gateways. Six providers behind one interface. Paste the
credentials, tick Active, save. Credentials are stored encrypted in the
central database, never in .env; a saved secret shows as dots and a blank
field on save means keep what is there.
| Gateway | Credentials | Webhook is verified by |
|---|---|---|
| Stripe | Publishable key, secret key, webhook signing secret | The Stripe-Signature header against the signing secret. |
| PayPal | Client ID, client secret, webhook ID | PayPal's verify-signature API, using the webhook ID. |
| Paystack | Public key, secret key | The X-Paystack-Signature HMAC. |
| Flutterwave | Public key, secret key, encryption key, secret hash | The verif-hash header against the secret hash. Uses the v3 API. |
| Mollie | API key (test_ or live_ — the prefix picks the environment) | Mollie sends no signature; the payment is re-fetched from Mollie with your key before anything is believed. |
| Bank Transfer | None — just Active | No webhook. You record the payment by hand under Subscriptions. |
What a workspace sees
Only active gateways with complete credentials appear as payment methods on the workspace's Plan & Billing page. Bank Transfer shows the account details from Settings → General Settings → Bank details instead of a button. Deactivating a gateway removes it from new checkouts but its webhooks are still accepted, so a payment already in flight is never lost.
The webhook URL
POST https://{any-workspace}.yourplatform.com/webhooks/subscriptions/stripe
POST https://{any-workspace}.yourplatform.com/webhooks/subscriptions/paypal
POST https://{any-workspace}.yourplatform.com/webhooks/subscriptions/paystack
POST https://{any-workspace}.yourplatform.com/webhooks/subscriptions/flutterwave
POST https://{any-workspace}.yourplatform.com/webhooks/subscriptions/mollie
The endpoint lives on the workspace subdomain — the platform domain has no
/webhooks — and the payment is found by the id the gateway echoes back, not
by which subdomain the call arrived on. So a gateway that allows one webhook URL can point
at any real workspace and serve all of them.
The webhook is deliberately outside every gate: no session, no CSRF, and it works when the workspace is suspended, locked for non-payment or still mid-update (during an update it answers 503, and every gateway retries on that). A webhook that fails signature verification is refused with a 400 and written to the logs with the gateway named — the usual cause is a signing secret pasted from the wrong environment.
Completing a payment
Two things can confirm a checkout: the customer coming back to the return URL, and the gateway calling the webhook. Either may arrive first, both usually arrive, and occasionally only one does. Completion is therefore idempotent: the first to arrive marks the payment paid, extends the subscription by one or twelve months, switches the plan if the checkout was an upgrade, unlocks the workspace and writes an info log; the second finds it already paid and does nothing. The return URL also asks the gateway directly whether the session was paid rather than trusting the redirect.
Landing CMS
Platform → Landing CMS. The public site on the platform domain, section by section. Nothing on it is hard-coded.
| Section | Fields |
|---|---|
| Hero | Eyebrow, title, description, primary and secondary buttons (text and URL). The primary button ships pointing at /register. |
| Features | Section title and subtitle, then a sortable list of features, each with a title, description and icon name. |
| How it works | Label, title, subtitle, and an ordered list of steps. |
| Pricing | Title and subtitle, and whether to show monthly, yearly or both. The plans themselves come from Plans — active ones, in sort order, with their feature bullets. |
| Stats | A list of value/label pairs — 14 days / Free trial on every plan. |
| Testimonials | Client name, company and review text. |
| FAQ | Question and answer pairs. |
| Call to action | Title, subtitle, one button. |
| Footer | About text, copyright line, contact email, phone and address, five social links, and whether to show an Admin login link to the panel. |
| SEO | Meta title, description and keywords. |
| Privacy policy and Terms | Served at /privacy-policy and /terms-conditions. Each is a fixed set of headed paragraphs — introduction, data collection, cookies, your rights and so on — with a last-updated date. Write your own; the shipped text is a scaffold, not legal advice. |
Every section has an active switch, so a page with no testimonials yet simply omits that block. Items in a list can be toggled off individually rather than deleted. The seeder that writes the starting copy runs once and never again — the moment a hero row exists it steps aside — so an update cannot put the defaults back over your words.
Branding — logo, favicon, platform name — comes from Platform settings, not from here, because the panel and the sign-in pages use the same marks.
Support tickets
Support → Support Tickets. Tickets that workspace administrators raise with you, from their Platform Support screen. Unread ones sort to the top with a count in the header.
A ticket has a number, a workspace, who opened it, a subject, a category (technical, billing, feature, other), a priority and a status. Open one to read the thread, reply, or change its status and priority.
| Status | Set by | Means |
|---|---|---|
| open | The workspace | Waiting on you. A new ticket, or one the workspace has replied to. |
| answered | Your reply | Waiting on them. Set automatically when you reply to an open ticket; their next reply flips it back to open. |
| resolved | You | Done, but they may still reply — which reopens it. |
| closed | You | Finished. The workspace can read it but not reply; they open a new one instead. |
Priority is low, medium, high or urgent. A workspace may pick the first three when opening a ticket; only you can mark one urgent, so the queue is ordered by your judgement rather than theirs.
Replies are plain text and appear under your name. There is no email notification in either direction in this version — see Mail — so the unread count on the sidebar is what tells you something is waiting.
Knowledge base
Support → Knowledge Base. Articles you write once and every workspace reads as its Help Center. Categories first (a name and an icon), then articles inside them, each with a title, a body, a sort position and a published switch.
Unpublished articles are invisible to workspaces, which is how you draft. Bodies are plain text; line breaks are kept. Each article counts its views, so you can see which questions people actually have. Deleting a category deletes the articles in it.
Filter the list by category, title or published state. Articles sort by category, then by their sort number, then by age.
Logs & system health
Monitoring → Logs & System Health. Five live checks along the top, and under them a log of every problem the platform has noticed about itself.
Health checks
| Check | Green when |
|---|---|
| Central database | A query answers, with the round-trip time shown. |
| Workspace databases | Every workspace's database exists on the server. A missing one is named — a workspace whose provisioning failed, or a database somebody dropped by hand. |
| Storage | storage/app is writable. |
| Disk space | More than 10% free. |
| Runtime | Always — it reports the PHP and Laravel versions and the queue driver, for the support email. |
The log
Not the framework's laravel.log — that still exists, and still has the stack
traces — but a structured list of things an operator should act on, each with a
workspace, a type, a severity, the message, any context, and a suggested
cause written by whatever raised it:
| Type | Raised by |
|---|---|
| provisioning | A workspace build that failed, with the exception and the file it came from. |
| database | A failed connection test on a workspace's custom credentials; a migration that failed during an update. |
| payment | A webhook whose signature did not verify (warning), and — at info — every payment that completed, so the money trail is here too. |
| general | A backup run that reported a database it could not dump. |
Severity is info, warning or critical. A log is unresolved until you mark it resolved; the dashboard counts unresolved ones and the workspace page counts those about that workspace. Clear resolved deletes everything already handled. Filter by status, severity or type.
Super admins
Administration → Super Admins. The people who sign in on the platform domain. These accounts live in the central database and are platform-side only — they never sign in to a workspace, and a workspace's administrator never reaches here.
Two roles. A super admin reaches every section, including this one. An admin reaches only the sections you tick:
| Section | Opens |
|---|---|
workspaces | Workspaces — list, create, edit, suspend, re-provision, database settings, delete. |
plans | Plans. |
cms | Landing CMS. |
subscriptions | Subscriptions, including recording payments and cancelling. |
transactions | The transactions list. |
gateways | Payment gateway credentials. |
support | Support tickets. |
kb | Knowledge base. |
system | Logs & system health. |
settings | General settings, mail, email templates, and platform backups. |
update | Platform update. |
admins | This screen. |
The dashboard is open to everybody. Unticked sections disappear from the sidebar and answer 403 when opened by URL — the same rule as inside a workspace. A support person who should only see tickets and the logs gets exactly those two boxes.
The last super admin cannot be demoted, so the panel can never lock itself out.
Platform settings
Settings → General Settings. Eight tabs. Each saves only its own fields.
| Tab | What lives there |
|---|---|
| General | Platform name; the currency code and symbol you charge in; the default language and which of the eight languages the switcher offers on the panel and the marketing site. |
| Branding | Logo and favicon, used on the panel, the sign-in screens and the marketing site. Stored in public/uploads/branding; removing one restores the built-in mark. |
| Appearance | Whether the platform name shows beside the logo, whether the theme customizer button appears, and the footer line at the bottom of every panel page. |
| Company | Who runs the platform — legal name, phone, email, website, address. Used on receipts. |
| Workspace defaults | What a new workspace is seeded with: default app name, language, currency code and symbol, and a default favicon. Also the reserved subdomains list, and a switch to seed demo data into new workspaces (stored for a future seeder — nothing reads it yet). Existing workspaces are never touched by changes here. |
| Reminders | When workspaces should be warned that a subscription or trial is ending: days-before lists, delivery channels, how far ahead the in-app banner shows. Stored but not yet delivered — the screen says so. See Deliberately not here. |
| Bank details | Bank name, account holder, account number, IBAN, SWIFT, branch, and free-text payment instructions. Shown to a workspace that chooses Bank Transfer on its billing page. |
| System | Hosting mode (VPS or shared); the environment, debug flag and application URL, written straight to .env; and a Clear cache button that flushes config, routes, views and the cache store. |
The System tab edits .env. A wrong application URL or a
typo in the environment can take the whole platform — every workspace — offline until
somebody with shell access fixes the file. Change with care, and keep debug mode off in
production: it shows stack traces to everybody, including your customers' customers.
The grace period — how many days after expiry a workspace stays usable
— is not on this screen. It is the grace_days setting, default 7, and is
read from the central settings table; see Trials, expiry &
suspension.
Mail & email templates
Settings → Mail Settings stores the platform's SMTP details — from name and address, host, port, encryption, username and an encrypted password. Settings → Email Templates lists templates by trigger, each with a subject, body, placeholders and an active switch.
In this version both screens are storage, not delivery. The platform sends no email of its own yet — no welcome on registration, no receipt, no trial reminder, no ticket notification — and ships no templates, so the templates screen is empty until a later release adds triggers. The values are kept so that release needs no re-entry. This is listed under Deliberately not here.
Mail inside a workspace — leave approvals, invoices, ticket replies to the
workspace's own customers — is Nexus's own mailer, configured under the workspace's
Settings → Notifications and described under
Notifications & email. Each workspace's server is stored in
that workspace's own settings, password encrypted, and is swapped into the mail config only
while that workspace is being served. A workspace that has not chosen a server — or that
picks Platform default — inherits the MAIL_* values in
.env, which is how you offer every customer a working relay without
configuring each one: set the platform's SMTP details there, and let the customers who
want their own domain on the envelope enter their own.
Platform update
Settings → General → Updates. After new code is on the server, this walks the central database and then every workspace database through their pending migrations, one at a time, from the browser.
-
Back up
Administration → Backups. The update screen reminds you; do it.
-
Copy the new files over
Overwrite everything except
.env,public/uploads/andstorage/. Those are your data and every workspace's. -
Press Start update
The platform goes into maintenance: the panel, every workspace subdomain and the marketing site all answer 503 (a page for browsers, JSON for the API and for webhooks, which retry). Only the updater itself and the sign-in it needs stay reachable. The lock is a file,
storage/app/platform-update.lock, on purpose — the databases are the very thing being migrated. -
Watch it run
Central first, then each provisioned workspace oldest-first, each as its own request so a slow tenant can never time the whole run out. Every target shows pending, running, done, skipped or failed with the migration output under it. Workspaces still pending, provisioning or failed are skipped — they have no schema to migrate.
-
Finish
Config, route, view and application caches are cleared and the lock is released. Hard-refresh the browser once.
Keep the tab open until it finishes: the browser drives the run. If it dies half-way — the laptop closed, the network dropped — the maintenance lock stays on. Come back to the screen, which says so, and either Re-run update (migrations that already ran are skipped, so this is safe) or Release maintenance lock if you are done. A target that fails is written to the logs and the run continues to the next, so one broken workspace never holds the other forty hostage.
From the command line the same thing is php artisan migrate --force then
php artisan tenants:migrate, without the maintenance page.
Platform backups
Administration → Backups. Create backup writes one
timestamped folder under storage/app/backups holding a .sql file
for the central database and one for every workspace database. The list shows each run's
files and size; download any single dump, or delete a run.
Unlike the backup inside a workspace, this one shells out to mysqldump —
it is the one tool guaranteed to restore cleanly into the same server, and a platform host
is not the kind that disables exec(). It is found on the PATH, then next to
the PHP binary (where Laragon and XAMPP keep it); set MYSQLDUMP_PATH in
.env to be explicit. The password travels in the environment, not on the
command line.
A database that could not be dumped is skipped, marked in the output, and written to the logs — so a zero exit code is not the whole story and the screen tells you when a run finished with errors. Workspaces on custom database servers are dumped with the platform's credentials against the platform host in this version; back those up from their own server.
php artisan nx:backup # everything
php artisan nx:backup --tenant=acme # one workspace, by subdomain
The backup covers the databases, not the uploads. Platform branding,
landing images, and every workspace's logos, avatars, CVs, contracts and attachments
live under public/uploads/. Copy that folder too, and .env.
There is no restore button, deliberately; restoring is
mysql -u USER -p DATABASE < file.sql, one database at a time.
What a workspace is
Everything from Foundations onward in this guide, exactly as
written, with its own sign-in page at acme.yourplatform.com/login, its own
administrator, its own users and roles, its own settings, uploads, API tokens and
languages. The customer's administrator has Nexus's Administrator role, which
bypasses every permission check inside the workspace — and no standing whatsoever on the
platform domain.
Three screens are added to the workspace's Settings group, and one banner. They are the only places a workspace and the platform touch:
Plan & Billing
Administrators only. The current plan and seat usage, payment history, and self-checkout for renewals and upgrades.
Platform Support
Any signed-in user. Tickets to the platform operator, separate from the workspace's own helpdesk.
Help Center
Any signed-in user. The platform's knowledge base, read-only.
The seat banner
On the user and employee forms, when a plan pool is full — with an Upgrade plan button for administrators.
Inside a workspace the REST API answers on the same subdomain
(acme.yourplatform.com/api/v1/…), tokens are per workspace, and the
API section applies unchanged.
Plan & Billing
Settings → Plan & Billing, inside the workspace. Administrators only — it is the one page ordinary staff never need. Three things on it.
Current plan and usage
The plan, its status (trial, active, expired or cancelled), the date the trial ends or the paid period runs to, and two meters: employees used against the employee limit, staff against the staff limit. Blank limits read as unlimited.
Renew or change plan
Pick a plan (the current one to renew, another to change), a cycle — monthly or yearly — and a payment method. The methods are whichever gateways you have activated. Choosing a card gateway creates a pending payment and sends the administrator to the gateway's hosted checkout; on return the payment is verified with the gateway and, if paid, the subscription is extended by one or twelve months from its current end date (or from today if it had lapsed), the plan is switched if they chose a different one, and any non-payment lock is lifted on the spot. A cancelled checkout comes back to the page with nothing changed.
Choosing Bank Transfer shows your bank details and payment instructions on the page. Nothing is recorded until you record the payment in the panel; the workspace sees it in its history at that moment.
Payment history
The last twenty payments — date, amount, method, status, plan and reference — including the ones you recorded by hand and any pending or failed checkouts.
Platform Support
Settings → Platform Support, inside the workspace. Any signed-in user of the workspace can open a ticket with the platform: a subject, a category (technical, billing, feature request, other), a priority (low, medium or high) and a message. They get a ticket number and a thread; you get it under Support Tickets marked unread.
Replies from you show in the thread and flag the ticket unread for them. They can reply until you close it; a closed ticket says so and points them at opening a new one. Opening a ticket is written to the workspace's activity log.
This is deliberately not under the workspace's Support module, and its data lives in your database, not theirs — it is the customer talking to you, and it would be wrong for it to appear in their own helpdesk's queue or SLA reports.
Help Center
Settings → Help Center, inside the workspace. The published articles from your Knowledge base, grouped by category; categories with nothing published are omitted. Read-only, available to every signed-in user, and each read counts towards the article's view count in your panel.
Trials, expiry & suspension
Every request to a workspace passes one gate after its database is found, and the gate can answer with one of three pages instead of the app:
| Page | Status | When | Who fixes it |
|---|---|---|---|
| Still being provisioned | 503 | Provisioning is pending, running or has failed. The 503 stops anything caching it. | Nobody, usually — it clears itself in under a minute. If it failed, you, with Re-provision. |
| Suspended | 403 | You pressed Suspend. | You, with Activate. |
| Subscription expired | 402 | The subscription is expired or cancelled and the grace period has passed. | The workspace admin, from Plan & Billing; or you, by recording a payment or extending the dates. |
The API gets the same answers as JSON with the same status codes.
The grace period
A subscription that runs out does not lock the workspace that minute.
grace_days — default 7 — is how long after the trial end or the paid-until
date the workspace keeps working as normal. That is the window in which a customer who
meant to renew can do so without their team noticing anything.
When the grace period passes, the workspace is locked, but not bricked. Sign-in, sign-out, password reset, the language switcher and the whole of Plan & Billing stay reachable, so the administrator can sign in, see the expired page with a Renew subscription button, pay, and be back inside in the same session. Everybody else sees the same page with contact your administrator. The data is never touched by any of this.
A workspace with no subscription row at all — one created by hand in the database, say — is never locked for billing. Bookkeeping must never take a customer down.
Suspension versus expiry
Expiry is the customer's to fix; suspension is yours. Use Suspend for a dispute, an abuse report or a customer who has asked you to pause — it does not care about payment state, and a payment does not lift it. The webhook still works while a workspace is suspended, so a renewal that lands during a suspension is banked and applied when you activate them again.
Around the screen
Four things sit outside the modules and are worth knowing before you start. They are the same on the platform panel and inside a workspace.
| Where | What it does |
|---|---|
| Search, in the header | Inside a workspace, one box across every module: employees, candidates, job openings, leads, deals, contacts, companies, invoices, tickets, projects and tasks. Results are filtered by what you may see — a sales user searching a name never gets the candidate record. |
| The bell | Every notification raised for you, newest first. Opening one marks it read and jumps to the record. Mark all read clears the count. |
| Calendar | One month view fed by every module: approved leave, holidays, training sessions, interviews, birthdays, CRM activities, invoice due dates, task due dates and milestones. Sources you lack permission for are never queried. |
| The customizer | The paintbrush button, bottom right. Accent colour, light/dark/system and corner radius, saved in your own browser — it changes nothing for anybody else. Hide the button in Settings → General Settings (either panel). |
The language switcher is in the user menu, alongside your profile and sign out. On the platform domain it offers the languages ticked in Platform settings; inside a workspace, whichever are switched on in that workspace's Settings → Languages.
Everything from here to Workspace backups describes the product inside a workspace — the same text as the single-company Nexus guide, because it is the same product. "Settings" in these sections means the workspace's own settings, and "administrator" means the workspace's administrator.
General settings
Settings → General Settings. Three tabs. Each saves only its own fields, so correcting the currency cannot blank the company address.
| Tab | What lives there |
|---|---|
| Company | Application name, legal name, email, phone, website, address and tax number; logo and favicon; default language and which languages are offered; timezone, date format and the first day of the week; the dashboard footer line; whether the site name shows beside the logo; whether the customizer button appears. |
| Money & invoicing | Currency code, symbol, decimal places and whether the symbol sits before or after the number; the default tax percentage; the quote and invoice number prefixes; default payment terms in days; the terms text and footer that print on every document. |
| API | The rate limit, in requests per minute per token. See REST API. |
The legal name, address and tax number are what appear on printed quotes, invoices and contracts — not the application name, which is the label in the sidebar.
Changing the invoice prefix starts a fresh numbering run. The counter
is kept per prefix, so switching from INV- to 2026- begins at
0001 again and leaves every existing invoice number untouched. That is usually what you
want in January, and never what you want mid-year.
Module settings live on their own screens, because each is a set of records rather than a set of switches: HR Settings, CRM Settings and Project Settings.
Locations
Settings → Locations. An office, a site, a country. Every person belongs to one, and one location is the default that new people get.
Locations matter in three places: holidays are declared per location, so a Turkish office and a German one keep their own calendars; leave day counting skips the holidays of the requester's location; and attendance and headcount reports can be filtered by location.
A location cannot be deleted while people are attached to it — move them first.
Users & roles
Settings → Users & Roles. The same list as Employees, seen from the access side: who can sign in, as what, and with which sections open to them.
Seven roles ship. A role is a starting point, not a cage — it sets a sensible set of permissions when you create somebody, and you tick or untick from there.
| Role | Opens by default |
|---|---|
| Administrator | Everything. This role bypasses the permission check entirely rather than being handed every box. |
| HR | Employees, attendance, leave, payroll, recruitment, performance, reports. |
| Manager | Employees, attendance, leave, performance, projects, reports. |
| Sales | CRM, sales, support. |
| Project manager | Projects, support, reports. |
| Accountant | Payroll, sales, reports. |
| Employee | Nothing beyond self-service — which every signed-in person has. |
Seats are counted against the workspace's plan. Employees are one pool and every other role is the staff pool. When a pool is full the form is refused with an upgrade prompt and this screen shows a banner beforehand; changing somebody's role from employee to any staff role uses a staff seat. See Plans & limits and Plan & Billing.
Deactivating somebody (the toggle on the row) stops them signing in immediately and kills their API tokens with them, without deleting anything they did. That is the right move when a person leaves: their name stays on the deals they won and the tasks they finished.
Permissions
Access is by section, not by screen. Twelve sections exist, and each one is a tick box on a person's record:
| Section | Opens |
|---|---|
employees | Employees, org chart, departments, designations, onboarding, documents, announcements, assets. |
attendance | Attendance, timesheets, shifts and schedules, holidays. |
leave | Leave requests and leave types. |
payroll | Pay runs, salary structures, loans, expense claims. |
recruitment | Job openings, candidates, interviews. |
performance | Goals, reviews, training, warnings. |
crm | Leads, deals, contacts, companies, activities, campaigns, templates, forms. |
sales | Products, quotes, invoices, payments, contracts. |
support | Tickets and the knowledge base. |
projects | Every project, all tasks, time tracking, workload, templates. |
reports | HR, sales and project reports, and the activity log. |
settings | General settings, locations, users, module settings, notifications, API tokens, languages, backups. |
What everybody gets
Self-service needs no section. Every signed-in person can see their own attendance, book their own leave, read their own payslips, claim their own expenses, fill in their own timesheet, track their own goals and see the tasks assigned to them — scoped to themselves, always. See My workspace.
Approvals are the other unpermissioned door: anybody who has people reporting to them gets an Approvals screen for their own reports, without being handed the whole HR module.
Projects are the one exception to section-only access. Without the
projects section a person still reaches the projects they are a member of —
a developer needs their board without being handed the portfolio. Viewers see, members
work, owners and managers run the project. See Projects.
Hiding a menu item is presentation; the middleware on the route is the actual guard. A person who types a URL they may not have gets a 403, not the page.
Departments & designations
People → Departments and People → Designations.
A department has a name, an optional head and an optional parent — the parent is what lets the org chart draw a tree rather than a row. A designation is a job title, and it is what shows under a person's name across the whole product.
Neither can be deleted while somebody is in it. Both are used as filters on almost every HR list and report, which is the real reason to keep them tidy.
Employees
People → Employees. The directory, and the record every other HR screen hangs off.
Adding somebody
Name, email and role are required; everything else can follow. The fields that matter later:
| Field | Used by |
|---|---|
| Manager | Who approves this person's leave, timesheets and expense claims, and where they sit on the org chart. |
| Department & designation | Filters and grouping across every HR list and report. |
| Location | Which holiday calendar applies to their leave and attendance. |
| Hired on | Prorates a first-year leave allowance and drives length-of-service figures. |
| Employment type | Full time, part time, contract, intern or freelance. Shown throughout; contract end dates are surfaced on the dashboard. |
| Probation ends on | Set automatically from the hire date and the probation length in HR settings; editable. |
| Left on | Setting it takes the person out of headcount, rosters and pay runs without deleting their history. |
The employee code is generated: a prefix from settings plus the next free number, zero-padded. Codes already handed out are never reused, so a code always points at one person for the life of the system.
The employee record
Tabs across the top: profile, attendance, leave, documents, assets, salary, goals and reviews. Everything on those tabs is the same data the module screens show, filtered to this person — there is no second copy.
Salary is its own screen, and needs the payroll section:
the basic amount, the pay frequency and the components attached to this person. See
Salary structures.
Prefer Left on and the active toggle to deleting a person. Deleting is offered, but a person who has payslips, approved leave and won deals is woven through the history. Marking them as left ends their employment everywhere the system cares while keeping every record readable.
Org chart
People → Org Chart. The reporting tree, drawn from the manager on each person's record. Click a node to open the person.
Anybody with no manager is a root. If the chart looks like a lawn rather than a tree, somebody's manager field is empty.
Onboarding
People → Onboarding. Checklists for a new starter — the laptop, the accounts, the contract, the first-week introductions.
Build a template once as a list of tasks, then assign it to a person. That copies the tasks onto them with due dates counted from their start date. The screen shows every open checklist and what is outstanding on each; tick items off as they happen. A candidate hired through recruitment can have a checklist attached at the moment they are converted into an employee.
Documents
People → Documents. Every file attached to a person, in one list — contracts, certificates, ID copies, right-to-work papers — each with a category and an optional expiry date.
Expiry is the point of the screen. Filter to expiring within 30 days or already expired, and the counts at the top say how many of each there are, so a visa or a certification does not lapse quietly.
Documents are uploaded on the person's own record, not here — this screen is the
overview across everybody. Files are stored under public/uploads/ and are
reachable only through the app.
Announcements
People → Announcements. Company notices, optionally scoped to a department or a location and optionally pinned. Published announcements appear on the dashboard for the people they are aimed at; drafts appear to nobody.
Company assets
People → Company Assets. Laptops, phones, keys, cars — anything issued to a person and expected back.
An asset has a name, an asset tag, a category, a serial number, a purchase date and a cost. Two buttons drive it: Assign hands it to somebody from a date, and Return takes it back. Each assignment is kept, so the asset shows every person who has held it and the person shows what they currently hold — which is the list you want on a leaver's last day.
Working time rules
Settings → HR Settings. Five tabs of policy that the rest of the HR module reads. Set these before you start recording anything, because they change what recorded data means.
| Tab | Settings |
|---|---|
| Working time | Which days of the week are working days; the standard start and end time; grace minutes before a clock-in counts as late; the overtime multiplier used by payroll; whether attendance may be entered by hand as well as clocked. |
| Leave & probation | The month the leave year starts, and the default probation length in months. |
| Payroll & expenses | The pay day of the month, the note printed on every payslip, and the list of expense claim categories. |
| Careers page | Whether the public careers page is on, and the introduction shown at the top of it. |
| Reviews | The default question set handed to every new review cycle. |
The leave year start month is the one to get right first. It decides which year a request is charged to, when balances reset and when carry-over is calculated. Changing it after balances exist does not restate them.
Attendance
Time & attendance → Attendance. A month at a time, everybody down the side, days across the top, one coloured cell each.
How a day gets recorded
Four actions make a day: in, break start, break end, out. People clock themselves from My Attendance or through the API; HR can also type a day in directly if allow manual attendance is on. Every row records where it came from, so a hand-entered day is distinguishable from a clocked one.
When the day is closed, the worked minutes are computed as the span between in and out minus any breaks, and the day is marked against the schedule: present, late (past the start time plus the grace minutes), half day, absent, on leave or a holiday. That status is what the grid colours and what payroll later reads.
The grid
Click any cell to open that person's day and correct it. The screen also does the two bulk jobs: Export writes the month out as CSV, and Import takes a CSV from a biometric device or a spreadsheet.
The import wants a header row. Identify the person with either
employee_code or email, and give at least a date:
employee_code,date,clock_in,clock_out,break_minutes,status,note
EMP-0004,2026-08-24,09:02,17:35,45,,
EMP-0007,2026-08-24,,,,absent,Called in sick
Rows naming nobody, or with no date, are skipped rather than guessed at, and the message afterwards says how many landed and how many did not. Imported rows are marked as such, so they stay distinguishable from clocked ones.
Approved leave and holidays are drawn into the grid automatically. Nobody has to mark a public holiday as an absence.
Timesheets
Time & attendance → Timesheets. One row per person per week: the minutes worked each day, the week's total, and the overtime in it.
Press Build to generate the week's sheets from the attendance already recorded — for everybody, or for one person. Building again refreshes any sheet still in draft or rejected, and leaves submitted and approved ones alone, so it is safe to press twice.
A sheet moves draft → submitted → approved or rejected. People submit their own from My Timesheet, where they can also correct the daily figures before sending them; managers decide from here or from Approvals.
Only approved timesheets feed payroll. The overtime minutes on an approved sheet are what a pay run pays at the multiplier; a sheet still sitting in draft pays nothing.
Shifts & rosters
Time & attendance → Shifts & Schedules. A shift is a named pattern — a start, an end, a break allowance and which days it covers. Everybody who has no shift assigned follows the company default from HR settings.
The roster view assigns people to shifts across a week. Assigning changes what "late" means for that person and what a full day of theirs is worth, which is how a part-time or night-shift employee stops looking permanently absent.
Holidays
Time & attendance → Holidays. Public holidays, per year, per location. A holiday can apply to every location or just one.
Holidays are subtracted everywhere it matters: they are not working days for leave counting, they are not absences on the attendance grid, they do not cost pay, and the workload chart does not expect anybody to be at their desk. Load next year's before December — leave requests that straddle the new year need them.
Leave
Time & attendance → Leave Requests and Leave Types.
Leave types
A type is the policy. Each carries:
| Field | What it does |
|---|---|
| Days per year | The full entitlement. Zero means unlimited-but-tracked — requests are recorded and never blocked by a balance. |
| Paid | Unpaid leave costs a day of pay per day taken in the next pay run. Paid leave does not. |
| Accrual | Yearly hands over the whole entitlement at the start of the leave year. Monthly releases a twelfth per month worked, so a new starter accumulates rather than beginning with a full year in hand. |
| Carry-over max | How many unused days may cross into the next leave year. Zero means use it or lose it. |
| Requires attachment | Forces a file on the request — a sick note, for instance. |
| Minimum notice days | Blocks a request filed later than this many days before it starts. |
Either accrual prorates a hire year: somebody who joins in September does not get twelve months of holiday.
Balances
A balance row exists per person, per type, per leave year, and is created the first time it is needed. It holds what was allocated, what was carried in, what has been taken and what is pending. Two buttons on the Leave Types screen manage them: Allocate recalculates entitlements for a year, and Adjust writes a manual correction against one person with a reason — which is how you handle a one-off grant without lying about the policy.
Requesting and deciding
People file from My Leave: a type, a date range, an optional half day, and a reason. Nexus counts the working days in the range, skipping weekends and the holidays at that person's location — a half day on a single date counts as 0.5 — and refuses the request if the balance will not cover it or the notice is too short.
The request goes to the person's manager, who approves or declines it here, from Approvals, or from the email. On approval the days move from pending to taken, the attendance grid fills in, and the leave lands on the shared calendar so nobody schedules a workshop into it. The requester is notified either way, and may cancel a pending request themselves.
The Leave calendar tab shows the whole team's booked leave in a month view — the screen to check before approving anything.
Salary structures
Payroll → Salary Structures defines the components; each person's own structure is on their record, under Salary.
A component is an earning or a deduction, calculated either as a fixed amount or a percent of basic. Housing allowance, transport, a pension contribution, a union fee — you define the vocabulary your country uses.
A person's structure is their basic amount, a pay frequency (monthly, weekly or hourly) and the components attached to them with a value each. That is everything payroll needs about them; the rest it reads from the clock.
There is no tax engine. Income tax and social contributions change by country and by year, and a table that is subtly out of date is worse than none. Model them as deduction components with the rates you are told to apply — a percent of basic, or a fixed amount — and they will appear on every payslip.
Pay runs & payslips
Payroll → Pay Runs. A run covers a period and a set of people, and moves draft → approved → paid.
-
Create the run
Pick the period start and end. Nexus takes the next reference number and generates a draft payslip for every employed person with a salary structure.
-
Read the payslips
Each is built from the structure, the attendance and leave in the period, the timesheets approved in it, the expense claims approved and not yet reimbursed, and the loans being repaid. Every line shows where it came from.
-
Fix and regenerate
Corrected an absence or approved a late timesheet? Press Regenerate and the draft is rebuilt from current data. Only a draft can be regenerated.
-
Approve
Freezes the figures. Nothing recalculates after this point.
-
Pay
Marks the run paid, stamps the payslips, reduces the loan balances by the instalments taken, and marks the reimbursed expense claims as paid. The payslip_ready notification goes out if it is switched on.
What a payslip is made of
| Line | How it is calculated |
|---|---|
| Basic pay | Monthly takes the basic as-is. Weekly scales it by the days in the period. Hourly multiplies it by the minutes actually worked in the period. |
| Components | Each earning or deduction on the person's structure — a fixed amount, or a percentage of the basic just computed. |
| Overtime | Overtime minutes from timesheets approved within the period, at the hourly rate times the multiplier from HR settings. |
| Expense reimbursement | Every approved, unpaid expense claim for that person, added as an earning. |
| Unpaid leave & absence | Unpaid leave days and recorded absences, each costing one day's pay. |
| Loan instalments | The next instalment of every active loan that has started, as a deduction. |
The day rate is the basic divided by the working days in the period — weekends and that person's location holidays excluded — and the hourly rate is the day rate over their scheduled hours. Nothing is rounded until the payslip is written, so the lines add up to the total exactly.
Gross is the sum of the earnings, deductions the sum of the deductions, and net the difference. Print gives a payslip laid out for A4 or PDF; Email payslips sends each person theirs; and everybody can read their own from My Payslips whether or not you email them.
Loans & advances
Payroll → Loans & Advances. A loan has a title, an amount, a number of instalments and a start date. From that start date, each pay run deducts one instalment and reduces the remaining balance when the run is paid.
A salary advance is the same record with one instalment. The person's payslip always shows what was taken and what is left, so nobody has to keep a side spreadsheet.
Expense claims
Payroll → Expense Claims. Staff file claims from My Expenses: a category, an amount, the date it was spent, a note and a receipt.
A claim runs pending → approved or declined → paid. The manager or payroll decides; the claimant is notified. A claim can be marked paid by hand here, or left to the next pay run, which reimburses every approved unpaid claim on the payslip and marks them paid when the run is paid. Either way a claim is only ever paid once.
The categories come from HR Settings → Payroll & expenses.
Jobs & candidates
Talent → Job Openings and Candidates.
An opening has a title, a department, a location, an employment type, a description, a number of seats and a status. Published openings appear on the careers page; drafts and closed ones do not.
Candidates sit on a kanban by stage — new, screening, interview, offer, hired, rejected — and move by dragging. Each records a source (careers page, referral, LinkedIn, job board, agency, other), so you can see which channel actually produces hires. A CV and any other files attach to the record, and a rating out of five can be set from the card.
Interviews & hiring
Talent → Interviews. Book an interview against a candidate: a date and time, a length, a place — a room or a meeting link — and a panel of interviewers. Everybody involved is notified and the interview appears on the shared calendar. The interview itself runs scheduled → completed or cancelled.
Afterwards each member of the panel leaves their own feedback: a rating, written comments and a recommendation of hire, hold or reject. One entry per interviewer, so the decision is made against something written down rather than the loudest memory in the room.
Hiring
Press Hire on a candidate and Nexus creates the user account, allocates the employee code, attaches an onboarding checklist if you choose one, moves the candidate to hired, and counts a seat filled on the opening. The candidate record stays, linked to the person, so you keep the whole history of how they arrived.
Careers page
A public page at /careers, outside the login, listing every published
opening. Each has its own page with the description and an application form: name, email,
phone, a message and a CV upload. A submission creates a candidate at the
new stage with the source set to the careers page.
Switch the page on or off, and write the introduction that sits above the list, in HR Settings → Careers page. The page follows the site theme and the visitor's language.
Goals & OKRs
Talent → Goals & OKRs. A goal belongs to a person, has a title, a description, a due date and a progress percentage, and runs active → completed or cancelled.
Under a goal sit its key results — the measurable parts. Each has a title, a target, a current value and a unit, so "grow trial sign-ups" becomes "trial sign-ups: 340 of 500". Moving a key result's current value is what moves the goal's progress; nobody types the percentage.
Goals can also nest: give one a parent and a company objective carries the team goals underneath it, each with its own owner. The goal page shows the children and where each has got to.
People update their own from My Goals. Goals appear on the employee record and feed the review conversation rather than being scored automatically — the number is a prompt, not a verdict.
Performance reviews
Talent → Reviews. Reviews happen in cycles: a name, a period, a due date and a question set (the default comes from HR settings, and each cycle can differ).
Opening a cycle hands every employee two forms — a self review for them and a manager review for their manager. Each question is rated one to five with room for comment. A submitted form is scored as the mean of its ratings, so a person's cycle shows their own score beside their manager's, and the gap between the two is usually the conversation worth having.
The cycle screen tracks who has submitted and who has not.
Training
Talent → Training. A course or session: a title, a trainer, a date, a location, a cost and a description.
Add attendees from the training record; mark who actually attended afterwards. Each person's completed training shows on their employee record, which is what a certification audit asks for.
Warnings
Talent → Warnings. Disciplinary records: the person, a severity — verbal, written or final — the date it was issued, who issued it, and the reason.
A warning can be acknowledged by the employee, which timestamps that
they saw it. Warnings need the performance section — they are not on the
employee record for a colleague with only the employees permission to read.
My workspace
Every signed-in person has this group, whatever their role. Everything in it is scoped to them; there is no way to reach somebody else's row through these screens.
| Screen | What it does |
|---|---|
| My Attendance | Clock in, clock out, start and end a break. Below, this month's own grid and the running weekly total. |
| My Leave | Balances per type for the leave year, the history of requests, a new request form, and cancel on anything still pending. |
| My Payslips | Every payslip from a paid run, with the printable version. |
| My Expenses | File a claim with a receipt; watch it move to approved and then paid. |
| My Tasks | Every project task assigned to this person, across all projects, by due date. |
| My Timesheet | This week's daily minutes, editable, then submit for approval. |
| My Goals | Own goals, with a slider to move progress. |
| Approvals | What is waiting on this person as a manager — see below. |
Approvals
My workspace → Approvals. One queue with three tabs: leave requests, timesheets and expense claims filed by the people who report to this person.
This is the screen a line manager lives in, and it needs no HR permission at all — managing people is not the same as running HR. Decisions taken here are identical to those taken from the module screens: the same rules, the same notifications, the same entries in the activity log.
Leads
CRM → Leads. A lead is somebody who is not yet a contact: a name, a company name as they said it, an email, a phone, a source, a status and an owner.
Statuses (new, contacted, qualified, unqualified by default) and sources are yours to define in CRM Settings. Email sends one of your templates to the lead and files the send as an activity.
Importing
Import takes a CSV with a header row, and lets you set an owner and a source for the whole file. These columns are recognised; anything else is ignored:
name,email,phone,company,job_title,status,source,value,notes
Export writes the filtered list back out in the same shape.
Converting
Convert turns a lead into a contact, optionally a company, and a deal at the top of a pipeline, in one step. The lead is kept and points at all three, so the source and the campaign it came from survive into the reports — which is the only way to answer "which channel produces revenue" rather than "which channel produces leads".
Deals & pipelines
CRM → Deals. A deal is an opportunity: a title, a value, a company, a contact, an owner, an expected close date and a stage.
Pipelines
Settings → CRM Settings → Pipelines & stages. A pipeline is an ordered set of stages, each with a colour and a probability. New pipelines start with the standard six — New 10%, Qualified 25%, Proposal 50%, Negotiation 75%, Won 100%, Lost 0% — and you rename, reorder, add and delete from there. One pipeline is the default; a company with a direct and a channel motion runs two.
The probability is what makes the forecast mean something: weighted pipeline is the sum of each open deal's value times its stage probability. Move the stages, and the forecast moves with them.
Working the board
The board view is the pipeline as kanban — drag a deal to move it, column totals at the top. Won and Lost close a deal; losing asks for a reason from the list in CRM settings, which is what turns "we lose a lot" into "we lose on price". Reopen puts a closed deal back. Every move is logged and the owner is notified when a deal is handed to them.
A deal's page carries its activities, quotes, comments and files, so the whole story of an opportunity is on one screen.
Contacts & companies
CRM → Contacts and Companies.
A company is the organisation: name, industry, website, phone, address, owner. A contact is a person, optionally at a company, with a job title, email and phone. Deals, quotes, invoices, contracts and tickets all point at one or both, which is what makes a company page a full account view — everything sold, billed and asked, in one place.
Two buttons matter on a contact. Email sends a template and files the activity. Portal access gives that contact a password for the client portal; revoking it shuts the door without touching anything else.
Activities
CRM → Activities. Calls, meetings, emails and to-dos, each against a lead, contact, company or deal, with a due date and an owner.
Open activities with a due date appear on the shared calendar and on the dashboard of whoever owns them. Marking one complete stamps it and drops it out of the queue. The timeline on a deal or contact is these records in date order — the answer to "when did we last talk to them".
Campaigns
CRM → Campaigns. A named marketing effort: a type, a period, a budget, the actual cost so far, an owner and a status. Leads carry the campaign they came from, so a campaign's page shows what it produced — leads, deals created, deals won and the revenue against the spend.
Nexus does not send bulk mail — see Deliberately not here. A campaign here is the record you attribute against, not a mail blaster.
Email templates
CRM → Email Templates. Reusable subjects and bodies for the Email button on a lead or a contact, and for canned replies on tickets.
Merge fields are written in double braces:
{{contact.first_name}} {{contact.last_name}} {{contact.email}}
{{company.name}}
{{deal.title}} {{deal.value}}
{{user.name}} {{user.email}}
{{app.name}}
Unknown fields render as nothing rather than leaking the placeholder into a customer's inbox. Preview shows the template filled with real data before you send it to anybody.
Web forms
CRM → Web Forms. A hosted form that creates a lead. Choose the fields, the source to stamp on submissions, and who owns what comes in.
Each form gets a public URL with an unguessable key — share the link, or drop it in an iframe on your marketing site. Regenerate issues a new key and kills the old link, which is what you press when a form starts collecting rubbish.
Products & services
Sales → Products & Services. A catalogue of what you sell: a name, an SKU, whether it is a product or a service, the unit it is sold in, the price, your cost and a tax percentage. Quote and invoice lines are picked from here — choosing one fills the description, price and tax so nobody retypes them, and the cost is what a margin is measured against.
Lines can also be typed freehand, so a one-off does not need a catalogue entry.
Quotes
Sales → Quotes. A quote is a numbered document for a company or contact, optionally against a deal, with lines, a discount, tax and a validity date.
The line editor
Add lines, pick a product or type your own, set quantity, price, discount and tax. The running total updates as you type, and it is computed exactly as the server will compute it: a document-level discount is spread across the lines pro rata, and tax is charged on what remains after it. The number you watch is the number that gets stored.
The life of a quote
Draft → sent → accepted, declined or expired. Send emails it to the client contact and marks it sent. Print gives the A4 document with your logo, legal name, terms and footer. Duplicate copies it for the next near-identical job.
Clients respond in one of two ways: the public link on the emailed document, or the client portal. Either way accepting or declining stamps the quote and tells the owner.
Convert to invoice copies the accepted quote — lines, discount, tax and all — into a draft invoice, and links the two.
Invoices
Sales → Invoices. Same document, same line editor, plus money coming in.
An invoice is draft, sent, partial, paid, overdue or cancelled. Sent is what starts the clock: the due date comes from the payment terms in Settings → Money & invoicing, and an unpaid invoice past it shows as overdue without anybody marking it. Partial appears as soon as a payment arrives that does not cover the balance; paid when it does.
Print gives the A4 document, Send emails it with a public link the client can open without a login, and the portal lists it for contacts who have access.
Recurring invoices
An invoice can be marked recurring with a period. Press Issue due invoices on the invoice list and every recurring parent that has come due produces its next copy as a draft.
A button rather than a scheduler, and safe to press twice. There is no cron on shared hosting, so recurring invoices wait for somebody to press the button — and each parent only advances one period per press, so pressing it three times on the first of the month does not bill a client three times.
Payments
Sales → Payments. A payment records money received against an invoice: the amount, the date, a method and a reference.
Part payments are ordinary — record each as it arrives, and the invoice moves to partial and then paid on its own. The payments list is the answer to "what came in this month", filterable by method and date, and the aged debt table in the sales report is built from what is still outstanding.
Nexus does not take card payments. There is no gateway and no checkout: a payment row is a record that money arrived in your bank, entered by whoever reconciles the account. See Deliberately not here.
Contracts
Sales → Contracts. A service agreement, maintenance, subscription, NDA or anything else: a client, a type, a value, a start and end date, and the body of the agreement.
Draft → sent → signed, and expired or terminated at the end. Send emails the client a link to the contract as a web page they can read and sign — a typed name and a timestamp, recorded with their IP address. Print gives the paper version with your letterhead.
The signature is a record of agreement, not a qualified electronic signature under any particular jurisdiction's law. For contracts that need one, use a signing service and attach the executed PDF to the record here.
Tickets & SLA
Sales → Support Tickets. A numbered ticket per issue: a subject, a category, a priority, a contact, an assignee and a status.
Tickets run open → pending → resolved → closed. Pending means waiting on the client — and a client reply on a pending ticket reopens it automatically, which is exactly the behaviour that stops things going quiet and getting forgotten.
The SLA clock
Every ticket gets a first-response target from its priority. The defaults are set in CRM Settings → Tickets & SLA:
| Priority | First response due within |
|---|---|
| Urgent | 2 hours |
| High | 8 hours |
| Medium | 24 hours |
| Low | 72 hours |
The first staff reply stamps the response time. A ticket past its due time with no reply carries a breached badge, and the breach rate is on the dashboard and in the reports.
Replying
A reply is either public — emailed to the client, visible in their portal — or an internal note, which stays inside the team. Canned replies are your email templates, offered on the reply box already merged for this ticket's contact, so picking one drops finished text in rather than something you still have to edit.
Either side replying raises the bell for the other. A client reply on a pending ticket reopens it and notifies the assignee; the first staff reply stamps the response time against the SLA.
Tickets can be created here, by a client in the portal, or over the API — which is how you point a support mailbox or a contact form at it.
Knowledge base
Sales → Knowledge Base. Articles with a category, a title, a slug and a
body. An article marked public is readable at /kb/{slug}
without a login, so you can link one from an email or a ticket reply; the rest stay
internal. Each article counts its views, which over a few months tells you what your
customers actually keep asking.
Clients reach the knowledge base from the portal, and agents from the ticket screen — which is where an article most often saves a reply.
Client portal
A separate front door at /portal for your clients. It is not the back
office with things hidden: it is a different set of screens on a different login, and staff
never use it.
| Screen | The client can |
|---|---|
| Home | See what is outstanding: unpaid invoices with the amount due and how much of it is overdue, quotes and contracts awaiting a decision, open tickets, and the projects being run for them. |
| Quotes | Read a quote and accept or decline it. |
| Invoices | Read and print invoices, and see what has been paid. |
| Contracts | Read a contract and sign it. |
| Tickets | Raise a ticket, read the thread and reply. Internal notes are never shown. |
| Profile | Update their own details and change their password. |
Give a contact access from their record in CRM → Contacts — set a password and they can sign in. Turn the whole portal off in CRM Settings → Client portal; every portal address then returns a 404 rather than a login form.
Sign-in is rate limited to five attempts a minute per email and address, and a client only ever sees documents belonging to their own company.
Projects
Projects → Projects. A project has a name, a code, a client company and contact, a category, an owner, a period, a status, a priority, a budget and a colour.
Two fields decide how it bills: billable, and the hourly rate that logged time is charged at. A member can carry their own rate, which overrides the project's — see Billing a project.
Statuses are planning, active, on hold, completed and archived. Progress is a percentage Nexus keeps up to date: the share of the project's tasks that are done. Nobody types it.
Members and access
The Members tab is who works on it, each as a viewer, a member or a manager. This is the one place in Nexus where access is per-record rather than per-section:
| Who | Reaches |
|---|---|
Anybody with the projects section | Every project, and the portfolio screens: all tasks, time tracking, workload, templates. |
| A project viewer | That project, read-only. |
| A project member | That project: works tasks, logs time, comments, uploads. |
| A project manager, or the owner | That project, plus running it: members, settings, milestones, sprints, billing. |
So a developer with no HR or CRM access and no projects permission still
opens the two projects they were added to, and nothing else.
Tasks
Projects → All Tasks for everything, or the Tasks tab inside a project.
A task carries a title, a description, a status, a priority, assignees, a start and due date, an estimate in hours, labels and a milestone. Priorities are fixed — low, medium, high, urgent — because every screen colours them. Everything else you define in Settings → Project Settings: the statuses that become board columns, the labels, and the project categories.
Structure
| Feature | What it does |
|---|---|
| Subtasks | A checklist inside a task, for work not worth its own card. |
| Dependencies | Task B is blocked by task A. Trying to finish B while A is open is refused, naming the blocker — the rule is enforced, not advisory. |
| Recurring | Repeat every n days, weeks or months, with an optional number of repeats. Finishing the task spawns the next occurrence with its dates shifted. |
| Time | Entries logged against the task, totalled against its estimate. |
| Comments & files | The discussion and the attachments. Commenting notifies everybody on the task. |
Assigning a task notifies the assignee. Every status change is logged, and finishing a task refreshes the project's progress figure.
Board, gantt & calendar
The same tasks, four ways. Each is a tab on the project.
| View | Best for |
|---|---|
| List | Filtering and bulk work — by status, assignee, priority, milestone, label or due date. |
| Board | Daily flow. Columns are your task statuses; drag a card to move it, and the order within a column is kept. |
| Gantt | Dates and dependencies across the whole project — where the plan is stacked and where it is empty. |
| Calendar | What is due when, month by month. |
A task with no dates does not appear on the gantt or the calendar; that is usually the explanation when something seems missing from them.
Milestones & sprints
Milestones are dated markers within a project — a delivery, a sign-off, a release. Tasks point at one, so a milestone shows how much of what it depends on is done, and it appears on the gantt and the shared calendar.
Sprints are a period with a set of tasks and a goal. The sprint page draws a burndown: the estimated hours still open at the end of each day, against the straight line the team would follow finishing at an even pace. Days that have not happened yet are left blank rather than drawn flat, so the chart stops at today and does not imply a plateau that has not occurred.
Issues, notes & files
Three more tabs on a project, for the things that are not tasks:
| Tab | What it holds |
|---|---|
| Issues | Bugs and problems, with a severity, a status, a reporter and an assignee, plus fields for the steps to reproduce and the environment. Kept apart from planned work so a bug list does not distort the board — and an issue can point at the task that fixes it. |
| Discussions | Threads about the project as a whole, rather than about one task. |
| Notes | Documents that live with the project — a brief, a decision record, meeting notes. |
| Files | Every attachment on the project and its tasks, in one list. |
| Expenses | Costs booked to the project, marked billable or not. Billable ones can be pulled into an invoice. |
Time tracking
Projects → Time Tracking, or the timer on any task.
Two ways to log time: press Start on a task and stop it when you are done, or type an entry with a date, a duration and a note. One timer runs per person, so starting a second stops the first.
Stopping a timer rounds the duration up to the block set in Settings → Project Settings — with the default fifteen minutes, seven minutes becomes fifteen and sixteen becomes thirty. The rounded figure is what gets stored, so the entry matches what gets invoiced.
Billable and approved
Entries are billable or not; the default for new entries is a setting. If time approval required is on, entries must be approved before they can be invoiced — the list has per-row approve and reject, and an Approve all for a filtered selection.
Export writes the filtered list as CSV, which is what most people actually want when a client asks for a breakdown.
Workload
Projects → Workload. Estimated hours of open work due per person per week, against what their week can actually hold — hours per day times the working days, holidays excluded.
An estimate on a task shared by several assignees is split between them rather than counted twice. Over-capacity weeks are coloured, which makes this the screen to open before promising a date.
Only tasks with an estimate and a due date can be planned, so a blank-looking workload usually means the estimates are missing, not that the team is free.
Project templates
Projects → Templates. A reusable project shape: milestones and tasks with their dates expressed as offsets — day 0, day 5, day 30 — rather than as fixed dates.
Creating a project from a template asks for a start date and applies the offsets from it. Whoever creates it becomes the owner. For an agency that runs the same onboarding on every client, this is the difference between a day of setup and a minute.
Billing a project
The one place the Projects module touches Sales. On a project, Invoice time collects every billable, approved, not-yet-billed time entry and every unbilled billable expense, and creates a draft invoice for the project's client.
Before you commit to it, the screen previews what would be billed: the hours, what they come to, the expenses, and the total.
| On the invoice | How it is built |
|---|---|
| Time lines | One line per person per task, so the client sees who did what. The quantity is the hours; the price is that person's rate. |
| The rate | The rate on that person's project membership if they have one, otherwise the project's hourly rate. |
| Expense lines | One line each, at the amount spent, untaxed. |
| Tax and terms | Tax at the default percentage from Money & invoicing; the due date from the payment terms there. |
The entries and expenses are stamped as billed against that invoice, so the same hour is never invoiced twice. The invoice is a draft: check it, edit the lines, then send it like any other.
If time approval required is off, entries are approved as they are logged and are billable straight away. If it is on, nothing reaches an invoice until somebody approves it — which is the point of turning it on.
A project with no client company cannot be invoiced — there is nobody to bill. Set the client on the project first.
Dashboard
The dashboard is assembled from what you are allowed to see. A sales user gets pipeline and revenue; an HR user gets headcount, attendance and who is off today; a project manager gets delivery. Somebody with all three gets all three, in that order. Nothing is computed for a section you cannot open, so a narrow account is a fast page.
| Widget | Shows |
|---|---|
| Mine | Everybody gets this one: today's clock state, leave balance, tasks due, and what is waiting on you to approve. |
| People | Headcount and its change, joiners and leavers per month, probations and contracts ending in the next 30 days, documents about to expire; present today and late today, submitted timesheets, worked and overtime trends; pending leave and who is off today; payroll net and gross per month, the last run, pending expense claims; open jobs, the candidate funnel, and today's interviews. |
| Commercial | Open pipeline with its weighted value and its shape by stage, open leads, activities due today and overdue, deals won and lost per month; invoiced against received drawn on one money axis — never two — so the gap reads as the collection lag it is; outstanding and overdue balances with the five worst invoices; ticket volume and SLA breaches. |
| Delivery | Active projects, tasks created and completed per week, overdue tasks and what is due this week, hours logged this week against last, unbilled hours waiting to be invoiced, open issues by severity, and the milestones coming up. |
Headline figures show their change against the previous period, because a number without a direction is decoration.
Reports
Three report screens, each a set of tables with charts over them, and each table downloadable as CSV from the button beside it. Same numbers on screen, in the chart and in the file.
| Report | Tables |
|---|---|
| HR | Headcount by department, employment type and gender, with the number on probation and the average tenure; joiners, leavers and month-end headcount per month; attendance per month — present, late, half days, absent, overtime hours and late hours; leave days by type and by department; payroll gross, deductions and net per month; the recruitment funnel by stage, candidates by source, open jobs and average days to hire; expense claims by category and by status. |
| Sales | Leads by status; deals by stage with weighted value; the conversion rate; won and lost per month; revenue per month; receivables aged into not-yet-due, 0–30, 31–60, 61–90 and 90+ days; top customers by invoiced and paid; performance by owner; ticket volume and SLA; campaign results. |
| Projects | Projects by status; tasks completed per week and the on-time rate; hours logged by project and by person; billable against non-billable; estimate against actual with the variance; the workload grid; sprint velocity; issues open and resolved by severity. |
Every report takes a period — a preset or your own date range — and the filters you would
expect. All three need the reports section.
Activity log
Reports → Activity Log. Every state change in the system: who, what, when, from which address, and against which record.
Leave decided, a deal won, an invoice sent, a payslip paid, a setting changed, a backup taken, a failed email — all of it. Filter by person, by action or by date, and open the record from the row.
This is where to look when somebody asks who changed something, and where a failed notification email is recorded — the mail is wrapped so that a mail server being down can never stop a leave request being filed, and the failure lands here instead of on the screen.
Notifications & email
Settings → Notifications. Two things on one screen: how mail leaves the building, and which events raise one.
Choose SMTP, sendmail or log, set the host, port, username, password and encryption, and the from address and name. Send test email proves it before you trust it. Saving stores the values with this workspace and re-binds mail immediately — no restart, no cache clear. Leaving the password blank means "leave it alone", not "clear it".
The log mailer writes messages to storage/logs instead of sending
them, which is the right setting on a staging copy that must not email real clients.
In the SaaS edition the server is per workspace. The details on this
screen are saved in the workspace's own settings (the password encrypted), not in
.env, so one company's SMTP never touches another's. The extra choice at
the top, Platform default, means "send through whatever the platform operator
set up" — the right answer for most customers, and what a new workspace starts on. Pick
SMTP only to send from your own domain through your own provider.
Events
Ten events exist. Each raises the bell always, and sends an email only if its switch is on:
| Event | Goes to | When |
|---|---|---|
| Leave requested | The approver | Somebody files a leave request. |
| Leave approved or declined | The person who asked | A manager decides it. |
| Payslip ready | Each employee on the run | A pay run is finalised. |
| Expense claim decided | The claimant | A claim is approved or declined. |
| Task assigned | The assignee | A task is assigned to somebody. |
| Task comment | Everyone on the task | Somebody comments on it. |
| Lead or deal assigned | The owner | A lead or deal is handed over. |
| Invoice or quote sent | The client contact | Somebody presses Send. |
| Ticket reply | The client, or the agent | Either side replies. |
| Interview scheduled | Interviewers and candidate | An interview is booked. |
An optional admin copy address receives a copy of everything, which is useful while you are setting the system up and noise afterwards.
REST API
For a mobile clock-in app, a website that files leads, a status board of your own, or a support mailbox that raises tickets.
Tokens
Settings → API Tokens. Create a token, choose which member of staff it acts as, and optionally limit it to particular sections. The token is shown once — only a hash is stored, and a token list that can show you the secret is a token list that leaks every secret.
A token can never do more than its owner. Take a permission away from somebody, or deactivate them, and their integrations lose it at the same moment.
Calling it
curl https://acme.yourplatform.com/api/v1/ping \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json"
Every response has the same shape — the payload under data, paging under
meta — so a client never has to guess where to look. List endpoints take
?per_page= (clamped to 200) and ?page=.
{
"data": [ { "id": 4, "name": "Amira Haddad", "employee_code": "EMP-0004" } ],
"meta": { "page": 1, "per_page": 50, "total": 12, "last_page": 1 }
}
Endpoints
| Method | Path | Needs | What it does |
|---|---|---|---|
| GET | /api/v1/ping | any token | Confirms the token works and reports what it may reach. |
| GET | /api/v1/employees | employees | The directory, searchable and filterable by department and location. |
| GET | /api/v1/employees/{id} | employees | One person in full. |
| GET | /api/v1/attendance | attendance | Attendance rows for a date range. |
| GET | /api/v1/holidays | attendance | Holidays, optionally for one location. |
| POST | /api/v1/attendance/clock | any token | Clocks the token's owner in, out, or on and off break. |
| GET | /api/v1/leave-requests | any token | The owner's own leave requests. |
| POST | /api/v1/leave-requests | any token | Files a leave request as the owner, under the same rules as the web form. |
| GET | /api/v1/leads | crm | Leads. |
| POST | /api/v1/leads | crm | Creates a lead — the endpoint for your own website form. |
| GET | /api/v1/deals | crm | Deals with their stage and value. |
| POST | /api/v1/deals | crm | Creates a deal. |
| PATCH | /api/v1/deals/{id}/stage | crm | Moves a deal to another stage. |
| GET | /api/v1/contacts | crm | Contacts. |
| POST | /api/v1/contacts | crm | Creates a contact. |
| GET | /api/v1/companies | crm | Companies. |
| GET | /api/v1/invoices | sales | Invoices with their status and balance. |
| GET | /api/v1/invoices/{id} | sales | One invoice with its lines and payments. |
| POST | /api/v1/tickets | support | Raises a ticket — point a mailbox or a contact form here. |
| GET | /api/v1/projects | projects | Projects with their status and progress. |
| GET | /api/v1/projects/{id} | projects | One project. |
| GET | /api/v1/projects/{id}/tasks | projects | Its tasks. |
| POST | /api/v1/tasks | projects | Creates a task. |
| PATCH | /api/v1/tasks/{id} | projects | Updates a task — status, assignees, dates. |
| GET | /api/v1/time-entries | projects | Time entries for a range. |
| POST | /api/v1/time-entries | projects | Logs time. |
Clocking and leave are self-service over the API too. Those three endpoints need no section — any valid token may call them, always acting as the token's owner. That is what makes a small mobile clock-in app possible without handing it the employee directory.
Rate limits and errors
120 requests a minute per token by default (Settings → General Settings → API), counted per token rather than per address, so several integrations behind one office connection do not throttle each other.
| Status | Means |
|---|---|
401 | Missing token, unknown token, or the owner is deactivated. The message is deliberately the same for all three — a distinct "no such token" turns the endpoint into an oracle. |
403 | The token may not reach that section. |
404 | No such record, or one the owner may not see. |
422 | The payload was rejected; errors names the field. |
429 | Rate limited. |
Every API call runs as a real member of staff, so it appears in the activity log under their name with the token noted.
Languages
Settings → Languages. Eight languages ship complete: English, French, Spanish, German, Portuguese, Turkish, Hindi and Arabic. Arabic switches the whole interface to right-to-left — layout, icons, tables and charts.
Tick which languages appear in the switcher, and pick the default for people who have not chosen. The default is always kept in the active list, so the switcher can never offer a language the site will not serve.
Translating
Nexus ships no English language file: every string in the source is its own key,
so the untranslated product is English by construction and a missing translation degrades to
readable English rather than to messages.save.
That means you never need a template. Open a language and the screen lists every string in the product with its translation beside it — filter to the untranslated ones and type. Four buttons help:
| Button | Does |
|---|---|
| Download | Exports the locale as a JSON file — hand it to a translator. |
| Upload | Takes the file back, merging it in. |
| Prune | Removes translations for strings that no longer exist in the product. |
| Enable | Puts the language in the switcher. |
Files live in lang/, one JSON per locale. To add a language beyond the
eight, add it to config/languages.php with its native name and direction; it
then appears here to be translated.
Data you type — department names, leave types, pipeline stages, ticket categories — is not translated. It is your content, and it shows in whatever language you entered it.
Workspace backups
Settings → Backup & Update, inside a workspace. Take a
backup writes every table of that workspace's database to a timestamped
.sql file, and the list below lets the workspace administrator download or
delete their own dumps. It sees nothing of the platform or of any other workspace.
The dump is written in PHP rather than shelled out to mysqldump, so it works
on any host. The output is plain SQL any MySQL client will restore, so a customer's data is
never hostage to this product — they can take their database and leave, which is the
right property for a SaaS to have.
The same screen shows the version the platform is running. The update instructions on it are Nexus's own and do not apply to a workspace: updates are done once, for everyone, by the operator — see Updating.
The backup covers the database, not the uploads. Logos, avatars, CVs,
contracts and every file attached to a record live in
public/uploads/t/{workspace}/ on the server, where only the operator can
reach them. A customer who wants a complete export needs that folder from you.
For the operator's own backups of everything at once, see Platform backups.
Updating
An update is a file copy and one visit to Platform Update. The screen migrates the central database and every workspace in turn, behind a maintenance page, and releases it when done. In full:
-
Back up
Administration → Backups → Create backup, and copy
public/uploads/and.envsomewhere safe. -
Copy the new files over
Overwrite everything except
.env,public/uploads/andstorage/. -
Run the updater
Settings → General → Updates → Start update, and keep the tab open until every target reads done.
-
Check the logs
A workspace that failed to migrate is in Logs & System Health with the error. Fix the cause and re-run; targets already migrated are skipped.
With shell access, php artisan migrate --force && php artisan tenants:migrate
&& php artisan optimize:clear does the same without the maintenance page. The
version you are running is in version.txt and on every workspace's
Backup & Update screen.
Artisan commands
Everything the panel does has a command-line equivalent, plus a few things only the command line does.
| Command | Does |
|---|---|
php artisan migrate --force | Migrates the central database only. |
php artisan tenants:migrate | Migrates every provisioned workspace database. --tenants=ID for one; the id is on the workspace page. |
php artisan tenants:seed --class=DemoSeeder --tenants=ID | Fills one workspace with the twelve-person demo company — the HR, CRM and project sample data. For a demo, never for a customer. |
php artisan saas:demo --subdomain=demo | Provisions a workspace exactly as registration does, then seeds the demo company into it. Options: --name, --email, --password, --plan=slug. |
php artisan nx:backup | Dumps the central and every workspace database to storage/app/backups. --tenant=subdomain for one. |
php artisan db:seed --class=PlanSeeder | Restores the three launch plans (idempotent — matches on slug and updates). |
php artisan db:seed --class=LandingCmsSeeder | Writes the marketing site's starting copy, only if none exists. |
php artisan optimize:clear | Clears every cache. The same as the panel's Clear cache button. |
php artisan test | The test suite, on in-memory SQLite. |
Troubleshooting
The platform
| Symptom | Cause and cure |
|---|---|
Every page redirects to /setup | storage/app/installed is missing. Either the install did not finish, or the file was deleted. Re-run the wizard, or recreate the file if the database is already good. |
/setup loads on a live site | The same file is missing. Recreate it — until you do, anybody can walk the installer and wipe the central database. |
| A new workspace's address does not resolve | DNS. The wildcard record is missing or has not propagated. The platform believes the workspace is ready because it is. |
| The subdomain resolves but shows the marketing site or a 404 | The web server is not passing the subdomain to this vhost — no ServerAlias *.yourplatform.com / server_name *.yourplatform.com. A genuine 404 with the platform's styling means the subdomain is not in the domains table: check the spelling against the workspace page. |
| The browser refuses the certificate on a subdomain | The certificate covers the apex only. Issue a wildcard. |
| Provisioning fails with Access denied or CREATE command denied | The database user cannot create databases. Grant it, or set hosting mode to shared and create the database by hand, then Re-provision. |
| Provisioning hangs at Preparing… | It runs after the response in the same PHP process; on a very slow server it can take a couple of minutes. If it never finishes and nothing is in the logs, the process was killed — check max_execution_time (the job raises it, but some hosts forbid that) and storage/logs/laravel.log. |
| Payment completes at the gateway but the workspace stays expired | Neither the return nor the webhook confirmed it. Check Transactions for a pending row, then the logs for a signature failure — a webhook secret from test mode on a live key is the usual cause. Fix the secret; then either have the customer revisit the return URL or record the payment by hand. |
| A gateway does not appear on the billing page | It is not active, or a required credential is blank. Both are on Payment Gateways. |
| Everything answers 503 The platform is being updated | The maintenance lock is on. Open Settings → General → Updates and either re-run or release it. From the shell: delete storage/app/platform-update.lock. |
| 500 on every page after an update | Stale caches. Delete the files in bootstrap/cache/ and run php artisan optimize:clear. If it persists, check storage/logs/. |
| 500 on a workspace, fine on the platform | Look in storage/logs/laravel.log for a cache-tagging error — the tenant cache store must support tags, and the database and file stores do not. The shipped code never caches in tenant context; a customisation that does will produce this. |
| Backup finished with errors | mysqldump was not found or could not reach a database. Set MYSQLDUMP_PATH; the failing database is named in the logs. |
| A platform admin cannot see a section | Their account is an admin without that box ticked. Administration → Super Admins. |
Inside a workspace
| Symptom | Cause and cure |
|---|---|
| Your plan allows up to N employees | The workspace is at its seat limit. Upgrade from Plan & Billing, or the operator raises the limit on the plan or moves the workspace to another. |
| This workspace's subscription has expired | Past the grace period. The administrator renews from the button on that page; the operator can also record a payment or extend the date. |
| This workspace is suspended | Only the operator can lift it. |
| The page loads unstyled | public/build/ did not get uploaded, or the domain is not pointed at public/. Check both. |
| Logo or avatar uploads fail | public/uploads/ is not writable, or PHP's upload_max_filesize is smaller than the file. |
| No emails arrive | Settings → Notifications, press Send test email. Failures are recorded in the activity log with the reason. Check the event's switch is on, and that the mailer is not set to log. A workspace on Platform default sends through the platform's MAIL_* in .env — if that is log, nothing leaves the server until the operator sets a relay. |
| A menu item is missing | That account lacks the section. Check Settings → Users & Roles. Hiding the link and blocking the route are the same permission. |
| A leave request is refused | The balance does not cover it, the notice is shorter than the type's minimum, or the type requires an attachment. The message says which. |
| A task will not move to done | It is blocked by another task that is still open. The message names it. |
| Payroll figures look wrong | Almost always the working time settings, the holidays for that location, or a timesheet that was never approved. Regenerate the draft run after fixing the source. |
| The workload chart is empty | Tasks need both an estimate and a due date to be planned. Missing estimates read as free capacity. |
| Recurring invoices did not go out | Nothing is scheduled. Press Issue due invoices on the invoice list. |
| The portal returns 404 | It is switched off in CRM Settings → Client portal, or that contact has no portal access. |
When something goes wrong on the server, storage/logs/laravel.log has the
detail and Logs & System Health has the summary. Turn
APP_DEBUG on only long enough to read the error, and turn it off again — it
shows stack traces to everybody, including every workspace's users.
Deliberately not here
Every link in the sidebar opens a real screen. A few things a platform like this could plausibly do are absent on purpose, or stored but not yet delivered, and this guide would rather name them than let you find out:
- No platform email yet. Registration, receipts, trial and expiry reminders and ticket replies send nothing in this version. The mail settings, templates and reminder schedule are kept so the release that wires them up needs no re-entry. In-app, the expiry lock and the seat banners are what a workspace sees.
- No cron and no queue worker. Provisioning runs after the response; payments are confirmed by the gateway calling you. Nothing is scheduled — which is also why nothing sends reminders on a date. That is what lets the platform run on a plain PHP host.
- No recurring card charges. Every gateway payment is a one-off checkout for a month or a year. The customer renews by pressing a button; you are never holding a card on file, and there is nothing to cancel on your side when they leave.
- No per-plan feature switches. A plan controls seats and price. Every workspace has every module. Gating modules by plan is a product decision this version does not make for you.
- No custom domains. A workspace lives on a subdomain of your platform
domain. Pointing
hr.acme.comat a workspace needs a certificate per customer and a row in thedomainstable, and the panel has no screen for it. - No workspace export or transfer. The customer's own backup is a full SQL dump of their database, which is most of it; their uploads folder is yours to hand over.
- No restore button, on either side. Restoring overwrites a company's history, and a mis-click there is unrecoverable. The command is on the screen instead.
- No automatic update. The updater runs migrations across every database while you watch; it does not fetch code. Copying the release is yours, so the moment of downtime is yours to choose.
- Inside a workspace, everything the single-company Nexus leaves out still applies: no payroll tax engine, no bulk email sender, no card payments on the workspace's own invoices, no qualified e-signature, no accounting ledger.
Changelog
1.0.0
First release of the SaaS edition.
- Multi-tenancy — one database and one subdomain per workspace; domain lookup, holding pages for provisioning, suspension and expiry; per-workspace uploads; the REST API on every subdomain.
- Onboarding — public registration with plan choice and a live progress page; provisioning after the response with no worker; failure capture and re-run; a demo command.
- Platform panel — dashboard with revenue and sign-up trends; workspaces with live seat counts, suspend, re-provision, delete and per-workspace database servers; plans with enforced seat limits; subscriptions with manual payments and cancellation; a transactions ledger; six payment gateways with encrypted credentials and verified webhooks; landing CMS covering every section of the marketing site plus privacy and terms; support tickets; knowledge base; health checks and a structured log; super admins with per-section access; platform settings with workspace defaults, bank details and reserved subdomains; a browser-driven updater behind a maintenance lock; whole-platform backups.
- Inside a workspace — Plan & Billing with self-checkout and plan changes; Platform Support; Help Center; seat-limit banners on the user and employee forms; the whole of Nexus 1.0 (people & HR, CRM & sales, support, projects, reports, API, eight languages with RTL).
Credits & support
Nexus SaaS is built on Laravel with stancl/tenancy for the multi-tenant foundation, the Tabler interface, Alpine.js for the interactive pieces, Chart.js for the charts, Tabler Icons and Vite for the build, and the official Stripe SDK. Thanks to everyone who maintains them.
Getting help
When you write in, four things turn a slow answer into a fast one: the version from
version.txt and the runtime line on Logs & System Health, the
exact steps that produced the problem, whether it happened on the platform domain or a
workspace subdomain (and which), and the last few lines of
storage/logs/laravel.log. A screenshot of the screen you were on helps more
than a description of it.
Nexus SaaS documentation · version 1.0
Thank you for buying it.