graphql¶
The graphql package provides a type-safe client for Aha.io's GraphQL API, generated by genqlient via gqlforge.
Installation¶
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)
}
ListFeatures¶
List features with release information (not available via REST API).
resp, err := generated.ListFeatures(ctx, client, 1, 50) // page, perPage
if err != nil {
log.Fatal(err)
}
for _, feature := range resp.Features.Nodes {
fmt.Printf("Feature: %s\n", feature.Name)
if feature.Release != nil {
fmt.Printf(" Release: %s (date: %s)\n",
feature.Release.Name,
feature.Release.ReleaseDate)
}
}
Note: The GraphQL ListFeatures query includes release information (release_id, release_date) that is not available in the REST API's feature listing.
Available Queries¶
| Query | Description |
|---|---|
ListFeatures |
List features with release info (pagination) |
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 using the graphql.SetCustomFieldValues
wrapper. Unlike calling the generated mutation directly, the wrapper also checks
the mutation's response payload for field-level validation errors (Aha can return
these inside a transport-level-successful response) and returns them as a normal
Go error, so a rejected/unknown custom field key never looks like a silent success:
values, err := graphql.SetCustomFieldValues(ctx, client,
"FEATURE-ID",
generated.CustomFieldableTypeEnumFeature,
map[string]any{
"priority_score": 85,
"customer_segment": "Enterprise",
"target_date": "2024-06-15",
},
)
if err != nil {
log.Fatal(err)
}
for _, v := range values {
fmt.Printf("%s = %v (%s)\n", v.Key, v.Value, v.HumanValue)
}
Custom field values are usually plain scalars (string/number/bool) matching the
custom field's type in Aha, not JSON objects — pass them as-is in the map[string]any.
Supported record types for custom fields:
CustomFieldableTypeEnumFeatureCustomFieldableTypeEnumIdeaCustomFieldableTypeEnumEpicCustomFieldableTypeEnumInitiativeCustomFieldableTypeEnumGoalCustomFieldableTypeEnumRelease
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.