User Settings

Language

The interface is available in English, Deutsch (German), Italiano (Italian), Español (Spanish) and Français (French). The language change takes effect immediately.

Your language preference is also used by the backend for notifications — reminder emails, and the messages sent to ntfy, Gotify and browser push. Backend messages (especially errors) always stay in English.

Date Format

Choose how dates are written: European (DD.MM.YYYY or DD-MM-YYYY), US (MM/DD/YYYY), ISO (YYYY-MM-DD), Canadian (DD/MM/YYYY), or with the month spelled out (for example “Oct 22, 2026” or “22 October, 2026”). This affects all date displays, including the date half of timestamps such as the audit log, and the expected input format when entering dates like birthdays.

Time Format

Choose whether times of day are shown in 12-hour (3:30 PM) or 24-hour (15:30) form. Automatic follows the usual style for your language in the browser; on Android it follows the device’s 12/24-hour setting. The choice is saved to your account, so it follows you to other browsers and the Android app.

Appearance

Choose your preference between light mode and dark mode. This setting is stored locally in your browser and is not synced across devices.

Custom Field Names

Define custom fields that appear on all your contacts for tracking information that doesn’t fit into the standard contact fields. You can change the order of the custom fields, though they will always be displayed after the standard fields.

Give related fields the same optional Group (for example “Travel profile”) and the contact page shows them together under that heading; on Android each group is its own drill-down. A group is only a shared label: renaming or clearing it moves no data, and fields with no group appear under “Custom fields”.

Contact Fields and Hide Empty Fields

Contact Fields (web) chooses which extended fields the contact form and contact page offer by default. Hide empty fields is an account-wide switch beside it: on a contact’s page, fields that have no value yet are not shown, and an Add a field menu lists them so you can still fill one in.

Both can be overridden for a single contact with Customize fields on that contact’s page. For each field you pick Default, Show or Hide, and you can force empty fields hidden or shown on that contact. The most specific choice wins: this contact’s override, then your account setting, then the built-in default set. Reset to default removes the contact’s overrides.

These are display preferences only. They never change a contact’s data, are not part of the contact card, and are not synced over CardDAV/CalDAV, shared, exported as vCard or JSContact, or served to MCP clients; they do not hide anything from access (sensitivity is enforced separately). They are kept with the contact in your account export (the account bundle) and move to the trash with the contact, so restoring it brings them back.

Relationship goals on a contact

These live on each contact’s page rather than in Settings, next to the relationship health badge.

  • Target closeness says how close you want to be: Immediate, Close, Casual or Distant. The badge then shows how you are doing against it (“Target: close · drifting”). If the contact has no cadence yet, you are offered one at the target’s usual interval; you can decline and keep just the target. A contact who falls behind their target appears in the dashboard’s reach-out suggestions, once per gap.
  • Ambient contact (“I see them all the time”) marks someone you are in regular contact with without logging it. Their health score counts as fully up to date and they are never overdue. People who live with you (current members of a household with your own contact) count as ambient automatically. Choosing This is me on a contact marks it ambient too, since tracking contact with yourself makes no sense; This isn’t me switches ambient off again only if choosing “This is me” was what switched it on (an ambient flag you set yourself, before or after, is left alone).
  • Skills & interests, work and anniversaries have their own sections on the contact page (drill-downs on Android) instead of fields in the edit form. Hobbies now live only under Skills & interests, which is what syncs over CardDAV. Activities a person dislikes have their own Dislikes section under Preferences.

Notifications

Choose how reminders reach you — email, ntfy, Gotify, or browser push — and register the browsers that should receive push notifications. Each channel has a test button that reports the actual failure reason rather than failing quietly.

See Notifications for what each channel needs, how they differ, and what to check when one stops arriving.

GeoPulse connection

