You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE]: multiple auth profiles per provider #5391
I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
this is a reopening of #893. i think it would be helpful to be able to have multiple profiles per provider. they can be switched between in the provider picker or set via command like (e.g. --provider X --profile Y).
#893 (comment) recommends an opencode.json per directory, but it would be nice to be able to maintain it globally and/or from the command line
I'd enjoy this feature, but personally would prefer if I could switch profiles without having to exit/restart opencode... not immediately certain what the best UI for that would be, I'll have to think about that.
I'd enjoy this feature, but personally would prefer if I could switch profiles without having to exit/restart opencode... not immediately certain what the best UI for that would be, I'll have to think about that.
yes exactly, this is the improvement over the suggestion of using an opencode.json per directory
I've analyzed the issue with Claude and designed an approach for this feature. Requesting feedback before implementation.
SUMMARY.md:
# Summary: Multiple Auth Profiles per Provider
## Issue Overview
**Issue**: [#5391](https://github.com/sst/opencode/issues/5391) - Multiple auth profiles per provider
**Reopening of**: [#893](https://github.com/sst/opencode/issues/893)
**Problem**: Users who have multiple API keys for the same provider (e.g., two OpenRouter keys for separate projects with separate cost tracking) cannot currently manage them. When running `opencode auth login` multiple times for the same provider, only the latest key is stored, overwriting the previous one.
## Current Architecture
### Auth Storage (`packages/opencode/src/auth/index.ts`)
- Auth credentials are stored in `~/.local/share/opencode/auth.json` (XDG data directory)
- Storage is keyed **only by provider ID**: `Record<string, Auth.Info>`
- Auth types: `oauth`, `api` (API key), `wellknown`
- When `Auth.set(providerID, info)` is called, it overwrites any existing auth for that provider
### Provider System (`packages/opencode/src/provider/provider.ts`)
- Providers are loaded from `models.dev` database and merged with config
- Provider lookup: `providers[providerID]` - single provider per ID
- Model selection uses `provider/model` format (e.g., `anthropic/claude-sonnet-4`)
- SDK instances cached by hash of `{npm, options}` including `apiKey`
### Config System (`packages/opencode/src/config/config.ts`)
- Supports `provider` config with custom options including `apiKey`
- Providers can be defined in `opencode.json` with custom settings
- No concept of "profiles" exists in current config schema
### CLI Auth Commands (`packages/opencode/src/cli/cmd/auth.ts`)
- `auth login`: Select provider, optionally use plugin auth, or enter API key
- `auth logout`: Select provider to remove
- `auth list`: Shows all stored credentials by provider ID
- No support for specifying a profile name during auth operations
### CLI Run Command (`packages/opencode/src/cli/cmd/run.ts`)
- Accepts `--model` flag in format `provider/model`
- No `--profile` flag exists
## Key Files
| File | Purpose |
| -------------------------------------------- | ----------------------------------- |
| `packages/opencode/src/auth/index.ts` | Auth storage (get/set/all/remove) |
| `packages/opencode/src/provider/provider.ts` | Provider loading, SDK creation |
| `packages/opencode/src/provider/auth.ts` | Provider auth methods (OAuth flows) |
| `packages/opencode/src/config/config.ts` | Config schema and loading |
| `packages/opencode/src/cli/cmd/auth.ts` | CLI auth commands |
| `packages/opencode/src/cli/cmd/run.ts` | CLI run command |
| `packages/opencode/src/flag/flag.ts` | Environment variable flags |
## User Requests
1. Multiple profiles per provider stored globally
2. Profile switching in provider picker (TUI)
3. Command-line specification: `--provider X --profile Y`
4. Ability to maintain profiles globally, not just per-directory config
PLAN.md:
# Implementation Plan: Multiple Auth Profiles per Provider
## Overview
Add support for multiple authentication profiles per provider, allowing users to maintain separate API keys for the same provider (e.g., different OpenRouter keys for different cost centers).
---
## Phase 1: Core Auth Module Changes
### File: `packages/opencode/src/auth/index.ts`
**1.1 Add profile utilities**
```typescript
// Parse composite key into provider and profile
function parseKey(key: string): { providerID: string; profile?: string }
// Build composite key from provider and profile
function buildKey(providerID: string, profile?: string): string
// Validate profile name (alphanumeric, hyphen, underscore)
function validateProfileName(name: string): boolean
```
**1.2 Update `Auth.get()` signature**
```typescript
// Before
export async function get(providerID: string): Promise<Info | undefined>
// After
export async function get(providerID: string, profile?: string): Promise<Info | undefined>
```
**1.3 Update `Auth.set()` signature**
```typescript
// Before
export async function set(key: string, info: Info): Promise<void>
// After
export async function set(providerID: string, info: Info, profile?: string): Promise<void>
```
**1.4 Update `Auth.remove()` signature**
```typescript
// Before
export async function remove(key: string): Promise<void>
// After
export async function remove(providerID: string, profile?: string): Promise<void>
```
**1.5 Add new functions**
```typescript
// List all profiles for a provider
export async function profiles(providerID: string): Promise<Array<{ profile?: string; info: Info }>>
// Check if default profile exists for provider
export async function hasDefault(providerID: string): Promise<boolean>
// Swap default with a named profile
export async function setDefault(providerID: string, profile: string): Promise<void>
```
---
## Phase 2: Provider Module Changes
### File: `packages/opencode/src/provider/provider.ts`
**2.1 Update `parseModel()` function**
```typescript
// Before: "openrouter/claude-sonnet-4" -> { providerID, modelID }
// After: "openrouter:work/claude-sonnet-4" -> { providerID, profile?, modelID }
export function parseModel(model: string): {
providerID: string
profile?: string
modelID: string
}
```
**2.2 Update auth loading in `state()`**
Around line 624, where `Auth.all()` is iterated:
```typescript
// Current: iterates provider IDs directly
// New: parse composite keys, group by provider, resolve which profile to use
```
**2.3 Update `getSDK()` to accept profile**
The SDK cache key already includes `apiKey` in options hash, so different profiles will naturally get different SDK instances. Just need to ensure the correct key is passed through.
---
## Phase 3: Config Schema Changes
### File: `packages/opencode/src/config/config.ts`
**3.1 Extend Provider schema**
```typescript
export const Provider = ModelsDev.Provider.partial().extend({
// ... existing fields
profile: z.string().optional().describe("Default auth profile to use for this provider"),
})
```
---
## Phase 4: CLI Auth Commands
### File: `packages/opencode/src/cli/cmd/auth.ts`
**4.1 Update `AuthLoginCommand`**
- After provider selection, check if default profile exists
- If exists, prompt for profile name (with validation)
- If not exists, create as default (no profile suffix)
- Update `Auth.set()` calls to include profile parameter
**4.2 Update `AuthListCommand`**
- Group credentials by provider
- Show profile names (or "default" for no-suffix keys)
- Format:
```
OpenRouter
default api
work api
Anthropic
default oauth
```
**4.3 Update `AuthLogoutCommand`**
- Show all credentials with profile names in selection
- Update `Auth.remove()` call to use parsed provider/profile
**4.4 Add `AuthSetDefaultCommand`**
- Select provider (only show providers with multiple profiles)
- Select profile to promote to default
- Call `Auth.setDefault(providerID, profile)`
---
## Phase 5: Provider Auth Module
### File: `packages/opencode/src/provider/auth.ts`
**5.1 Update `api()` function**
Add profile parameter:
```typescript
export const api = fn(
z.object({
providerID: z.string(),
key: z.string(),
profile: z.string().optional(),
}),
async (input) => {
await Auth.set(input.providerID, { type: "api", key: input.key }, input.profile)
},
)
```
**5.2 Update OAuth callback handling**
Ensure profile is passed through OAuth flows when saving credentials.
---
## Phase 6: CLI Run Command
### File: `packages/opencode/src/cli/cmd/run.ts`
**6.1 Update model parsing**
The `--model` flag already goes through `Provider.parseModel()`, so after Phase 2.1, profile support will work automatically.
---
## Phase 7: TUI Changes (if applicable)
### Files in `packages/opencode/src/cli/cmd/tui/`
**7.1 Provider/Model picker**
- Group providers with multiple profiles
- Show nested selection: Provider → Profile → Model
- Or flat list with "Provider (profile)" format
---
## Phase 8: Error Handling
### New error types needed:
```typescript
// In auth/index.ts
export const ProfileNotFoundError = NamedError.create(
"AuthProfileNotFoundError",
z.object({
providerID: z.string(),
profile: z.string(),
}),
)
export const InvalidProfileNameError = NamedError.create(
"AuthInvalidProfileNameError",
z.object({
profile: z.string(),
}),
)
```
---
## Implementation Notes
1. **Composite key format**: Keys in `auth.json` use `providerID` for default, `providerID:profileName` for named profiles. The `:` character is the delimiter.
2. **Profile name validation**: `/^[a-zA-Z0-9_-]+$/` - alphanumeric plus hyphen and underscore.
3. **Backward compatibility**: All existing auth.json files work unchanged. Existing code calling `Auth.get(providerID)` without profile gets the default.
4. **Profile resolution order**:
- Explicit in model string (`provider:profile/model`)
- Config setting (`provider.X.profile`)
- Default profile (no suffix in auth.json)
5. **Non-existent profile**: Error immediately, no fallback to default.
---
## Testing Considerations
1. Migration: existing auth.json files should work without changes
2. Multiple profiles for same provider stored and retrieved correctly
3. Profile in model string parsed correctly
4. Config profile override works
5. Error on non-existent profile
6. `set-default` swap works correctly
7. CLI commands handle profiles in all flows
---
## Order of Implementation
1. Phase 1 (Auth module) - foundation
2. Phase 3 (Config schema) - small change, needed for resolution
3. Phase 2 (Provider module) - uses Auth + Config
4. Phase 4 (CLI auth commands) - user-facing
5. Phase 5 (Provider auth) - OAuth flows
6. Phase 6 (CLI run) - should mostly work after Phase 2
7. Phase 7 (TUI) - if needed
8. Phase 8 (Errors) - can be added incrementally
Looking for feedback on this approach before starting implementation. Any concerns or alternative suggestions?
Reacted by Jan-Henrik Damaschke, Tanmay Yadav, Ivan Uhalin, Petru Hincu, biglongx and LoopControl
For a workaround in the meantime for the same provider (I have two subscriptions for Anthropic). I use a different directory for the auth values, which means no changes are needed to opencode.json:
alias opencode-work='XDG_DATA_HOME=~/.local/share/opencode-work opencode'
Still not perfect, but at least I don't have to re-auth just to switch to the same provider.
Reacted by Petru Hincu, Ori Schwartz, Glen Kusuma, LoopControl, Leonardo Amaral and Radoslaw
Yes, I just want to switch between accounts/workspaces. The "per provider" part doesn't matter.
e.g. I want to be able to switch between personal opencode zen, work opencode zen, personal claude max, or work claude max in the same picker
Reacted by Taejung Heo, Filiberto, Nicholas Jackson, Futsy, Jan-Henrik Damaschke, Felix, JD Welch, Luciano Vitti, Michael Hanson, Eugene Korekin and 19 more
Then in auth.json, you can use the alias for the aliased provider and fall back to the regular provider name if there is no alias? So basically, an aliased provider would act like a locally defined completely separate provider that is referenced everywhere by its alias, and in the config, we describe what type it is?
Reacted by Raymel, Glen Kusuma, Anton Ukhanev and hzmi
I vibe-coded (with opencode of course) a simple shell script for switching between different auth.json. Technically not multiple profiles per provider but hopefully still useful for switching between personal / work accounts.
The proposed PR looks very promising, but please allow to set a default profile in local opencode.jsonc file. I work in a software agency and a lot of customers requires for us to use their copilot licenses. I'm in a position where i need to juggle 4 licenses between the repositories. It would be awesome to be able to just bind profile to a repository and don't think about that
Very cool plan, but for the time being this handwritten code should do the job:
#!/usr/bin/env node
constfs=require('node:fs')constos=require('os')constauthFilePath='~/.local/share/opencode/auth.json'constgetAuthConfigFile=(filePath)=>{consthomeDir=os.homedir()returnfilePath.replace(/^~/,`${homeDir}`)}constbackup=(filePath,overwrite)=>{overwrite=Boolean(overwrite)constexists=fs.existsSync(`${filePath}.bkp`)if(!exists){fs.copyFileSync(filePath,`${filePath}.bkp`)console.log(`copied to ${filePath}.bkp`)return}if(overwrite){fs.copyFileSync(filePath,`${filePath}.bkp`)console.log(`overwridden to ${filePath}.bkp`)return}console.log('already backed up')}constswitchConfig=(config,present,neww)=>{consttmp={...config[present]}config[present]={...config[neww]}config[neww]=tmpreturnconfig}construn=(configPairs)=>{constresolvedFilePath=getAuthConfigFile(authFilePath)constconfigStr=fs.readFileSync(resolvedFilePath)constconfig=JSON.parse(configStr)constnewConfig=configPairs.reduce((acc,pair)=>{returnswitchConfig(config,pair[0],pair[1])},config)console.log("backing up present config")backup(resolvedFilePath)console.log("switching config")fs.writeFileSync(resolvedFilePath,JSON.stringify(newConfig,null,2))}letconfigPairs=[['opencode','opencode2'],['opencode-go','opencode2-go']]run(configPairs)
Save to ~/.local/bin/switchopencode given ~/.local/bin is in $PATH
First thing to improve could be to accept the pair as cli args. something like:
constparseArgs=(argv)=>{constargs=argv.slice(2)if(args.length===0)returnnullif(args.length%2!==0){console.error('error: args must be key pairs, blablabla')process.exit(1)}returnargs.reduce((pairs,arg,i)=>{if(i%2===0)pairs.push([arg,args[i+1]])returnpairs},[])}// then in run():construn=(fallbackPairs)=>{constconfigPairs=parseArgs(process.argv)??fallbackPairs// ... rest unchanged}
I guess what can be done, is if opencode uses some form of $XDG_SESSION per session as override for their opencode config, then a command hook can be provided to switch it up.
To rotate group, one could use another file to save the current state and simply rotate:
conststateFilePath='~/.local/share/opencode/rotate-state.json'constrotationGroups=[['opencode','opencode2','opencode3','opencode4'],['opencode-go','opencode2-go','opencode3-go','opencode4-go'],]constrotateGroup=(config,group,currentIndex)=>{constlen=group.lengthconstnextIndex=(currentIndex+1)%lenconstincomingKey=group[nextIndex]constactiveKey=group[0]constoutgoingKey=group[currentIndex]constnext={ ...config}next[outgoingKey]={ ...config[activeKey]}next[activeKey]={ ...config[incomingKey]}returnnext}for(constgroupofrotationGroups){constactiveKey=group[0]// get current index// calcualte next index// save new state}// blablabla
This is a native feature of v2, tho it still needs some polish: opencode.ai/v2/docs
@rekram1-node Thank you so much. There is a follow-up issue to set account per workspace: #51152 . I've even done some spec proposal, could you take a look at that if you have the time?
Activity
ariane-emory commentedon Dec 12, 2025
I'd enjoy this feature, but personally would prefer if I could switch profiles without having to exit/restart opencode... not immediately certain what the best UI for that would be, I'll have to think about that.
charles-cooper commentedon Dec 13, 2025
yes exactly, this is the improvement over the suggestion of using an opencode.json per directory
rekram1-node commentedon Dec 13, 2025
You can switch profiles without exiting using /connect but you can only have 1 profile at a time.
So yes you can switch between already @ariane-emory but ofc not as nicely as what this feature is suggesting
charles-cooper commentedon Dec 13, 2025
i think what's needed to implement this is basically a picker in the UI and an option to set provider/profile for session on the opencode server
charles-cooper commentedon Dec 13, 2025
Proposed Implementation Plan
I've analyzed the issue with Claude and designed an approach for this feature. Requesting feedback before implementation.
SUMMARY.md:
PLAN.md:
Looking for feedback on this approach before starting implementation. Any concerns or alternative suggestions?
grenaad commentedon Dec 13, 2025
For a workaround in the meantime for the same provider (I have two subscriptions for Anthropic). I use a different directory for the auth values, which means no changes are needed to
opencode.json:Still not perfect, but at least I don't have to re-auth just to switch to the same provider.
ryanwinchester commentedon Jan 10, 2026
Yes, I just want to switch between accounts/workspaces. The "per provider" part doesn't matter.
e.g. I want to be able to switch between personal opencode zen, work opencode zen, personal claude max, or work claude max in the same picker
lukastaegert commentedon Mar 6, 2026
What if you allow an option to add aliased versions of a provider? E.g. in your opencode.json
{ "$schema": "https://opencode.ai/config.json", "enabled_providers": ["github-copilot", "github-copilot-work"], "provider_alias": { "github-copilot-work": "github-copilot" } }Then in auth.json, you can use the alias for the aliased provider and fall back to the regular provider name if there is no alias? So basically, an aliased provider would act like a locally defined completely separate provider that is referenced everywhere by its alias, and in the config, we describe what type it is?
aftercares0 commentedon Mar 8, 2026
would love if i could use multiple copilot pro accounts
yeoweizheng commentedon Mar 25, 2026
I vibe-coded (with opencode of course) a simple shell script for switching between different
auth.json. Technically not multiple profiles per provider but hopefully still useful for switching between personal / work accounts.You can get it here - https://gist.github.com/yeoweizheng/49c48fc7ff7e8c0f5165ca290e2162d4
Hazmi35 commentedon Jun 18, 2026
Any news on this?
jakoss commentedon Jul 17, 2026
The proposed PR looks very promising, but please allow to set a default profile in local opencode.jsonc file. I work in a software agency and a lot of customers requires for us to use their copilot licenses. I'm in a position where i need to juggle 4 licenses between the repositories. It would be awesome to be able to just bind profile to a repository and don't think about that
ikouchiha47 commentedon Sep 14, 2026
Very cool plan, but for the time being this handwritten code should do the job:
Save to
~/.local/bin/switchopencodegiven~/.local/binis in$PATHFirst thing to improve could be to accept the pair as cli args. something like:
I guess what can be done, is if opencode uses some form of
$XDG_SESSIONper session as override for their opencode config, then a command hook can be provided to switch it up.To rotate group, one could use another file to save the current state and simply rotate:
rekram1-node commentedon Oct 1, 2026
This is a native feature of v2, tho it still needs some polish: https://opencode.ai/v2/docs
jakoss commentedon Oct 1, 2026
@rekram1-node Thank you so much. There is a follow-up issue to set account per workspace: #51152 . I've even done some spec proposal, could you take a look at that if you have the time?