Skip to content

graphql

The graphql package provides a type-safe client for Aha.io's GraphQL API, generated by genqlient via gqlforge.

Installation

import (
    "github.com/grokify/aha-go/graphql"
    "github.com/grokify/aha-go/graphql/generated"
)

Overview

The GraphQL API provides access to functionality not available in the REST API, including:

  • Full-text search across documents
  • Creating features and ideas with validation
  • Setting custom field values on any record
  • Discovering required fields and custom field definitions

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/grokify/aha-go/graphql"
    "github.com/grokify/aha-go/graphql/generated"
)

func main() {
    // Create a genqlient-compatible client
    client := graphql.NewGenqlientClient("mycompany", "your-api-key")
    ctx := context.Background()

    // Get a feature with full type safety
    resp, err := generated.GetFeature(ctx, client, "FEAT-123")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Feature: %s - %s\n", resp.Feature.ReferenceNum, resp.Feature.Name)
}

Client

Creating a Client

// Basic client (recommended)
client := graphql.NewGenqlientClient("subdomain", "api-key")

// With custom HTTP client
httpClient := &http.Client{Timeout: 30 * time.Second}
client := graphql.NewGenqlientClientWithHTTP("subdomain", "api-key", httpClient)

Queries

All queries are type-safe and return strongly-typed responses.

GetFeature

resp, err := generated.GetFeature(ctx, client, "FEAT-123")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Name: %s\n", resp.Feature.Name)
fmt.Printf("Status: %s\n", resp.Feature.WorkflowStatus.Name)

SearchDocuments

Search across pages, features, and other document types.

resp, err := generated.SearchDocuments(ctx, client, "onboarding", []string{"Page", "Feature"})
if err != nil {
    log.Fatal(err)
}

for _, node := range resp.SearchDocuments.Nodes {
    fmt.Printf("Name: %s\n", *node.Name)
    fmt.Printf("URL: %s\n", node.Url)
    fmt.Printf("Type: %s\n", node.SearchableType)
}

Available Queries

Query Description
GetFeature Get feature by reference number
GetPage Get page by reference number
GetIdea Get idea by reference number
GetRelease Get release by reference number
GetGoal Get goal by reference number
GetEpic Get epic by reference number
GetInitiative Get initiative by reference number
GetRequirement Get requirement by reference number
GetProject Get project by ID
GetAccount Get account information
SearchDocuments Full-text search across documents
GetFeatureWithIntegrations Get feature with integration fields
GetFeatureWithLinks Get feature with record links
GetFeatureScreenDefinition Get required fields configuration
GetProjectCustomFields Get custom field definitions
GetFeatureWithCustomFields Get feature with custom field values

Mutations

Creating Features

// Create a feature in a release
resp, err := generated.CreateFeatureWithRelease(ctx, client,
    "My New Feature",           // name
    "RELEASE-ID",               // releaseId
    "<p>Description here</p>",  // description
    "tag1, tag2",               // tagList
    nil,                        // skipRequiredFieldsValidation
)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Created: %s\n", resp.CreateFeature.Feature.ReferenceNum)

// Create a feature in a project (without specifying release)
resp, err := generated.CreateFeatureWithProject(ctx, client,
    "Feature Name",
    "PROJECT-ID",
    "<p>Description</p>",
    "",
    nil,
)

// Create a feature with full assignment options
resp, err := generated.CreateFeatureWithAssignments(ctx, client,
    "Feature Name",      // name
    "RELEASE-ID",        // releaseId
    "<p>Description</p>", // description
    "tags",              // tagList
    nil,                 // skipRequiredFieldsValidation
    "INITIATIVE-ID",     // initiativeId
    "EPIC-ID",           // epicId
    "USER-ID",           // assignedToUserId
)

Creating Ideas

resp, err := generated.CreateIdea(ctx, client,
    "New Idea",     // name
    "PROJECT-ID",   // projectId
    nil,            // skipRequiredFieldsValidation
)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Created: %s\n", resp.CreateIdea.Idea.ReferenceNum)

Setting Custom Fields

Set custom field values on any record type:

