Skip to content

Workspace Settings

TruePPM’s workspace is the installation itself — a single-tenant configuration row that backs the pages under Workspace → Settings: General, Members, Invites, Groups & teams, and Programs, plus workspace archive/delete actions (#517/#518/#519, ADR-0087). ADR-0087 scoped the original three — General, Members, and Groups & teams — and the section has grown since.

Multi-tenancy is an Enterprise feature. In the community edition there is exactly one workspace per deployment.


Settings help comes at two altitudes: a link on every section, and a on individual fields that need it.

Every section in Workspace, Program, and Project settings ends its description with a Learn more → link to the page documenting that section. It sits at the tail of the sentence under the section title, opens in a new tab, and — like the field-level ⓘ below — is not permission-gated: someone who cannot change a setting can still read what it does.

The link is announced to screen readers by section (“Learn more about General, opens in a new tab”), not as a bare “Learn more”, so the several dozen of them stay distinguishable when tabbing or listing links.

Section help answers “what is this page and where is it documented”. Field help, below, answers “what are my choices for this input”. They are complementary, and most sections carry both.

Settings fields that carry jargon, a policy choice, or an inheritance cascade also carry a contextual-help affordance — a circled in the field’s label row. Opening it explains the setting in plain language and, where a deeper guide exists, deep-links to it via Learn more →.

The ⓘ supplements the always-visible hint beneath each field rather than replacing it. The hint answers “what does my current pick mean”; the popover answers “what are all my choices”.

Three behaviors are worth knowing:

  • It is a non-modal dialog, not a tooltip. The panel contains a link, and a link inside an aria-describedby tooltip cannot be reached, so the affordance is a role="dialog" with aria-modal="false". Focus moves into the panel on open, Tab reaches the Learn more → link, and Esc closes the panel and returns focus to the ⓘ. Opened from inside a settings modal, Esc peels one layer at a time — the popover first, the modal second.
  • There is no permission gate. The help renders regardless of role, so a member with read-only access can understand a setting without the rights to change it.
  • Its screen-reader name is the field. The trigger announces as “About the <field> options” rather than as an unlabeled icon button.

On the General page the ⓘ appears on these fields:

FieldLearn more →
Default timezoneTimezone & date format
Fiscal year startsFiscal year start
Work weekWorking calendars
Default project viewThis page
Iteration terminologyMethodology preset
Allow guestsSharing & access
Public sharingSharing & access
Keep Monte Carlo run historyForecast history
Run history limitForecast history
Run attribution visible toForecast history
Story picker shows Ready stories only, by defaultSprint backlog table
Duration change → percent completeThis page
Program & project overridesThis page
Forecast-history overridesRetention

The same affordance appears throughout the program and project settings pages.


FieldTypeDefaultDescription
namestring"TruePPM Workspace"Display name shown in the nav header and email footers.
subdomainstring""Read-only via the API. Reserved for a future hosted edition; self-hosted installs leave this blank.
timezonestring (IANA)"UTC"Default timezone used for display and for interpreting dates without explicit timezone info.
fiscal_year_start_monthinteger (1–12)1Fiscal-year start month. Drives quarter labels across the workspace, including the Schedule timeline.
fiscal_year_start_dayinteger (1–31)1Fiscal-year start day, validated against the month (year-agnostic: February caps at 28; 30-day months reject 31).
fiscal_year_start_displaystring"January 1"Read-only. Human label derived from month + day, e.g. "April 6".
work_weekarray of 7 booleansMon–Fri true, Sat–Sun falseWorking-day flags, Monday through Sunday. Controls which days the CPM engine treats as working days when no project calendar overrides.
default_project_viewstring"board"The view tab that opens by default when a user opens a project ("board", "schedule", etc.).
allow_guestsbooleantrueWhether users with guest status may be added to projects. This is the workspace default; programs and projects inherit it and may override it per scope. See Sharing & Access Inheritance.
public_sharingbooleanfalseWhen true, designated read-only views may be shared via link so anyone with the link can view without signing in. This is the workspace default; programs and projects inherit it and may override it per scope. See Sharing & Access Inheritance.
public_sharing_override_policystring"suggest"Whether downstream scopes may override the workspace sharing values. "suggest" (default) lets programs/projects override; "enforce" makes the workspace value a hard ceiling. enforce is an Enterprise capability — in the community edition it degrades to suggest (no lock).
sprint_picker_ready_only_default
(ships in 0.4)
booleantrueWhether the sprint story picker starts filtered to Definition-of-Ready stories. This is the workspace default; programs and projects inherit it and may override it per scope (Shape A: null override = inherit). Advisory only — the picker’s own “Show all” toggle always reveals a not-ready story, and committing one is never blocked. There is no override policy / enforcement seam for this field.
  • Any active workspace member can GET /api/v1/workspace/.
  • Workspace Admin or Owner is required to PATCH /api/v1/workspace/.

The workspace row is created lazily on first access — no seed migration is needed on a fresh installation.

The Fiscal year starts control offers four quick presets (Jan 1, Apr 1, Jul 1, Oct 1) plus a Custom… option that opens a month + day picker for arbitrary starts such as the UK tax year (April 6). The value is year-agnostic — it stores only month and day — so the day is validated against the month (February is capped at 28; 30-day months reject 31), enforced server-side on PATCH.

This anchor controls how quarters are labeled across the workspace. On the Schedule timeline a fiscal year that starts in April shows Q1 = Apr–Jun, labeled Q1 FY27 (fiscal years are named by the calendar year in which they end). See Fiscal quarters.

Upgrade note. This setting replaced the earlier free-text fiscal_year_start string. The upgrade migration parses existing values ("January 1", "April", "4/1", …) into the structured month/day pair; anything unrecognized falls back to January 1 and is logged.

The Workspace logo control lets an Owner or Admin upload a square logo that surfaces in the top bar beside the workspace name. When no logo is set, the top bar falls back to a letter-mark derived from the workspace name.

  • Formats: PNG or WebP only. SVG is rejected — an SVG can carry embedded script, so accepting one would open a stored-XSS vector.
  • Size: 2 MB maximum. Larger files return HTTP 413.
  • Dimensions: at least 256×256 is recommended. The browser warns below that size but still allows the upload; the server does not enforce a minimum.
  • Validation: the server identifies the image by its magic bytes, not the declared Content-Type, so a mislabeled or disguised file is rejected with HTTP 415.

The logo is served from a public endpoint (GET /api/v1/workspace/logo/) with X-Content-Type-Options: nosniff and Content-Disposition: inline — branding is non-sensitive, and a public URL keeps it usable in an <img> tag without attaching a bearer token. Replacing the logo deletes the previous blob; Remove (DELETE /api/v1/workspace/logo/) clears it and restores the letter-mark.

MethodPathAccessDescription
GET/api/v1/workspace/logo/PublicServe the current logo (404 when unset).
POST/api/v1/workspace/logo/Admin+Upload/replace the logo (multipart file).
DELETE/api/v1/workspace/logo/Admin+Clear the logo.

The General settings response exposes logo_url (a cache-busting public URL) or null when no logo is set.


Workspace roles are separate from per-project roles and use a coarser three-level hierarchy:

RoleOrdinalDescription
Member100Default for all workspace users. Can read workspace-level data and access projects they are invited to.
Admin300Can manage members (invite, change roles, deactivate), manage groups, and edit workspace-level settings.
Owner400Same capabilities as Admin. At least one Owner must exist at all times (last-Owner guard).

These role ordinals are distinct from the five project-scoped roles (Owner/Admin/Scheduler/Member/Viewer — see Roles and Permissions). A workspace Member may hold any project role; a workspace Admin is not automatically an admin on any project.

status is orthogonal to role — it tracks account lifecycle, not permission tier:

StatusMeaning
activeNormal — the user can authenticate and access their projects.
guestExternal collaborator. Permitted only when allow_guests is enabled on the workspace.
deactivatedThe user’s Django account is disabled (is_active=false) and they cannot authenticate. Deactivation does not delete the user or their data.

Deactivating a user sets auth.User.is_active = false atomically inside the same database transaction — the user is immediately locked out of authentication. To restore access, set their status back to active.

The workspace must always have at least one user with the Owner role. Attempting to demote, deactivate, or remove the last Owner returns HTTP 400:

{"detail": "Cannot demote the last Owner of the workspace."}

Member list responses include sso: false and two_fa: false in the community edition. These fields report governed SSO and two-factor enforcement — whether a member is provisioned and policy-controlled from a directory of record — which is an Enterprise feature; the fields are placeholders and carry no functional meaning in OSS.

They do not describe basic login federation. Pointing TruePPM at your own identity provider so your team logs in via OIDC / OAuth2 ships in the OSS core at 0.4 — that is login-only federation, not directory governance. For the full carve-out (log in via your own IdP → OSS; provision/deprovision/govern from a directory → Enterprise) and a dated comparison against the open-core competition, see SSO Is Not an Enterprise Feature.

The Members page provides an Export CSV action that downloads the member list as a CSV file. The export is generated entirely in the browser — it requires no server endpoint and never leaves the client until you save it.

  • The file is named trueppm-workspace-members.csv.
  • Columns are Name, Email, Role, Status, and Groups (a member’s groups are joined into one semicolon-separated cell).
  • The export reflects the currently visible rows — if a search term or role filter is active, only the matching members are exported. Clear the filters to export the full roster.

This feature was added in 0.3.

  • Workspace Admin+ can list all members and perform role/status changes.
  • Non-admin members see only their own membership row.
  • A user cannot assign a role above their own (HTTP 403 if attempted).

Invites (/settings/members → invite flow)

Section titled “Invites (/settings/members → invite flow)”

Workspace Admins send email invitations to bring new users into the workspace.

  1. An Admin POSTs to /api/v1/workspace/invites/ with {email, role}.
  2. The API creates a pending invite row and sets email_pending=true. The raw token is emailed to the recipient, never stored in the database (only its SHA-256 hash is persisted).
  3. The drain_invite_emails Celery Beat task dispatches the email every 30 s (5-minute orphan window to avoid racing the create transaction). Email delivery failures are retried up to 3 times; at exhaustion the invite is marked failed, and an admin can re-send it (see Resend an invite) without revoking and re-creating it.
  4. The recipient clicks the link to reach the accept flow. They POST to /api/v1/workspace/invites/accept/ with {token, username, password}. This endpoint:
    • is publicly accessible (no session required),
    • hashes the submitted token and looks up a non-expired pending invite,
    • provisions a new User account or links the invite to an existing account if the invite email matches,
    • creates a WorkspaceMembership at the invited role,
    • marks the invite accepted.
  5. Error responses are generic (“invalid or expired token”) to prevent token enumeration.
  • Tokens are generated with secrets.token_urlsafe(32) (256 bits of entropy).
  • Only the SHA-256 hash is stored permanently.
  • The raw token is held transiently in email_token until the drain sends the email, then cleared — a database snapshot taken after delivery contains only the hash.
  • The accept endpoint is rate-limited to 20 requests/minute per IP address.

Invites expire 7 days after creation. Statuses:

StatusMeaning
pendingAwaiting acceptance (or email delivery).
acceptedAccepted; membership created.
revokedCanceled by an Admin before acceptance.
expiredTTL elapsed without acceptance.

Accepted, revoked, and expired invites older than 30 days are purged by a nightly purge_stale_invites Beat task.

A pending or failed invite can be re-sent without revoking and re-creating it. The Members page offers a per-row Resend action and a Resend all button that re-queues every outstanding invite in one request. This was added in 0.3.

Resending re-issues the token: a fresh raw token is generated and emailed, so any earlier link the recipient still holds stops working. The invite’s 7-day TTL is reset from the resend, and the email re-enters the same outbox drain described above. A resend on an invite whose email is still in flight is an idempotent no-op — it will not send twice.

MethodPathAccessDescription
POST/api/v1/workspace/invites/{id}/resend/Admin+Re-issue and re-queue one invite. Returns 202 {"queued": true}.
POST/api/v1/workspace/invites/resend-all/Admin+Re-queue every pending/failed invite. Returns 202 {"requeued": <count>}.

Only pending and failed invites are resendable — resending an accepted, revoked, or expired invite returns HTTP 409. The per-invite endpoint is rate-limited to 5 requests/minute; the bulk endpoint bundles every invite into a single throttle bucket so it cannot be used to flood recipients with email.

Invite emails use the same SMTP outbox as notification emails. SMTP must be configured for invites to be delivered. See Outbound Email (SMTP) for transport configuration.


Groups let workspace Admins grant multiple users access to multiple projects in one operation. A group has a name, an optional description, an optional lead, and a list of members.

Each group card has a Manage button that opens a management panel (a side drawer on desktop, a bottom sheet on mobile). From there an Admin can:

  • Add or remove members — pick any workspace member from the searchable list; removing a member revokes the access the group conferred on them.
  • Grant or revoke project access — pick a project, choose the role to confer (Viewer, Team Member, Resource Manager, or Project Manager), and grant it; each grant shows its conferred role and can be revoked.

Every change takes effect immediately (there is no separate save step) and runs the project-access cascade described below. Directory (LDAP/AD) sync of group membership is a TruePPM Enterprise capability.

Linking a group to a project (via POST /api/v1/workspace/groups/{id}/projects/) confers a project role on every current group member. This reconciliation (reconcile_group_access) runs synchronously in the request transaction and creates or updates ProjectMembership rows for all affected (member × project) pairs. Board-presence events are broadcast to affected project WebSocket consumers after the transaction commits.

The same reconciliation runs when:

  • a member is added to or removed from the group,
  • the conferred role for a project link is changed,
  • the group is deleted (all group-conferred memberships are removed).

Group-conferred memberships are tagged internally (source_group). If a user already has a direct ProjectMembership on a project (one not sourced from a group), that direct grant is never overwritten or revoked by group reconciliation. Group membership is additive: it only removes the rows it created.

A group can never confer the Owner project role. The conferred role is validated to reject Owner at write time. This preserves the project last-Owner guard — ownership must always be explicitly granted to an individual.

MethodPathAccessDescription
GET/api/v1/workspace/groups/Any memberList all groups.
POST/api/v1/workspace/groups/Admin+Create a group.
GET/api/v1/workspace/groups/{id}/Any memberRetrieve a group.
PATCH/api/v1/workspace/groups/{id}/Admin+Update name, description, or lead.
DELETE/api/v1/workspace/groups/{id}/Admin+Delete group (removes group-conferred memberships).
POST/api/v1/workspace/groups/{id}/members/Admin+Add a member (triggers cascade).
DELETE/api/v1/workspace/groups/{id}/members/{user_id}/Admin+Remove a member (triggers cascade).
POST/api/v1/workspace/groups/{id}/projects/Admin+Link the group to a project with a conferred role (triggers cascade).
DELETE/api/v1/workspace/groups/{id}/projects/{project_id}/Admin+Unlink the group from a project (removes group-conferred memberships).

The Programs section is a bulk-edit matrix over every program in the workspace. It is the workspace-scoped counterpart of the program’s own Projects → bulk-edit surface: instead of editing one program at a time, an admin sets a single field across a selection of programs in one atomic call.

  1. Check the programs you want to change (or select all).
  2. Pick one field to set:
    • Methodology — the default delivery model new projects in each program inherit. Under a workspace inherit methodology lock this column is read-only (display-only).
    • Iteration label — the program’s iteration-container label; Reset to inherited clears the override so the program inherits the workspace default again.
    • Slip propagation — what each program does when a cross-project dependency slips: No action, Warn only, or Block & escalate.
    • Escalation days — how long a cross-project slip may persist before escalation (1–30 days).
  3. Apply. The change is all-or-nothing across the selected rows and bumps each program’s server_version. A selection can touch at most 200 programs per call.

Inherited values are shown distinctly from explicit overrides. Methodology and the two risk-policy fields are always-set columns (no “inherit” state).

  • Any active workspace member can read the program list (the matrix is visible read-only, without the edit action bar).
  • Workspace Admin or Owner is required to apply a bulk change.
MethodPathAccessDescription
GET/api/v1/programs/Any memberList programs (the matrix reads methodology, iteration_label, risk_slip_propagation, and risk_escalation_days).
POST/api/v1/programs/bulk-fields/Admin+Set one field across the selected programs in one atomic call.

The Demo data page (Settings → System → Demo data) lists every sample program bundled with this instance — the same catalog the Load demo data picker on the Programs page offers — with, for each one, its entity counts (projects / tasks / resources), file size, and a SHA-256 of the exact bytes the download serves. A Download button lets you read the raw JSON fixture before trusting it, and a Load button builds the sample program in one click, same as the Programs-page picker. See Sample projects & JSON import/export for what each bundled sample contains and the full load/import/export walkthrough, and Inspect before you import for verifying a download’s hash from the command line.

Two of the bundled programs build their data procedurally in Python rather than from a file — the page says so explicitly rather than letting a reader believe they have audited every sample once they have checked the downloadable ones.

Unlike every other page under Workspace → Settings, Demo data carries no workspace-admin requirement — any authenticated user can open it, and any authenticated user can already call the underlying load endpoint (they become the new program’s owner). The reasoning: this page only ever discloses public, Apache-2.0-licensed files already committed to the OSS repository, so gating the page would block a non-admin from following the demo loader’s own Inspect files ↗ link — a link any authenticated user can already see — into a page they suddenly cannot open. The mutation this page exposes (loading a sample) is gated the same way it always was: by the API’s own rate limit, not by a role check on this page.

MethodPathAccessNotes
GET/api/v1/programs/samples/Any authenticated userThe catalog: key, title, description, size, SHA-256, and entity counts per bundled sample.
GET/api/v1/programs/samples/{key}/download/Any authenticated userThe exact bytes of one fixture, rate-limited per account.
POST/api/v1/programs/load-sample/Any authenticated userBuilds the sample program; the caller becomes its owner. Rate-limited (six loads per minute per account).

The Archive / Delete section holds the workspace-wide actions that cannot be undone. Each requires an explicit confirmation before it runs.

Builds a full archive (JSON plus attachments) of everything in the workspace — members, groups, programs, projects, tasks, baselines, and history. The export runs in the background and TruePPM emails a download link when it is ready; the link expires after a few days. Take an export before either of the destructive actions below. See Data export.

Hands workspace ownership to another active member. The transfer demotes you to Admin in the same operation, so it is not a way to add a second Owner — a workspace has exactly one. Only an active member can receive ownership; invited-but-unaccepted users do not appear in the picker. See Roles & permissions.

Permanently deletes the workspace and all of its data: every program, project, task, baseline, group, and member. This cannot be undone, and every member loses access immediately. Confirmation requires typing the workspace name exactly.

This is not the same as deleting a project. A deleted project passes through Trash and can be restored inside the retention window; a deleted workspace does not.