Skip to content

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 workflow

These 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:

  • zones requires projects: true
  • tasks requires projects: true
  • invoices requires projects: 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.

TT Time Tracker — Internal Documentation