resp, err := generated.SetCustomFieldValues(ctx, client,
    "FEATURE-ID",
    generated.CustomFieldableTypeEnumFeature,
    []generated.CustomFieldValueInput{
        {Key: "priority_score", Value: 85},
        {Key: "customer_segment", Value: "Enterprise"},
        {Key: "target_date", Value: "2024-06-15"},
    },
)
if err != nil {
    log.Fatal(err)
}

Supported record types for custom fields:

  • CustomFieldableTypeEnumFeature
  • CustomFieldableTypeEnumIdea
  • CustomFieldableTypeEnumEpic
  • CustomFieldableTypeEnumInitiative
  • CustomFieldableTypeEnumGoal
  • CustomFieldableTypeEnumRelease

Available Mutations

Mutation Description
CreateFeatureWithRelease Create feature in a release
CreateFeatureWithProject Create feature in a project
CreateFeatureWithAssignments Create feature with full assignments
CreateIdea Create an idea
SetCustomFieldValues Set custom fields on any record
UpdateFeatureName Update feature name
UpdateFeatureDescription Update feature description
UpdateFeatureStatus Update feature workflow status
UpdateFeatureTags Update feature tags
AssignFeatureToInitiative Assign feature to initiative
AssignFeatureToRelease Assign feature to release
AssignFeatureToEpic Assign feature to epic
AssignFeatureToUser Assign feature to user
PromoteIdeaToFeature Promote idea to feature
PromoteIdeaToEpic Promote idea to epic
CreateRecordLink Create link between records

Required Field Validation

Helper functions to discover and validate required fields before creating records.

Discover Required Fields

import "github.com/grokify/aha-go/graphql"

// Get required fields for features (pass any existing feature ID)
reqs, err := graphql.GetFeatureRequirements(ctx, client, "EXISTING-FEATURE-ID")
if err != nil {
    log.Fatal(err)
}

// Get list of required field IDs
requiredIDs := reqs.RequiredFieldIDs()
fmt.Printf("Required fields: %v\n", requiredIDs)

// Get required custom field keys
customKeys := reqs.RequiredCustomFieldKeys()
fmt.Printf("Required custom fields: %v\n", customKeys)

// Check if a specific field is required
if reqs.IsRequired("description") {
    fmt.Println("Description is required")
}

Validate Before Creating

// Validate that all required fields are provided
missing := graphql.ValidateRequiredFields(reqs, map[string]any{
    "name":        "Feature Name",
    "description": "Description",
})

if len(missing) > 0 {
    fmt.Printf("Missing required fields: %v\n", missing)
    return
}

// Now safe to create
resp, err := generated.CreateFeatureWithRelease(ctx, client, ...)

Get Custom Field Definitions

Discover available custom fields for a project:

defs, err := graphql.GetProjectCustomFieldDefinitions(ctx, client, "PROJECT-ID")
if err != nil {
    log.Fatal(err)
}

for _, def := range defs {
    fmt.Printf("Field: %s (key: %s, type: %s)\n", def.Name, def.Key, def.Type)
    if len(def.Options) > 0 {
        fmt.Println("  Options:")
        for _, opt := range def.Options {
            fmt.Printf("    - %s\n", opt.Name)
        }
    }
}

Error Handling

resp, err := generated.GetFeature(ctx, client, "INVALID")
if err != nil {
    // Check error type
    fmt.Printf("Error: %v\n", err)
    return
}

if resp.Feature == nil {
    fmt.Println("Feature not found")
    return
}

Example Client (Learning Reference)

The graphql/example package provides a handwritten GraphQL client as educational reference code for users learning to write GraphQL clients without code generation.

import "github.com/grokify/aha-go/graphql/example"

client := example.NewClient("mycompany", "your-api-key")

var result struct {
    Feature struct {
        Name        string `json:"name"`
        Description struct {
            MarkdownBody string `json:"markdownBody"`
        } `json:"description"`
    } `json:"feature"`
}

err := client.Query(ctx, example.GetFeatureQuery, map[string]any{"id": "FEAT-123"}, &result)

For production use, the generated client is recommended.

Regenerating the Client

If the Aha.io schema changes, regenerate the client:

# Install gqlforge
go install github.com/grokify/gqlforge/cmd/gqlforge@latest

# Validate operations against schema
gqlforge validate --schema graphql/schema.graphql --operations "graphql/operations/*.graphql"

# Regenerate typed client
gqlforge generate

API Reference

For complete API documentation, see pkg.go.dev.