# Multiple accounts

One archive can hold several Telegram accounts.

## Single-account and indexed mode

An install starts in single-account mode. It reads one account from `TELEGRAM_API_ID`, `TELEGRAM_API_HASH` and `TELEGRAM_PHONE`.

Indexed mode declares accounts with numbered variables:

| Variable | Required | Meaning |
| --- | --- | --- |
| `TG_ACCOUNT_<N>_API_ID` | yes | Integer API id from my.telegram.org |
| `TG_ACCOUNT_<N>_API_HASH` | yes | API hash |
| `TG_ACCOUNT_<N>_PHONE_NUMBER` | yes | Phone number with country code |
| `TG_ACCOUNT_<N>_LABEL` | no | Name shown in the viewer |
| `TG_ACCOUNT_<N>_SESSION_NAME` | no | Session file name in `SESSION_DIR` |

Any non-empty value in one of these five variables switches the install to indexed mode. From then on, `TELEGRAM_API_ID`, `TELEGRAM_API_HASH` and `TELEGRAM_PHONE` are not used for any account. `TELEGRAM_API_ID` must still be a number or empty, or startup stops. A per-account filter override on its own never switches modes, so a single-account install can still use `TG_ACCOUNT_1_` filters.

## Rules for account variables

- `N` starts at 1 and has no leading zeros.
- Indexes are contiguous: account 3 needs accounts 1 and 2.
- Each account needs `API_ID`, `API_HASH` and `PHONE_NUMBER`. `API_ID` must be an integer.
- Phone numbers must differ between accounts.
- Session names must differ between accounts.
- A `TG_ACCOUNT_` variable with an unknown suffix stops startup.
- An empty value counts as unset.
- Errors about the credential variables name the variable, never its value.

## Labels and session names

The label defaults to `default` for account 1 and `account<N>` for the others. The label is read again on every start, so you can rename an account at any time.

Session names resolve like this:

| Account | Session name, first match wins |
| --- | --- |
| 1 | `TG_ACCOUNT_1_SESSION_NAME`, then `SESSION_NAME`, then `telegram_backup` |
| 2 and up | `TG_ACCOUNT_<N>_SESSION_NAME`, then `telegram_backup_account<N>` |

Account 1 follows the same chain as single-account mode, so an existing install can add a second account without logging in again.

## Going from one account to two

1. Stop the stack.

   ```
   docker compose down
   ```
2. In `.env`, copy the three `TELEGRAM_*` values to `TG_ACCOUNT_1_*`. Do not set `TG_ACCOUNT_1_SESSION_NAME`. If you had set `SESSION_NAME`, keep it. The backup then reuses the existing session file.

   Keep the three old `TELEGRAM_*` lines in `.env` as well. Without them, Compose warns that `TELEGRAM_API_ID`, `TELEGRAM_API_HASH` and `TELEGRAM_PHONE` are not set. You can ignore the warnings, because indexed mode does not read these variables.
3. Add the `TG_ACCOUNT_2_*` variables.

   ```
   # Account 1: the credentials that used to be TELEGRAM_*
   TG_ACCOUNT_1_API_ID=12345678
   TG_ACCOUNT_1_API_HASH=0123456789abcdef0123456789abcdef
   TG_ACCOUNT_1_PHONE_NUMBER=+15550100001
   TG_ACCOUNT_1_LABEL=Personal

   # Account 2: the new one
   TG_ACCOUNT_2_API_ID=87654321
   TG_ACCOUNT_2_API_HASH=fedcba9876543210fedcba9876543210
   TG_ACCOUNT_2_PHONE_NUMBER=+15550100002
   TG_ACCOUNT_2_LABEL=Work
   ```
