RESOURCES

Go SDK

SDK to integrate Phase in server-side applications running Go.

Prerequisites


Note

Starting from version 2.0.0, the Phase Go SDK is a pure Go implementation and no longer requires libsodium or CGO.

Upgrading from v1.x: Update your module dependency and imports from github.com/phasehq/golang-sdk to github.com/phasehq/golang-sdk/v2:

go get github.com/phasehq/golang-sdk/v2/phase

Then update your imports:

// 1.x.x
import "github.com/phasehq/golang-sdk/phase"

// 2.x.x
import "github.com/phasehq/golang-sdk/v2/phase"

Users on v1.x are unaffected and can continue using github.com/phasehq/golang-sdk without changes.

Install the SDK

Install the SDK using go get.

Install
go get github.com/phasehq/golang-sdk/v2/phase

Import the SDK

Import the SDK in your Go files to start using its features.

import "github.com/phasehq/golang-sdk/v2/phase"

Initialize the SDK

Before interacting with the Phase service, initialize the SDK with your service token and the host information.

Parameters:

  • token type string: Your Phase Service Token (pss_service:v1:... or pss_service:v2:...) or User Token (pss_user:v1:...)
  • host type string: The URL of the Phase Console instance. Defaults to https://console.phase.dev if empty.
  • debug type bool: Setting to true will result in a higher level of log verbosity useful when debugging
package main

import (
    "log"
    "github.com/phasehq/golang-sdk/v2/phase"
)

func main() {
    token := "pss_service:v1:....."
    host := "https://console.phase.dev" // Adjust this for a self-hosted instance of Phase
    debug := false // For logging verbosity, disable in production

    p, err := phase.New(token, host, debug)
    if err != nil {
        log.Fatalf("Failed to initialize Phase client: %v", err)
    }

    _ = p // Use the client to manage secrets: p.Get(...), p.Create(...), etc.
}

Usage

Note

All operations accept either AppID or AppName to identify your application. AppID is recommended as it avoids ambiguity when multiple applications share the same name.

You can get the AppID by going to your application settings in the Phase Console, hovering over UUID under the App section and clicking the Copy button:

hello world

Secret Types

Phase supports three secret types, available as constants:

ConstantValueDescription
phase.SecretTypeSecret"secret"Default — standard encrypted secret
phase.SecretTypeSealed"sealed"Write-only in the Phase Console — value is hidden in the Console UI after creation (still returned by the SDK / API)
phase.SecretTypeConfig"config"Non-sensitive configuration value

Use phase.ValidateSecretType(t) to validate a type string — returns nil for valid types (including empty string, which defaults to "secret").

Secret Lifecycle

The Lifecycle field on SecretResult indicates how a secret's value is managed, available as constants:

ConstantValueDescription
phase.SecretLifecycleStatic"static"Default — the value is managed by the user
phase.SecretLifecycleRotating"rotating"The value is automatically rotated by the Phase rotation engine

Secrets with a rotating lifecycle are managed by the Phase rotation engine and cannot be updated or deleted through the SDK — manage them via the rotating-secret controls in the Console. See Rotating Secrets. Dynamic secrets (IsDynamic: true) are leased credentials and carry no lifecycle value.

Creating Secrets

Define key-value pairs and specify the Environment, App name or ID, and path (optional) to create new Secrets.

Options:

type CreateOptions struct {
    KeyValuePairs []phase.KeyValuePair
    EnvName       string
    AppID         string
    AppName       string // Alternative to AppID
    Path          string
    OverrideValue string
    Type          string // "secret" (default), "sealed", or "config" — applied to all pairs when KeyValuePair.Type is empty
}

type KeyValuePair struct {
    Key   string
    Value string
    Type  string // Per-pair type override; takes precedence over CreateOptions.Type
}
err := p.Create(phase.CreateOptions{
    KeyValuePairs: []phase.KeyValuePair{
        {Key: "API_KEY", Value: "api_secret"},
        {Key: "DB_HOST", Value: "localhost:5432"},
    },
    EnvName: "Production",
    AppID:   "app-id-here",  // Or use AppName: "MyApp"
    Path:    "/api/keys",    // Optional, default path: /
})
if err != nil {
    log.Fatalf("Failed to create secret: %v", err)
}

// Create a sealed secret (value hidden in the Console after creation)
err = p.Create(phase.CreateOptions{
    KeyValuePairs: []phase.KeyValuePair{
        {Key: "STRIPE_KEY", Value: "sk_live_..."},
    },
    EnvName: "Production",
    AppID:   "app-id-here",
    Type:    phase.SecretTypeSealed,
})

// Create config values (non-sensitive)
err = p.Create(phase.CreateOptions{
    KeyValuePairs: []phase.KeyValuePair{
        {Key: "APP_PORT", Value: "8080"},
        {Key: "LOG_LEVEL", Value: "info"},
    },
    EnvName: "Production",
    AppID:   "app-id-here",
    Type:    phase.SecretTypeConfig,
})

Getting Secrets

Provide the Environment name, App name or ID, and optionally filter by specific keys, tags, and path.

Options:

type GetOptions struct {
    EnvName                        string
    AppID                          string
    AppName                        string   // Alternative to AppID
    Keys                           []string // Optional: filter by specific key names
    Tag                            string   // Optional: filter by tag
    Path                           string   // Optional: filter by path; empty returns secrets from all paths
    Dynamic                        bool     // Optional: include dynamic secrets
    Lease                          bool     // Optional: generate leases for dynamic secrets
    LeaseTTL                       *int     // Optional: lease TTL in seconds
    Raw                            bool     // Optional: skip ${REF} reference resolution
    FailOnReferenceResolutionError bool     // Optional: error if any ${REF} fails to resolve (default: leave it unresolved)
}