Settings → GeoPulse connects a self-hosted GeoPulse instance so you can turn a day of location history into activity suggestions (ADR 0033). It is optional and does nothing until configured.

  1. In GeoPulse, create an API token (Profile → Security).
  2. In Settings → GeoPulse, enter the Base URL of your GeoPulse server (an http:// or https:// URL, for example http://geopulse:8080) and paste the API Token, then Save connection. The token is stored encrypted and never shown again; leave the field empty on later saves to keep the stored one.
  3. Test connection (shown once a token is stored) checks that the server is reachable and the token is accepted. Remove connection deletes the stored URL and token; activities you already logged are kept.

To use it, open Activities and choose Log from location history. Pick a date and Look up. The dialog lists the places GeoPulse recorded as stays that day (time, duration, city) and, for each, the file names of any photos GeoPulse found nearby. Choose Log activity on a stay you want to record: it opens the normal new-activity dialog pre-filled with the place and date, you choose who you were with, and save.

  • Nothing is stored until you confirm. The list is a live lookup for one date; there is no background sync.
  • You choose the contacts. Nothing is inferred from location.
  • Photos are display-only. They are shown as file names and never copied into the activity.
  • No duplicates. A stay you already logged is marked “Already logged”.
  • If the lookup fails (server down, token revoked) the dialog shows the error with a link to Settings, and you can still log the activity by hand. If only the photo check fails, the stays are still listed, marked “Photos could not be checked.”
  • Operators: set GEOPULSE_BLOCK_PRIVATE_URLS=true on any instance reachable from the internet, so a user cannot point the connection at a loopback or LAN address (see Deployment). Ownership and failure behaviour are in Integrations.
  • The Android app has the same flow (Settings → GeoPulse, and Log from location history on the Activities screen); see Android app. Both are hidden on a local (embedded) profile.

Two-factor authentication

Settings → Two-factor authentication adds a second step to signing in with your password. The second step is either a 6-digit code from an authenticator app (TOTP) or a passkey — whichever you have enrolled, and either one is enough. CardDAV sync and API tokens are not affected.

Turning on an authenticator app

  1. Choose Enable two-factor authentication.
  2. Scan the QR code with your authenticator app (or type the manual setup key), then enter the 6-digit code it shows and choose Enable and continue.
  3. Save your recovery codes (below).

Enabling your first second factor signs out every other session and device you have. Your current browser stays signed in.

Recovery codes

When your account gets its first second factor, the app shows 10 recovery codes once. Each code works a single time and takes the place of the second step — use one if you lose your authenticator app or device. They are not shown again, so copy them (Copy all codes) and store them somewhere safe, such as a password manager.

To replace them, choose Regenerate recovery codes (offered whenever any second factor is set up, including a passkey-only account) and confirm with a current authenticator code, an unused recovery code, or — if you have a passkey — Verify with a passkey instead. The old set stops working immediately and the new set is shown once. Adding a passkey to an account that already has recovery codes keeps the existing set; it does not mint new ones.

Every proof asked for while you are signed in (regenerating, removing a factor, adding another, deleting your account) shares one attempt limit: after 5 wrong codes or passkeys in a row those actions are refused for a while, even with the right code, with “Too many failed verification attempts”. It never blocks signing in.

Turning it off

Disable asks for a current code and signs out every other session. If you still have a passkey enrolled, the recovery codes and the passkeys stay; if not, the recovery codes are deleted too.

If you lose every factor

A user with no working authenticator, passkey or recovery code cannot sign in on their own. An instance admin can clear the account’s second factors from User Management using the Reset 2FA action: it disables the authenticator app, removes every registered passkey, deletes the recovery codes and ends the user’s sessions. The user can then sign in with just their password and enroll again. Use it only for a genuinely locked-out user; it is a no-op for an account with no 2FA. Only admins see it.

Accounts that sign in through an identity provider

Accounts provisioned through OIDC single sign-on cannot enroll an authenticator app or a passkey — the app refuses with “Two-factor authentication is not available for accounts that sign in through an identity provider”. Second-factor policy for those accounts belongs to the identity provider.

Passkeys

Settings → Passkeys lets you use your device’s fingerprint, face or screen lock (or a hardware security key) as the second step instead of typing a code. A passkey works alongside an authenticator app; you can have both, and at sign-in either satisfies the second step (the sign-in page offers Use a passkey instead).

  • Add a passkey: optionally give it a name (for example “Laptop” or “YubiKey”), choose Add a passkey and follow your browser’s prompt. If you cancel or the prompt times out, nothing is saved. If this passkey is your account’s first second factor, you are shown recovery codes once — see Recovery codes — and your other sessions are signed out.
  • The list shows each passkey’s name, when it was added and when it was last used. A passkey added without a name is labelled after the browser and device that created it (for example “Chrome 153 on Linux”). Rename changes the label at any time; it needs no extra proof, since a name changes nothing about your account’s security.
  • Remove a passkey: removing is deliberately harder than adding, because it weakens the account. Remove asks for a proof: a current authenticator code, a recovery code, or — via Verify with another passkey instead — an assertion from a different passkey (that button only appears when you have another one; the passkey being removed can never vouch for itself).
  • Removing your last factor: if the passkey you remove is the only second factor left (no authenticator app either), two-factor authentication is effectively off. Your recovery codes are deleted and your other sessions are signed out. Add a factor again to turn it back on.

Passkeys and FRONTEND_URL

A passkey is bound to the address you load the app from. The instance uses the hostname of its FRONTEND_URL setting as the passkey’s relying-party ID and accepts passkeys only from that origin. Two consequences:

  • Changing the hostname of FRONTEND_URL invalidates every enrolled passkey for every user. Authenticator apps and recovery codes are unaffected, so keep those set up before a move. Operators: see FRONTEND_URL and Deployment.
  • Passkeys are unavailable when FRONTEND_URL is * (the development default), because that names no concrete origin. Adding one then fails with “passkeys require FRONTEND_URL to be set to the concrete origin users load the app from”. A browser without WebAuthn support shows “This browser doesn’t support passkeys.” instead of the add form.

Passkeys on Android

Passkeys you create in the Android app are the same credentials as the ones you create on the web, because both are bound to the same instance host: the same list, the same removal rules. Native Android passkeys are an operator opt-in and only work on publicly reachable HTTPS instances. See Android app → Passkeys and ADR 0034.

Feeds

Settings → Feeds (below API tokens) creates private Atom feeds so a feed reader can follow the timelines of your contacts. Design details are in ADR 0030.

  • Create Feed: give it a name, then choose Feed of — One contact (that contact’s merged timeline) or All contacts (the aggregate across your non-archived contacts) — and a Detail level. Headlines carries entry titles and dates only; Full also copies note text, activity details and gift descriptions into the feed. By design, items above normal sensitivity are never included in a feed at either detail level.
  • From a contact: on a contact’s page, open the actions menu and choose Subscribe via feed to open the same dialog already set to that contact.
  • The feed list shows each feed’s name, what it follows, its detail level, when it was created and when it was last accessed.

The feed URL is a password

A feed’s URL contains a secret token, and anyone who has the URL can read the feed — no sign-in is involved, which is how feed readers work. Treat it like a password:

  • The URL is shown once, when you create (or rotate) the feed. If you lose it, rotate the feed.
  • Do not paste it into chats or public trackers. A feed reader keeps its own copy of everything it fetches, and this app cannot delete that copy later, so prefer a self-hosted or local reader.
  • If a URL leaks, revoke it.

Rotate, revoke, and revoke all

  • Rotate revokes the feed’s URL immediately and issues a new one with the same settings; the new URL is shown once.
  • Revoke stops one feed’s URL working immediately. It cannot be undone; create a new feed instead.
  • Revoke All revokes every active feed you have.

Each account can have at most 50 active feeds. Creating a 51st fails with “feed limit reached (maximum 50 active feeds)”; revoke one you no longer use. Rotating does not count against the cap.

Feeds are managed in the web app only; the Android app has no feed management screen.

Account bundle export

The account bundle is a single JSON file holding a complete, re-importable copy of your data. It is served by GET /api/v1/export/account (see the API reference) and is what the Android app writes with Export account bundle to file and uses to move a local-only profile to a server. The web app has no button that downloads one, but it is where the bundle is imported: Settings → Data → Import account bundle takes a bundle exported from another Mycorrhizal instance or from a local-only Android profile.

What it includes

The bundle is full fidelity, like the CSV export: it includes every item regardless of its sensitivity (normal, private and secret), relationships still awaiting confirmation (suggested rows), and embedded profile photos. There is no opt-in for sensitive data, because the bundle is your own backup landing on your own device.

Export Sensitive (private/secret) items Suggested rows Purpose
Account bundle Included Included Full backup; move between your own instances. Re-importable.
CSV (Download CSV) Included, labelled per column Included Full backup you can read in a spreadsheet; profile photos not included.
vCard (Download VCF), JSContact Left out unless you opt in Left out Handing contacts to another app or person.

Because the bundle carries everything, keep the file as private as a password-manager export.

Size limit

The bundle is built in memory as one document, and the import accepts at most 64 MiB. If your account would produce a larger bundle, the export refuses instead of handing you a file that could not be imported: the response is HTTP 507 with a message giving the bundle’s size and the 64 MiB limit. Remove contacts or profile photos you no longer need and try again, or use the CSV or vCard export. Successful exports also report the limit in an X-Mycorrhizal-Bundle-Max-Bytes response header.

Moving data between instances

Export the bundle from the source, then import it on the destination with Settings → Data → Import account bundle. You get the same review step as other imports, with per-contact add, merge or skip. The import is a one-time move, never a live sync, and re-running it does not duplicate contacts that already arrived. For moving an Android local-only profile to a server, see Android app.

Your own “Me” contact is not duplicated: the bundle records which contact is you, and the import merges it into the “Me” contact the destination account already has (it appears in the review step as a merge into that contact). If the destination has no “Me” yet, the bundle’s one becomes it.


Back to top

Mycorrhizal CRM - Self-hosted personal contact management

This site uses Just the Docs, a documentation theme for Jekyll.