4. Log in. The login checks every account and skips any account that is already logged in. It asks only for the new account's code, then its two-step password if the account has one. It fails if the account that logs in does not own the configured phone number. With `--qr` it shows one QR code for each account still to log in.

   ```
   docker compose run --rm telegram-backup python -m telegram_archive auth
   ```

   See [Log in to Telegram](<https://geiserx.github.io/Telegram-Archive/getting-started/telegram-login/index.md>) for details.
5. Start the stack.

   ```
   docker compose up -d
   ```

The next run fetches the new account's history from the beginning. Account 1 continues from where it stopped.

The stock `docker-compose.yml` passes `.env` to the backup service through `env_file`, so the backup gets the `TG_ACCOUNT_*` variables. The viewer does not need them.

## Per-account filters

To set a chat filter for one account, add the `TG_ACCOUNT_<N>_` prefix. You can use these suffixes:

| Suffix | Overrides |
| --- | --- |
| `CHAT_IDS` | `CHAT_IDS` |
| `CHAT_TYPES` | `CHAT_TYPES` |
| `INCLUDE_CHAT_IDS` | `GLOBAL_INCLUDE_CHAT_IDS` |
| `EXCLUDE_CHAT_IDS` | `GLOBAL_EXCLUDE_CHAT_IDS` |
| `PRIVATE_INCLUDE_CHAT_IDS`, `PRIVATE_EXCLUDE_CHAT_IDS` | the same names |
| `GROUPS_INCLUDE_CHAT_IDS`, `GROUPS_EXCLUDE_CHAT_IDS` | the same names |
| `CHANNELS_INCLUDE_CHAT_IDS`, `CHANNELS_EXCLUDE_CHAT_IDS` | the same names |
| `PRIORITY_CHAT_IDS` | `PRIORITY_CHAT_IDS` |
| `SKIP_MEDIA_CHAT_IDS` | `SKIP_MEDIA_CHAT_IDS` |
| `INCLUDE_FOLDER_IDS` | `GLOBAL_INCLUDE_FOLDER_IDS` |
| `PRIVATE_INCLUDE_FOLDER_IDS`, `GROUPS_INCLUDE_FOLDER_IDS`, `CHANNELS_INCLUDE_FOLDER_IDS` | the same names |

How a value resolves for one account:

- A value in the indexed variable wins for that account.
- An empty or unset indexed variable inherits the global value.
- The literal `none`, in upper or lower case, means an empty list.
- An override for an account number that is not declared stops startup.
- A non-integer entry in an indexed id list stops startup. The error does not name the variable.

This example backs up everything for account 1 and only channels for account 2:

```
CHAT_TYPES=private,groups,channels
TG_ACCOUNT_2_CHAT_TYPES=channels
```

A global include list turns off `CHAT_TYPES`. If you set `GLOBAL_INCLUDE_CHAT_IDS`, also set `TG_ACCOUNT_2_INCLUDE_CHAT_IDS=none` so account 2 does not inherit it. [Choosing chats](<https://geiserx.github.io/Telegram-Archive/configuration/choosing-chats/index.md>) explains how the filters combine.

> [!WARNING]
>
> **Folder ids are per account**
>
> Telegram numbers folders separately in each account: folder 3 of one account has nothing to do with folder 3 of another. This applies to `GLOBAL_INCLUDE_FOLDER_IDS` and to the `PRIVATE_`, `GROUPS_` and `CHANNELS_` folder lists. With more than one account, startup stops if two or more accounts would inherit the same folder list. Set the list per account, or set it to `none` for the other accounts:
>
> ```
> GLOBAL_INCLUDE_FOLDER_IDS=3
> TG_ACCOUNT_2_INCLUDE_FOLDER_IDS=none
> ```

## Settings that stay global

Everything that is not a chat filter applies to all accounts. That includes `DOWNLOAD_MEDIA`, `MAX_MEDIA_SIZE_MB`, the listener toggles, `SKIP_TOPIC_IDS` and the transcription settings.

## How runs work

- The scheduler opens one shared Telegram connection per account.
- Accounts are backed up one after another, in configuration order. A run takes about as long as all the accounts' runs added together.
- If one account fails, the error is logged with its index and the other accounts continue.
- After login, the backup uses the Telegram user id to match each account to its data row. Changing the order of the `TG_ACCOUNT_<N>` numbers does not move data between accounts.
- All accounts share one owner id, one last backup time and one "backup running" flag. Each value shows whichever account updated it last.
- Each account gets its own listener, unless `ENABLE_LISTENER=false`. See [Real-time listener](<https://geiserx.github.io/Telegram-Archive/configuration/listener/index.md>).

## In the viewer

A small label with the account name, called an account tag, appears on chat rows and in the info panel, and the chat header names the account after the member count. Each account keeps its own tag colour. Tags only appear when the viewer login can see more than one account. On a narrow header the account's name gives way first; the chat list and the info panel keep it.

![Chat list with Personal and Work account tags](<https://geiserx.github.io/Telegram-Archive/images/screenshots/chat-list-desktop.png>)

- A group or channel that several archived Telegram accounts belong to is listed once. The viewer shows the copy from the lowest account id the user may see.
- Private chats are never merged.
- In a group or channel, a message sent by any archived Telegram account shows as your own message, on the right. When more than one archived account holds or writes in the chat, the message names its account before its time, like a channel signature, as that account's tag: the same small label, in the same colour, as on the chat list.
- A private chat between two archived accounts is one row per account. In each row that account's messages are on the right and the other account's are on the left, as in the Telegram apps, so the two rows mirror each other. The header names the row's account, and the messages carry no account name. A screen reader hears who spoke at the first message of each run.
- An admin can limit a viewer account to some Telegram accounts. See [Logins, viewer accounts and share links](<https://geiserx.github.io/Telegram-Archive/viewer/access/index.md>).

![A group both accounts hold: each account's messages on the right, named before the time with the account's tag](<https://geiserx.github.io/Telegram-Archive/images/screenshots/accounts-shared-group.png>)

![A private chat between the two accounts, opened from the Personal account's row in the Night theme](<https://geiserx.github.io/Telegram-Archive/images/screenshots/accounts-private-chat-mobile.png>)

## Caveats

- Imports from Telegram Desktop always land in the first account row, the one the account at index 1 claimed on its first login. Renumbering accounts later does not change that.
- `backfill-topics` sets `CHAT_IDS` to the chat you name. For an account with `TG_ACCOUNT_<N>_CHAT_IDS`, the command uses that list instead.
- `scripts/auth_noninteractive.py` logs in one account from `TELEGRAM_API_ID`, `TELEGRAM_API_HASH`, `TELEGRAM_PHONE` and `SESSION_NAME`, and ignores `TG_ACCOUNT_<N>_*`. To log in account N with it, pass that account's values in those variables and its session name in `SESSION_NAME`. It does not check the phone number.
- To combine two existing archives that hold different Telegram accounts, use the `merge` command. See [Merge two archives](<https://geiserx.github.io/Telegram-Archive/operations/maintenance/#merge-two-archives>).

See [Import and maintenance tasks](<https://geiserx.github.io/Telegram-Archive/operations/maintenance/index.md>) for imports and `backfill-topics`, and [Upgrading](<https://geiserx.github.io/Telegram-Archive/operations/upgrading/index.md>) for moving from 7.x.