Each secret is returned as a SecretResult:

type SecretResult struct {
    Key          string
    Value        string
    Comment      string
    Path         string
    Type         string       // "secret", "sealed", or "config"
    Lifecycle    string       // "static" (default) or "rotating" — see Secret Lifecycle
    Application  string
    Environment  string
    Tags         []string
    Overridden   bool         // true if a personal override is active
    IsDynamic    bool         // true for dynamic secrets
    DynamicGroup string       // provider group label for dynamic secrets
}

Get all secrets

secrets, err := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here", // Or use AppName: "MyApp"
})
if err != nil {
    log.Fatalf("Failed to get secrets: %v", err)
}
for _, s := range secrets {
    log.Printf("%s=%s", s.Key, s.Value)
}

Get specific keys

secrets, err := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
    Keys:    []string{"API_KEY", "DB_HOST"},
})

Filter by tag and path

secrets, err := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
    Tag:     "backend",
    Path:    "/api/config",
})

Include dynamic secrets with leases

secrets, err := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
    Dynamic: true,
    Lease:   true,
})

Updating a Secret

Provide the new value along with the environment name, application name or ID, and key to update an existing secret.

Options:

type UpdateOptions struct {
    EnvName         string
    AppID           string
    AppName         string // Alternative to AppID
    Key             string
    Value           string
    SourcePath      string // Path where the secret currently lives
    DestinationPath string // Optional: move the secret to a new path
    Override        bool   // Set a personal override
    ToggleOverride  bool   // Toggle personal override on/off
    Type            string // "secret", "sealed", or "config" — leave empty to keep existing type
}
err := p.Update(phase.UpdateOptions{
    EnvName: "Production",
    AppID:   "app-id-here", // Or use AppName: "MyApp"
    Key:     "API_KEY",
    Value:   "my_updated_api_secret",
})
if err != nil {
    log.Fatalf("Failed to update secret: %v", err)
}

Deleting Secrets

Specify the environment name, application name or ID, keys to delete, and optionally the path.

Options:

type DeleteOptions struct {
    EnvName      string
    AppID        string
    AppName      string // Alternative to AppID
    KeysToDelete []string
    Path         string
}
keysNotFound, err := p.Delete(phase.DeleteOptions{
    EnvName:      "Production",
    AppID:        "app-id-here", // Or use AppName: "MyApp"
    KeysToDelete: []string{"API_KEY", "OLD_SECRET"},
    Path:         "/api/keys", // Optional, default path: /
})
if err != nil {
    log.Fatalf("Failed to delete secret: %v", err)
}
if len(keysNotFound) > 0 {
    log.Printf("Keys not found: %v", keysNotFound)
}

Secret References

Get() automatically resolves ${REF} syntax in secret values before returning results. This includes same-environment (${KEY}), cross-environment (${staging.SECRET_KEY}), cross-app (${backend_api::production.API_KEY}), and path-scoped (${/backend/config/DB_HOST}) references.

// References are resolved automatically — no extra steps needed
secrets, _ := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
})

for _, s := range secrets {
    // s.Value already has all ${REF} references resolved
    fmt.Printf("%s=%s\n", s.Key, s.Value)
}

To get raw, unresolved values (e.g. for display or inspection), set Raw: true:

secrets, _ := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
    Raw:     true,
})

for _, s := range secrets {
    // s.Value contains the original ${REF} syntax, not the resolved value
    fmt.Printf("%s=%s\n", s.Key, s.Value)
}

Fail on unresolved references

By default, if a reference can't be resolved — the referenced secret doesn't exist, was mistyped, or the lookup fails due to a network, rate-limit, or permissions error — the original ${REF} text is left in place and resolution continues. This is convenient for inspection tooling, but risky for applications that need fully-resolved configuration.

Set FailOnReferenceResolutionError: true to fail fast instead. Get() then returns an error the moment any reference can't be resolved, so you never load an unresolved ${...} reference string into your app's config:

secrets, err := p.Get(phase.GetOptions{
    EnvName:                        "Production",
    AppID:                          "app-id-here",
    FailOnReferenceResolutionError: true,
})
if err != nil {
    // e.g. a referenced secret was deleted, mistyped, or the lookup was rate-limited
    log.Fatalf("Failed to resolve secret references: %v", err)
}
Note

FailOnReferenceResolutionError has no effect when Raw: true, since raw mode skips reference resolution entirely.

Overrides

Create or update a personal override for a secret:

// Create a secret with an override value
err := p.Create(phase.CreateOptions{
    KeyValuePairs: []phase.KeyValuePair{
        {Key: "API_URL", Value: "https://api.example.com"},
    },
    EnvName:       "Development",
    AppID:         "app-id-here",
    OverrideValue: "http://localhost:3000",
})
if err != nil {
    log.Fatalf("Failed to create secret: %v", err)
}

// Update override for existing secret
err = p.Update(phase.UpdateOptions{
    EnvName:  "Development",
    AppID:    "app-id-here",
    Key:      "API_URL",
    Value:    "http://localhost:4000",
    Override: true,
})
if err != nil {
    log.Fatalf("Failed to update override: %v", err)
}

// Toggle override on/off
err = p.Update(phase.UpdateOptions{
    EnvName:        "Development",
    AppID:          "app-id-here",
    Key:            "API_URL",
    ToggleOverride: true,
})
if err != nil {
    log.Fatalf("Failed to toggle override: %v", err)
}