Packages

The extension for building GraphQL APIs with Ash

Current section

Files

Jump to
ash_graphql documentation topics use-struct-types-with-graphql.md
Raw

documentation/topics/use-struct-types-with-graphql.md

<!--
SPDX-FileCopyrightText: 2020 Zach Daniel
SPDX-License-Identifier: MIT
-->
# Use Struct Types with GraphQL
Custom struct types provide structured GraphQL input validation for standalone data arguments instead of generic JSON strings.
## The Problem
Relationship arguments automatically get structured inputs, but standalone data arguments default to JsonString:
```elixir
# Relationship: Gets structured CreateAddressInput
argument :address, :map
change manage_relationship(:address, type: :direct_control)
# Standalone data: Becomes JsonString (no validation)
argument :metadata, :map
```
## Solution: Custom Struct Types
### Using Ash.TypedStruct (new structures)
```elixir
defmodule MyApp.Types.TicketMetadataType do
use Ash.TypedStruct
typed_struct do
field :priority, :string, allow_nil?: false
field :category, :string, allow_nil?: false
field :tags, {:array, :string}, allow_nil?: true
end
use AshGraphql.Type
@impl true
def graphql_type(_), do: :ticket_metadata
@impl true
def graphql_input_type(_), do: :create_ticket_metadata_input
end
```
### Using NewType (reference existing resources)
```elixir
defmodule MyApp.Types.TicketMetadataType do
use Ash.Type.NewType,
subtype_of: :struct,
constraints: [
instance_of: MyApp.TicketMetadata
]
use AshGraphql.Type
@impl true
def graphql_type(_), do: :ticket_metadata
@impl true
def graphql_input_type(_), do: :create_ticket_metadata_input
end
```
**Key Differences:**
- **TypedStruct**: Define fields manually, full control, requires manual sync
- **NewType**: Auto-inherits from resource, stays in sync, less flexible
### Use in your resource
```elixir
defmodule MyApp.Ticket do
actions do
action :add_metadata do
argument :metadata, MyApp.Types.TicketMetadataType,
allow_nil?: false
end
end
end
```
## Result
```graphql
input AddMetadataInput {
metadata: CreateTicketMetadataInput! # Structured input
}
input CreateTicketMetadataInput {
priority: String!
category: String!
tags: [String!]
}
```
## Common Issues & Solutions
**Still seeing JsonString?**
1. Ensure `graphql_input_type` references an existing input type
2. Target resource must have GraphQL mutations defined:
```elixir
graphql do
mutations do
create :create_ticket_metadata, :create
end
end
```
3. Run `mix ash.codegen` to regenerate schema
**Note:** Using `:struct` with `instance_of` directly also falls back to JsonString when used as an input. Always wrap in a custom type for structured validation.