Contributing to the OpenFGA Go SDK

April 11, 2026 · View on GitHub

Thank you for considering contributing to the OpenFGA Go SDK! This guide will help you get set up and submit effective contributions.

Table of Contents

Code of Conduct

By participating and contributing to this project, you are expected to uphold our Code of Conduct.

Prerequisites

  • Go: Latest two releases supported per Go's release policy. We target (latest-1).0 in go.mod and use the latest as toolchain.
  • golangci-lint: Used for linting (install)
  • gosec and govulncheck: Used for security scanning
  • make: For running common commands (make check, make test, etc.)

Getting Started

  1. Fork github.com/openfga/go-sdk
  2. Clone your fork
  3. Create a branch: git checkout -b feat/your-feature
  4. Make your changes
  5. Run make check to verify everything passes
  6. Push and open a PR

Useful commands

make fmt        # Format code with gofmt
make lint       # Format + go vet + golangci-lint
make test       # Run all tests with race detection and coverage
make security   # Run gosec and govulncheck
make check      # All of the above

Run a single package's tests:

go test -race -v ./client/...

Project Structure

This SDK has a dual-layer architecture:

  • Low-level layer (root package): APIClient, APIExecutor, OpenFgaApi generated models, error types
  • High-level layer (client/ package): OpenFgaClient with fluent API, batch splitting, credential injection, telemetry
PackagePurpose
client/High-level SDK client (Check, BatchCheck, Expand, etc.)
credentials/Authentication (none, API token, client credentials)
oauth2/OAuth2 token flows and Bearer token transport
telemetry/OpenTelemetry metric recording
internal/constants/SDK constants (batch limits, retry defaults)
internal/utils/retryutils/Exponential backoff, Retry-After header parsing

Generated vs Hand-Written Code

Parts of this SDK are auto-generated from the sdk-generator repository using OpenAPI Generator.

