Skip to content

Commit 50eb646

Browse files
docs: how to add and use structured GraphQL types for standalone struct arguments (#367)
--------- Co-authored-by: Zach Daniel <zachary.s.daniel@gmail.com>
1 parent a98214c commit 50eb646

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Use Struct Types with GraphQL
2+
3+
Custom struct types provide structured GraphQL input validation for standalone data arguments instead of generic JSON strings.
4+
5+
## The Problem
6+
7+
Relationship arguments automatically get structured inputs, but standalone data arguments default to JsonString:
8+
9+
```elixir
10+
# Relationship: Gets structured CreateAddressInput
11+
argument :address, :map
12+
change manage_relationship(:address, type: :direct_control)
13+
14+
# Standalone data: Becomes JsonString (no validation)
15+
argument :metadata, :map
16+
```
17+
18+
## Solution: Custom Struct Types
19+
20+
### Using Ash.TypedStruct (new structures)
21+
22+
```elixir
23+
defmodule MyApp.Types.TicketMetadataType do
24+
use Ash.TypedStruct
25+
26+
typed_struct do
27+
field :priority, :string, allow_nil?: false
28+
field :category, :string, allow_nil?: false
29+
field :tags, {:array, :string}, allow_nil?: true
30+
end
31+
32+
use AshGraphql.Type
33+
34+
@impl true
35+
def graphql_type(_), do: :ticket_metadata
36+
37+
@impl true
38+
def graphql_input_type(_), do: :create_ticket_metadata_input
39+
end
40+
```
41+
42+
### Using NewType (reference existing resources)
43+
44+
```elixir
45+
defmodule MyApp.Types.TicketMetadataType do
46+
use Ash.Type.NewType,
47+
subtype_of: :struct,
48+
constraints: [
49+
instance_of: MyApp.TicketMetadata
50+
]
51+
52+
use AshGraphql.Type
53+
54+
@impl true
55+
def graphql_type(_), do: :ticket_metadata
56+
57+
@impl true
58+
def graphql_input_type(_), do: :create_ticket_metadata_input
59+
end
60+
```
61+
62+
**Key Differences:**
63+
- **TypedStruct**: Define fields manually, full control, requires manual sync
64+
- **NewType**: Auto-inherits from resource, stays in sync, less flexible
65+
66+
### Use in your resource
67+
68+
```elixir
69+
defmodule MyApp.Ticket do
70+
actions do
71+
action :add_metadata do
72+
argument :metadata, MyApp.Types.TicketMetadataType,
73+
allow_nil?: false
74+
end
75+
end
76+
end
77+
```
78+
79+
## Result
80+
81+
```graphql
82+
input AddMetadataInput {
83+
metadata: CreateTicketMetadataInput! # Structured input
84+
}
85+
86+
input CreateTicketMetadataInput {
87+
priority: String!
88+
category: String!
89+
tags: [String!]
90+
}
91+
```
92+
93+
## Common Issues & Solutions
94+
95+
**Still seeing JsonString?**
96+
1. Ensure `graphql_input_type` references an existing input type
97+
2. Target resource must have GraphQL mutations defined:
98+
```elixir
99+
graphql do
100+
mutations do
101+
create :create_ticket_metadata, :create
102+
end
103+
end
104+
```
105+
3. Run `mix ash.codegen` to regenerate schema
106+
107+
**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.

0 commit comments

Comments
 (0)