Configuration
Customize your app - locale, theme, layout, data conventions, auth, and more
TL;DR: All static configuration lives in one file:
src/lib/config/index.ts. Change the values, and the entire app adapts.
Straktur uses a single appConfig object for compile-time settings. It's not a .env file — it's typed TypeScript, so you get autocomplete and type safety.
src/lib/config/index.ts ← All settingsApp Identity
export const appConfig = {
name: "My App",
hasLogoIcon: true,
hasFullLogo: false,
logoExtension: "png",
}| Property | Type | Default | Description |
|---|---|---|---|
name | string | "Straktur" | App display name used in UI and emails |
hasLogoIcon | boolean | true | Set to true when logo-icon.<ext> exists in public/ |
hasFullLogo | boolean | false | Set to true when logo.<ext> exists in public/ |
logoExtension | "svg" | "png" | "png" | File extension for logo files in public/ |
Logo Files
Place your logo files in public/:
| File | Usage | Recommended size |
|---|---|---|
public/logo.<ext> | Full logo in expanded sidebar | PNG: 280×64px (displays at 140×32, @2x retina) |
public/logo-icon.<ext> | Square icon in collapsed sidebar & favicon | PNG: 64×64px (displays at 32×32, @2x retina) |
How the sidebar uses logo settings
hasFullLogo | hasLogoIcon | Expanded sidebar | Collapsed sidebar |
|---|---|---|---|
true | true | Full logo image | Icon image |
false | true | Icon + app name text | Icon image |
true | false | Full logo image | First letter of name |
false | false | App name text only | First letter of name |
You don't need both files. A common setup is hasLogoIcon: true + hasFullLogo: false — the sidebar shows the icon alongside the app name when expanded, and just the icon when collapsed.
Locale
Controls formatting of dates, numbers, and currency across the entire app.
locale: {
languageTag: "en-US",
currency: "USD",
timezone: "America/New_York",
}| Property | Type | Default | Description |
|---|---|---|---|
languageTag | string | "en-US" | BCP 47 language tag — "en-US", "pl-PL", "de-DE" |
currency | string | "USD" | ISO 4217 currency code — "USD", "PLN", "EUR" |
timezone | string | "America/New_York" | IANA timezone — "America/New_York", "Europe/Warsaw" |
All formatting utilities (formatDate, formatCurrency, formatNumber) read from these values automatically. You never pass locale strings manually.
The currency here is the default fallback. If your app supports multi-org with different currencies, the organization's currency takes precedence at runtime via the useFormatCurrency() hook.
Data Conventions
Controls how the app interprets values stored in the database.
data: {
percentStorage: "human",
}| Property | Type | Default | Description |
|---|---|---|---|
percentStorage | "human" | "ratio" | "human" | How percentages are stored in the database |
Percentage Storage
Two conventions exist for storing percentages in databases:
| Mode | DB value | Meaning | Used by |
|---|---|---|---|
"human" | 23.00 | 23% | Most business apps, readable in DB tools |
"ratio" | 0.23 | 23% | Financial systems, Intl.NumberFormat standard |
This setting affects meta.format: "percent" in DataTable columns. Set it once, and every percentage column formats correctly — no custom cell renderers needed.
// In your column definition — just works with either convention:
{
accessorKey: "commissionRate",
header: ({ column }) => <DataTableColumnHeader column={column} title="Commission" />,
meta: { format: "percent" },
}Pick one convention and be consistent across all tables. Mixing "human" and "ratio" in the same database will cause display bugs.
Theme
theme: {
defaultMode: "system",
defaultAccent: "neutral",
}| Property | Type | Default | Description |
|---|---|---|---|
defaultMode | "light" | "dark" | "system" | "system" | Default color mode for new users |
defaultAccent | string | "neutral" | Default accent color (see colors.ts for available options) |
Users can override both in the UI. These are just defaults.
Layout
layout: {
preset: "default",
}| Property | Type | Default | Description |
|---|---|---|---|
preset | "default" | "compact" | "default" | Sidebar layout variant |
| Preset | Sidebar variant | Description |
|---|---|---|
default | Full sidebar | Standard layout with collapsible icon sidebar |
compact | Inset sidebar | Smaller footprint, same collapsible behavior |
Auth & Signup
Controls authentication methods and how new users join your app. See Authentication Setup for detailed provider configuration.
auth: {
signupMode: "join_default_org_pending_approval",
defaultOrganizationSlug: "acme",
defaultJoinRole: "viewer",
newUserStatus: "pending",
methods: {
emailPassword: true,
passwordReset: true,
magicLink: false,
google: false,
github: false,
discord: false,
apple: false,
},
}| Property | Type | Default | Description |
|---|---|---|---|
signupMode | SignupMode | "join_default_org_pending_approval" | How new users are onboarded |
defaultOrganizationSlug | string | "acme" | Org to auto-join (when signup mode requires it) |
defaultJoinRole | string | "viewer" | Role assigned when auto-joining |
newUserStatus | string | "pending" | Status for new users created via auth sync |
Signup Modes
| Mode | Behavior |
|---|---|
join_default_org_pending_approval | User signs up, joins default org, admin must approve |
invite_only | Only invited users can join |
self_serve_org | User creates their own organization on signup |
Auth Methods
| Method | Default | Requires |
|---|---|---|
emailPassword | Enabled | Nothing |
passwordReset | Enabled | Email provider |
magicLink | Disabled | Email provider |
google | Disabled | OAuth credentials |
github | Disabled | OAuth credentials |
discord | Disabled | OAuth credentials |
apple | Disabled | OAuth credentials + HTTPS domain |
Routes
routes: {
defaultAfterLogin: "/examples/dashboard",
}| Property | Type | Default | Description |
|---|---|---|---|
defaultAfterLogin | string | "/examples/dashboard" | Redirect target after login, registration, or org selection |
Change this to your app's main page:
routes: {
defaultAfterLogin: "/dashboard",
}Examples Mode
Controls the visibility of the Golden Path example data (Clients, Contacts, Activities, Files).
examples: {
mode: "development", // Read from NEXT_PUBLIC_EXAMPLES_MODE
}| Mode | Behavior |
|---|---|
development | Full CRUD, "Example" badges shown in UI (default) |
demo | Full CRUD, clean names — for marketing and presentations |
hidden | Example data hidden from lists and navigation — for production |
Set via environment variable:
NEXT_PUBLIC_EXAMPLES_MODE=hiddenFull Example
Here's what a production config might look like:
export const appConfig = {
name: "Acme CRM",
hasLogoIcon: true,
hasFullLogo: true,
logoExtension: "png",
locale: {
languageTag: "pl-PL",
currency: "PLN",
timezone: "Europe/Warsaw",
},
data: {
percentStorage: "human",
},
theme: {
defaultMode: "system",
defaultAccent: "blue",
},
layout: {
preset: "default",
},
auth: {
signupMode: "invite_only",
defaultOrganizationSlug: "acme",
defaultJoinRole: "member",
newUserStatus: "pending",
methods: {
emailPassword: true,
passwordReset: true,
magicLink: false,
google: true,
github: false,
discord: false,
apple: false,
},
},
routes: {
defaultAfterLogin: "/dashboard",
},
examples: {
mode: "hidden",
},
} as const