app.config.yaml ​
app.config.yaml holds the instance's settings: sign-in, registration, media, and optional features. Start from app.config.example.yaml. The server checks the whole file when it starts, and refuses to start with a clear message if something is wrong, rather than guessing.
Settings that can also come from the environment (marked below) take the environment's value when both are set.
Sign-in and registration ​
yaml
auth:
registrationPolicy: open
adminApprovalRequired: false
email:
enabled: true
methodKey: email
verificationRequired: false
authMethods:
google:
enabled: false
methodKey: google
type: oidc
oauth:
clientId: ""
clientSecret: ""
authUrl: "https://accounts.google.com/o/oauth2/v2/auth"
tokenUrl: "https://oauth2.googleapis.com/token"
userInfoUrl: "https://openidconnect.googleapis.com/v1/userinfo"
idField: "sub"
scope: "openid profile email"| Setting | What it does |
|---|---|
registrationPolicy | open lets anyone sign up. invite-only and closed both turn sign-up off, by email and by provider |
adminApprovalRequired | New accounts wait for an admin under Manage instance → Approval before they can use the instance |
email.enabled | Email and password sign-in |
email.verificationRequired | New accounts confirm their email address first. Needs email set up |
authMethods | Sign-in providers, one block each |
Sign-in providers ​
Each block under authMethods is one provider, shown as a button on the sign-in page:
type:oidcfor OpenID Connect, oroauth2.methodKey: the provider's name in URLs. Registerhttps://chat.example.com/oauth/<methodKey>/callbackas the redirect URI with the provider.oauth:clientId,clientSecret,authUrl,tokenUrl,userInfoUrl,idField(the user-info field that identifies the person, such assuborid) andscope. All are required.position: optional, the button's order.
The example file has Google, GitHub, Discord, Steam and Keycloak ready to fill in. Any other OIDC or OAuth 2 provider works the same way.
Servers ​
yaml
config:
joinDefaultServer: true
defaultServerId: 1
usersCanCreateServers: false
buzz:
enabled: true
cooldownSeconds: 60| Setting | What it does |
|---|---|
joinDefaultServer | New accounts join the server defaultServerId. If that server doesn't exist yet, nothing happens |
defaultServerId | Required with joinDefaultServer. On a new instance, the first server created has ID 1 |
usersCanCreateServers | Whether everyone can create servers. Moderators and admins always can |
buzz.enabled | /buzz, which shakes the other person's window in a direct message |
buzz.cooldownSeconds | Seconds between one person's buzzes in a conversation, 1 to 86,400. Also BUZZ_COOLDOWN_SECONDS |
The whole config: block is optional.
Media ​
yaml
media:
storage: local
local:
dataDir: /data
uploads:
maxAttachmentsPerMessage: 10
# maxFileSizeMb: 100 # operator override of the saved instance setting
defaultMaxFileSizeMb: 100
minFileSizeMb: 1
maxAllowedFileSizeMb: 1024
jsonBodyLimitBytes: 102400
emoji:
maxArchiveBytes: 33554432
maxImageBytes: 1048576
maxFilesPerImport: 250
maxArchiveEntries: 1000Where uploads are stored, and which files people can upload. See Media storage for local and S3 storage and the uploads: settings.
Upload size, attachment count, JSON size and emoji limits can all be overridden in the server environment. See the commented variables in .env.example and the matching media.uploads values above. Environment values win over YAML; an explicit maxFileSizeMb also overrides the saved administrator setting. Restart backend workers after changing configuration. Clients receive the effective limits from the instance.
Soundboard ​
yaml
soundboard:
maxDurationSeconds: 30
maxFileSizeMb: 5
maxSoundsPerServer: 50
audioExtensions: [mp3, wav, ogg, webm, m4a, aac, flac, opus]This optional top-level block sets soundboard limits for every server on the instance. Environment overrides are SOUNDBOARD_MAX_DURATION_SECONDS, SOUNDBOARD_MAX_FILE_SIZE_MB, SOUNDBOARD_MAX_SOUNDS_PER_SERVER and comma-separated SOUNDBOARD_AUDIO_EXTENSIONS. Omitted settings use the defaults above. Restart the backend after changing the file; clients receive the effective limits through instance details.
Duration, file size (in MiB) and sound count must be positive whole numbers. The defaults are deliberately small, not a ceiling: raise them to allow long clips such as full tracks (for example maxDurationSeconds: 600 and maxFileSizeMb: 25). The general upload size limit under Manage instance → Config also applies; the lower size limit wins, so raise it too if it is lower. Durations are limited only by what the database can store (about 24 days). audioExtensions must be a non-empty selection from the formats above. Leading dots and uppercase extensions are normalized. Unsupported formats and unknown settings fail startup.
Each member's browser decodes a sound into memory the first time it plays, at roughly 20 MB per minute of stereo audio, so very long clips cost memory on every listener's device.
The upload form checks these limits and decodes the audio to measure its duration. Files must use a codec the browser can decode; accepting an M4A container does not transcode its contents. The backend checks file size, format signatures, reported duration and sound count. Existing sounds remain available when limits are lowered.
GIF search ​
yaml
gifs:
enabled: true
contentFilter: mediumThe composer's GIF button searches KLIPY and gifgooder. It stays off until one of them is set up, and offers each one that is, as tabs when there are two. For KLIPY, set KLIPY_API_KEY in the environment (preferred), apiKey: here, or the key in Manage instance → Config. For gifgooder, set GIFGOODER_URL and GIFGOODER_TOKEN in the environment; they can't be set here. contentFilter is high, medium, low or off, and applies to KLIPY only.
Bots ​
yaml
bots:
maxPerServer: 50
maxPerCreator: 10How many bots a server can have, and how many one person can create in it, from 1 to 1,000. Disabled and suspended bots count too. Optional.
Federation ​
yaml
federation:
enabled: false
privateKeyPath: /data/federation/instance-ed25519.pem
allowedInstances: []
blockedInstances: []
approvalRequired: false
allowInsecureHttp: false
queueWorkers: separate
media:
prune:
daysSinceLastAccess: 30
maxOverallSizeGB: 10Off unless enabled. See Federation for what each setting does. In Docker, keep privateKeyPath on /data and queueWorkers: separate, since the image runs the federation workers itself.