On this page
Everything an owner or admin administers inside one organization, and where the ceiling above them sits.
MSO Cloud separates two administration levels. Org admin covers one tenant and lives under /settings — that is this page. Platform admin covers every tenant, is reachable only by a super admin, lives under /admin, and has its own guide: Hướng dẫn Super Admin. Where a setting below says a super admin must enable something first, there is no equivalent inside the tenant's own settings.
Written for: an organization's owner or admin. Before you start: you hold the
manage_org_settingscapability, which the owner and admin roles carry by default.
flowchart TB PLAT["Platform admin · /admin · super admin only<br/>customers · per-customer settings<br/>platform intelligence · operations · identity"] ORG["Org admin · /settings · one tenant<br/>members, roles, brand access · connectors + credentials<br/>AI, alerts, theming, menu<br/>LDAP connection · API tokens · backup"] USERS["The org's members"] PLAT -->|"enables plugins and LDAP for"| ORG ORG -->|"configures inside what was enabled"| USERS
Org admin#
Everything here lives under an org's own /settings area and applies to that org only.
Most of these pages are gated on the manage_org_settings capability, which by default only the owner and admin roles carry — the two admin-tier roles hold the same locked capability set; a super admin can grant a capability to another role in the platform-wide role matrix. Two pages are gated on fixed roles instead of a tunable capability: backup and restore requires owner or admin, and member administration requires owner, admin, or a role that has been granted manage_members.
- Members and roles. Invite members, assign a role (owner, admin, editor, viewer), and manage per-brand access at
/settings/members. - Brand management. Create and edit brands, and set a brand's own visual identity layer, at
/settings/brands. - Connectors and credentials. Add, edit, sync and disable marketplace and marketing connectors at
/connectors; the shared credential store (service accounts, API keys reused across connectors) is at/settings/credentials, gated onmanage_connectors. - Mapping and import templates. Configure CSV/Excel column mapping and category mapping at
/settings/mappingand/settings/categories. - Org AI settings. Configure the AI provider, budget and runtime for the org's chat assistant and AI-backed surfaces at
/settings/ai, gated onmanage_ai. - Alert and brief rule configuration. Tune which signal rules feed the org's morning brief, their thresholds, channels, and per-user mute and snooze, at
/settings/alerts. The available rule packs depend on which pillars the org runs. See Morning Brief. Which external channels a member may even choose from is a platform-level ceiling: an owner or admin cannot turn on a channel nobody has configured, or that the platform operator has not allowed for this org. Ask the operator to enable it — see Thông báo. - Notification inbox. Every member, including an owner or admin, reads their own notifications, links their own channels (browser push, Telegram, Zalo) and sets their own per-type/per-channel preference at
/settings/notifications— see User Guide. There is no org-wide override: an owner or admin configures rules and thresholds, never another member's inbox, and/settings/alertsdecides only whether a rule reaches a person at all. - Org theming and menu. Pick the org's visual theme at
/settings/organization, and rename, reorder, hide, pin or extend menu items at/settings/menu. - Report layout. Build and reshape a report with the Layout Editor (trình sửa bố cục): add cards from the insert catalogue, set widths on the 12-column grid, declare the Trang-level or Bảng-level filter bar, configure AI cards, and check the result with "Xem như khách" before saving. A menu item can pin a whole Trang or one named Bảng, and a brand can carry its own menu names, order, groups and pins over the org default, without ever surfacing an item the plugin, role or pillar gate hides. The grid grammar and the presentation document behind it: Trình sửa bố cục.
- LDAP/AD login. Configure the org's LDAP connection at
/settings/ldap, once a super admin has turned LDAP on for the org. A disabled org still reaches this page, with no config form. - API tokens. Issue and revoke org-scoped API tokens at
/settings/api-tokens. - Backup and restore. Back up and restore this org's own data at
/settings/backup.
Which settings tabs an org sees also depends on the shape of the org, not only on the viewer's role: a tenant that does not run marketplace connectors is never offered SKU-to-category vocabulary, shop aliases or creator cost sheets. A super admin working inside a tenant sees the same tab strip that tenant's own admins see, because the org, not the viewer, is the subject of the question; direct URLs still resolve for a super admin through the page guard.
Connect a Google Sheet to a dataset
An org that runs on its own datasets (HR, finance) gets them from a live Google Sheet: one workbook, one tab per dataset, synced on the connector's schedule. No engineer, no re-uploaded workbook.
- Share the sheet with the connector. At
/connectors, add a Google Sheets connector and paste the service-account JSON. The service-account address is theclient_emailfield of that JSON; share the workbook with that address as a Viewer in Google Sheets, otherwise the connector's Test button returns a not-shared error. - Point it at the workbook. Paste the spreadsheet id from the sheet's URL, then press Test — the connector answers with the workbook's real tab names, which the binding rows below use.
- Add one binding per tab. In "Liên kết tab → luồng dữ liệu", set the range to the tab
(
Employees!A1:Z) and the stream to Bộ dữ liệu của tổ chức. Save the connector once before binding a dataset — the guided step reads the sheet through the saved credential. - Bind the tab to a dataset. The binding row then offers "Nguồn bộ dữ liệu":
- Bộ dữ liệu có sẵn — pick the dataset this tab feeds, then Đọc thử tab.
- Tạo mới từ tab — type a dataset key and its Vietnamese and English names, press Đọc thử tab, tick the fields to keep under "Trường đề xuất", tick "Thuộc khoá" on the fields that identify one row, pick the "Trường thời gian", and press Xác nhận lược đồ. Nothing is confirmed on your behalf: the platform proposes, you decide.
- Map the columns. Under "Gắn cột với trường", choose the field each tab column carries; leave a column on "Không dùng cột này" to skip it. If the tab has its own row id, name it under "Cột mã dòng gốc"; leave it empty and the key columns are joined into one row key instead.
- Press "Gắn tab vào bộ dữ liệu". The platform mints the tab's mapping template and points the binding at it; the next scheduled sync loads the tab's rows.
Two rules the platform enforces rather than asks about. Each tab needs its own mapping template — two tabs may not share one — and a tab can only feed a dataset whose records ARE its rows: one sheet row per record at the dataset's key. A dataset built by combining several tabs, by splitting one row into several periods, or by pre-totalling rows into a coarser key cannot be fed from a single tab, and the binding is refused with the missing key field named rather than loaded half-right.
Bind the tabs and the reporting figures follow, computed from the tabs rather than typed in. Each tab is bound as its own dataset — Employees, OT_Tracking, Recruitment_Funnel, Finance_Budget, Promotions — and the reporting datasets behind the dashboards (headcount, recruitment funnel, staff cost, revenue, promotions) are DERIVED from them by expressions the pack carries: active headcount is the roster counted at each month excluding leavers, hires and terminations are counted at the month their onboard or resign date names, and the recruitment funnel's five stages are five date columns of one candidate row. Nothing is scripted per customer, and a figure whose source column is empty shows a dash with its reason, never a zero.
Menu configuration
/settings/menu is the single owner of how the org's navigation reads. It has six tabs, each writing one field on the organization:
| Tab | What it changes | Limit |
|---|---|---|
| Item names | Rename a menu item for this org | 48 characters per label |
| Item order | Drag items into the order this org reads them in | — |
| Group order | Reorder the menu groups | — |
| Hide items | Remove an item from the sidebar for everyone in the org | — |
| Pin dashboards | Pin saved dashboards straight into the sidebar | 8 dashboards |
| Custom items | Add the org's own named links | 24 items |
Two behaviours matter in practice. The rename and reorder tabs list only the items the org can actually see: an item hidden by the org, or belonging to a pillar the org does not run, is not offered for renaming, because renaming something invisible produces no visible change. The hide tab lists the wider set, so a hidden item can always be brought back.
Hiding an item is navigation cosmetics, not access control. The page's own help text says so: hiding removes an item from the menu and does not change who may reach it. The capability check on the page and on every procedure behind it is the security boundary. What a role may see and do is a separate, platform-level setting (see Customers below).
The same editor is reachable from the platform shell at /admin/orgs/<slug>/menu; it is one feature with two entrances, writing through the same procedures, not two independent editors.
Members and roles
/settings/members (gated on owner, admin, or a role granted manage_members) is one card: an Invite action plus a table of the org's members.
Invite takes an email, an optional display name, a role (owner, admin, editor, viewer), and a brand scope: every brand, no brand at all, or a specific pick — the pick list only offers brands not yet archived. Each existing row lets an admin change the member's role and re-open the same brand-scope picker; a Remove action opens a confirm dialog first. Two guards on top of the form itself: the org can never end up with zero members who both administer settings and can see at least one brand (removing the last such member, or narrowing their brand access to none, is refused with a named error rather than silently accepted), and a brand scope of "no brand" is a separate, deliberate choice the picker asks the admin to confirm before saving — it is easy to reach by accident and hides every brand-scoped page from that member.
A member's row states its status as invited (has not accepted yet) or active, and shows the brand summary as "every brand", "no brand", or a count — never the raw list of brand ids.
Sessions
/settings/sessions is a personal page, and its scope is exactly one session: the browser reading the page. It shows the signed-in email, the role in the active org, and whether two-factor is on, and offers a sign-out for that browser.
There is no device list and no remote revoke, on this page or anywhere in the platform. Sessions are stateless tokens, so there is nothing per device to enumerate or invalidate. The control that actually locks a person out is disabling their account: the session is re-checked against the user record on every read, and a disabled user loses their memberships and any super-admin right immediately.
Confirming a destructive action
Revoking an API token, and every other destructive action in the product, opens a confirmation dialog rather than a browser prompt. On a destructive confirm the dialog is announced as an alert, focus starts on Cancel, clicking the backdrop does not dismiss it, there is no close button, and both buttons are disabled while the change is in flight. The token revoke dialog states the consequence plainly: any system using the token loses access immediately, this cannot be undone, and a new token must be issued.
Sources#
apps/web/src/lib/rbac.ts,apps/web/src/server/page-access.ts- role capabilities and page gatesapps/web/src/server/routers/org-menu.ts,apps/web/src/app/(dashboard)/settings/menu/_components/org-menu-editor.tsx- menu configurationapps/web/src/app/(dashboard)/settings/sessions/page.tsx- session scopeapps/web/src/app/(dashboard)/settings/api-tokens/_components/api-tokens-client.tsx,packages/ui/src/components/confirm-dialog.tsx- destructive confirmapps/web/src/server/routers/notification-settings.ts,packages/notify/src/channels.ts- the notification preference path an org admin tunesapps/web/src/app/(dashboard)/settings/members/page.tsx,apps/web/src/app/(dashboard)/settings/_components/org-members-card.tsx,packages/i18n/src/messages/vi.tssettings.members*- member administration