Generated files (will be wiped on regeneration)

  • All model_*.go files (data model files)
  • api_open_fga.go (deprecated in favor of api_executor.go)
  • streaming.go
  • internal/constants/constants.go — these constants are generated from sdk-generator and are consistent across all OpenFGA SDKs
  • docs/**

You can identify generated files by the header comment: NOTE: This file was auto generated by OpenAPI Generator ... DO NOT EDIT.

The full list is tracked in .openapi-generator/FILES.

If you need to change a generated file

You may modify generated files in this repo, but changes must also be submitted to sdk-generator to persist:

  1. Submit a PR to openfga/sdk-generator with the template/spec changes
  2. Open your PR in this repo and link the sdk-generator PR in the description
  3. Without a linked sdk-generator PR, your changes will be overwritten on the next regeneration

This workflow ensures fixes propagate to all OpenFGA SDKs (Go, JS, Java, .NET, Python).

Hand-written files (safe to edit directly)

  • api_client.go, api_executor.go, configuration.go, errors.go, utils.go, response.go
  • client/**, credentials/**, oauth2/**, telemetry/**
  • internal/utils/**
  • All *_test.go files

Cross-SDK Consistency

OpenFGA maintains SDKs in multiple languages: Go, JS/TS, Java, .NET, and Python. These SDKs should behave consistently:

  • Before implementing a feature or fix, check how the other SDKs handle it. Use them as reference to ensure your implementation matches the expected behavior and interface. When submitting your PR, note whether the change applies across SDKs.
  • Public interfaces should match as closely as each language allows — same method names, same parameters, same defaults, same error behavior.
  • Constants (batch limits, retry defaults, telemetry metric names, etc.) are generated from sdk-generator and must stay consistent across all SDKs.
  • Behavioral contracts must be identical: which status codes are retried, how Retry-After headers are parsed, how token refresh works, how streaming channels behave.

If you discover a discrepancy between SDKs, please open an issue in openfga/sdk-generator so it can be tracked and resolved across all SDKs.

Development Workflow

Code style

  • Format with gofmt (not goimports) — CI checks this
  • Lint with golangci-lint run using the repo's .golangci.yaml
  • Use openfga.ToPtr[T]() for optional fields — the older PtrString(), PtrBool(), PtrInt() helpers are deprecated
  • Propagate context.Context throughout — never drop or ignore contexts
  • Use github.com/sourcegraph/conc for concurrent batch operations

Fluent API pattern

All client methods follow this pattern:

response, err := fgaClient.Check(ctx).
    Body(client.ClientCheckRequest{
        User:     "user:alice",
        Relation: "viewer",
        Object:   "document:budget",
    }).
    Options(client.ClientCheckOptions{
        AuthorizationModelId: openfga.ToPtr("01H..."),
    }).
    Execute()

Maintain this pattern when adding new methods.

Writing Tests

Every PR must include or update tests that exercise the changed code.

Test conventions

  • Use t.Parallel() at both the top-level test function and inside each t.Run() subtest
  • Use table-driven tests with t.Run() for multiple cases
  • Use testify for assertions: require for fatal checks, assert for non-fatal
  • Mock HTTP calls with github.com/jarcoal/httpmock — never make real network calls
  • Use httptest.NewServer for streaming/NDJSON response tests
  • Never use time.Sleep — use channels, contexts, or test helpers
  • All tests must be race-safe (CI runs with -race)

Example

func TestMyFeature(t *testing.T) {
    t.Parallel()
    tests := []struct {
        name     string
        input    string
        expected string
    }{
        {"valid input", "foo", "bar"},
        {"empty input", "", ""},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            result := myFunction(tt.input)
            assert.Equal(t, tt.expected, result)
        })
    }
}

PR Requirements

Title format

PR titles must follow Conventional Commits:

type(scope): description

Scope is optional. Examples:

  • feat: add StreamedListUsers method
  • fix(client): handle nil response in BatchCheck
  • chore(deps): bump go.opentelemetry.io/otel

Allowed types:

TypeUse for
featNew features
fixBug fixes
refactorCode changes that don't fix bugs or add features
perfPerformance improvements
choreMaintenance (use chore(docs), chore(ci), chore(test) for those areas)
revertReverting previous changes
releaseRelease PRs (e.g., release: v0.8.0)

Checklist

Before submitting your PR:

  • make check passes (fmt + lint + test + security)
  • Tests added or updated for the changed code
  • Documentation updated if the public interface or functionality changed (README, godoc comments, dedicated doc file if warranted)
  • If generated files were modified, a linked sdk-generator PR is referenced in the description
  • If telemetry metrics were added or changed, the changes are noted for the changelog

Adding New API Methods

New public methods that call the OpenFGA API must follow this checklist:

  1. Use the executor: All API methods must go through api_executor.go — no custom HTTP logic. Client methods in client/ call API methods, but API methods must all use the executor.
  2. Set OperationName in APIExecutorRequest — this becomes the fga_client_request_method telemetry attribute.
  3. Pass storeId through for metric attributes.
  4. Add to SdkClient interface in client/client.go.
  5. Add telemetry attributes in telemetry/attributes.go and telemetry/configuration.go if the method introduces a new dimension (like batch_check_size for BatchCheck).
  6. Validate inputs: StoreId and AuthorizationModelId must be valid ULIDs (see internal/utils/IsWellFormedUlidString).
  7. Wrap errors using the typed error types from errors.go — never return raw errors to users.

Use existing methods like Check, BatchCheck, or ListObjects in client/client.go as reference.

Prefer api_executor.go over api_open_fga.go — the latter is generated and deprecated.

Working with Telemetry

The SDK emits OpenTelemetry metrics for all client operations using the fga_client_* prefix.

Key rules

  • High-cardinality attributes (e.g., url_full, unique per-request IDs) must be disabled by default in DefaultTelemetryConfiguration(). Some observability providers charge heavily for high cardinality.
  • No PII in metric attributes — no user IDs, tokens, or request bodies.
  • Unit tests must use noop.NewMeterProvider() — never live OTel exporters.
  • Document changes: any additions or modifications to metrics must be called out in the changelog on release.

Adding a new metric

  1. Define the metric in telemetry/configuration.go as a MetricConfiguration
  2. Add the recording method in telemetry/metrics.go
  3. Add attribute keys in telemetry/attributes.go if needed
  4. Test with noop provider

Security Considerations

This SDK handles user credentials. When working on security-sensitive areas (credentials/, oauth2/, errors.go, api_client.go, api_executor.go):

  • Never log or include tokens, client secrets, or credentials in error messages
  • Never expose sensitive headers in string representations or debug output
  • OAuth2 token refresh must be thread-safe (OpenFgaClient is shared across goroutines)
  • Always close HTTP response bodies in all code paths, including error paths
  • Retry only on 429 and 5xx — never retry 4xx client errors (except 429)
  • Respect Retry-After and X-RateLimit-Reset headers for backoff timing
  • Do not remove token expiry jitter (300s) — it prevents thundering herd against token issuers

Getting Help