Appearance
Feature Flags
TT Time Tracker supports per-tenant feature toggles. Some features can be disabled for specific workspaces — for example, a tenant that only tracks time and never uploads invoices can turn off the invoice feature entirely.
Where flags are stored
Feature settings are stored as a JSON column on the Tenant model:
prisma
model Tenant {
features Json? // FeatureSettings
}The FeatureSettings shape:
typescript
type FeatureSettings = {
projects?: boolean // default: true
vehicles?: boolean // default: true
zones?: boolean // requires projects; default: false
tasks?: boolean // requires projects; default: false
invoices?: boolean // requires projects; default: false
hoursOverview?: boolean // default: true
emailSync?: boolean // default: true
punchClock?: boolean // default: false
approvalWorkflow?: boolean // default: true
}A missing field means the default applies.
Frontend: useTenant() store
The tenant store exposes computed properties for each flag:
typescript
const tenant = useTenant()
tenant.hasProjects // projects feature
tenant.hasVehicles // vehicles feature
tenant.hasZones // zones (requires hasProjects)
tenant.hasTasks // tasks (requires hasProjects)
tenant.hasInvoices // invoices (requires hasProjects)
tenant.hasHoursOverview // hours overview dashboard
tenant.hasEmailSync // email sync configuration
tenant.hasPunchClock // punch clock widget
tenant.hasApprovalWorkflow // entry approval workflowThese are used in components to conditionally render UI and in route guards to redirect users away from disabled features.
Backend: FeatureGuard
The FeatureGuard and @RequireFeature() decorator gate API endpoints:
typescript
import { RequireFeature } from '../../casl/feature-flag.decorator'
@Get()
@RequireFeature('emailSync')
findSyncs() { ... }If the tenant's features.emailSync is false, the guard returns 403 Forbidden before the controller method runs.
Enabling/disabling features
Admins can toggle features in the workspace Settings page. Feature changes take effect immediately — the frontend re-fetches the tenant on each navigation.
Super admins can also modify features directly via the super-admin tenant management UI at /admin/tenants/:tenantId.
Dependency between flags
Some flags only make sense when a parent flag is enabled:
zonesrequiresprojects: truetasksrequiresprojects: trueinvoicesrequiresprojects: true
The frontend store enforces these dependencies:
typescript
const hasZones = computed(() => !!hasProjects.value && !!features.value?.zones)
const hasTasks = computed(() => !!hasProjects.value && !!features.value?.tasks)
const hasInvoices = computed(() => !!hasProjects.value && !!features.value?.invoices)If projects is disabled, zones, tasks, and invoices are also effectively disabled regardless of their individual settings.