Skip to content

[FEATURE]: multiple auth profiles per provider #5391

Closed

Description

@charles-cooper

Feature hasn't been suggested before.

  • 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

Activity

  1. added
    discussionUsed for feature requests, proposals, ideas, etc. Open discussion
    on Dec 11, 2025
  2. ariane-emory commented on Dec 12, 2025

    @ariane-emory
    Contributor

    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.

  3. charles-cooper commented on Dec 13, 2025

    @charles-cooper
    ContributorAuthor

    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

  4. rekram1-node commented on Dec 13, 2025

    @rekram1-node
    Collaborator

    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

  5. charles-cooper commented on Dec 13, 2025

    @charles-cooper
    ContributorAuthor

    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

  6. charles-cooper commented on Dec 13, 2025

    @charles-cooper
    ContributorAuthor

    Proposed Implementation Plan

    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?

  7. grenaad commented on Dec 13, 2025

    @grenaad

    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.

  8. ryanwinchester commented on Jan 10, 2026

    @ryanwinchester
    Contributor
    1. Profile switching in provider picker (TUI)

    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

  9. lukastaegert commented on Mar 6, 2026

    @lukastaegert

    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?

  10. aftercares0 commented on Mar 8, 2026

    @aftercares0

    would love if i could use multiple copilot pro accounts

  11. yeoweizheng commented on Mar 25, 2026

    @yeoweizheng

    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

  12. removed
    discussionUsed for feature requests, proposals, ideas, etc. Open discussion
    on May 3, 2026
  13. Hazmi35 commented on Jun 18, 2026

    @Hazmi35

    Any news on this?

  14. jakoss commented on Jul 17, 2026

    @jakoss

    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

  15. ikouchiha47 commented on Sep 14, 2026

    @ikouchiha47

    Very cool plan, but for the time being this handwritten code should do the job:

    #!/usr/bin/env node
    
    const fs = require('node:fs')
    const os = require('os')
    
    const authFilePath = '~/.local/share/opencode/auth.json'
    
    const getAuthConfigFile = (filePath) => {
    	const homeDir = os.homedir()
    	return filePath.replace(/^~/, `${homeDir}`)
    }
    
    const backup = (filePath, overwrite) => {
    	overwrite = Boolean(overwrite)
    
    	const exists = 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')
    }
    
    const switchConfig = (config, present, neww) => {
    	const tmp = {...config[present]}
    
    	config[present] = {...config[neww]}
    	config[neww] = tmp
    
    	return config
    }
    
    const run = (configPairs) => {
    	const resolvedFilePath = getAuthConfigFile(authFilePath)
    	const configStr = fs.readFileSync(resolvedFilePath)
    	const config = JSON.parse(configStr)
    
    	const newConfig = configPairs.reduce((acc, pair) => {
    		return switchConfig(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))
    }
    
    let configPairs = [
    	['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:

    const parseArgs = (argv) => {
      const args = argv.slice(2)
    
      if (args.length === 0) return null
    
      if (args.length % 2 !== 0) {
        console.error('error: args must be key pairs, blablabla')
        process.exit(1)
      }
    
      return args.reduce((pairs, arg, i) => {
        if (i % 2 === 0) pairs.push([arg, args[i + 1]])
        return pairs
      }, [])
    }
    
    // then in run():
    const run = (fallbackPairs) => {
      const configPairs = 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:

    const stateFilePath = '~/.local/share/opencode/rotate-state.json'
    
    const rotationGroups = [
      ['opencode',    'opencode2',    'opencode3',    'opencode4'],
      ['opencode-go', 'opencode2-go', 'opencode3-go', 'opencode4-go'],
    ]
    
    const rotateGroup = (config, group, currentIndex) => {
      const len = group.length
      const nextIndex = (currentIndex + 1) % len
    
      const incomingKey = group[nextIndex]
      const activeKey   = group[0]
      const outgoingKey = group[currentIndex]
    
      const next = { ...config }
    
      next[outgoingKey] = { ...config[activeKey] }
      next[activeKey]   = { ...config[incomingKey] }
    
      return next
    }
    
    for (const group of rotationGroups) {
        const activeKey    = group[0]
        // get current index
        // calcualte next index
        // save new state
      }
    
    // blablabla
  16. rekram1-node commented on Oct 1, 2026

    @rekram1-node
    Collaborator

    This is a native feature of v2, tho it still needs some polish: https://opencode.ai/v2/docs

  17. jakoss commented on Oct 1, 2026

    @jakoss

    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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions