Overview
Geni infers response schemas from what your controller actions return. It reads thetoArray() method of each JsonResource, types every key from the expression it returns, and wraps the result exactly the way Laravel sends it over the wire.
Everything happens statically: model attributes come from your migrations and $casts, never from a database connection.
A Complete Example
Given this model, migration and resource:#/components/schemas/UserResource:
#/components/schemas/UserStatus ({"type": "string", "enum": ["active", "suspended"]}) and referenced wherever it is used.
How Keys Are Typed
Model Attributes
$this->attribute is typed from the migration column, then refined by $casts or the casts() method:
- Booleans, arrays, dates (
format: date-time) and enum casts are applied. An enum cast becomes a$refto a component schema for the enum. - Nullable columns produce a union type such as
["string", "null"]or["integer", "null"]. - Carbon formatters keep nullability:
$this->created_at?->toIso8601String()is["string", "null"].
Expressions and Literals
- Literal strings and numbers produce a
const('label' => 'user'is{"type": "string", "const": "user"}). - Casts such as
(bool) $this->email_verified_ator(int) ...use the cast type. - Nested arrays become objects with their own
propertiesandrequiredlists; lists of literals become arrays (['a', 'b']is an array of strings). - String concatenation, ternaries,
match, common helpers (route(),url(),count(), …) are also typed.
Relations
$this->postsor$this->whenLoaded('posts')without a resource becomes an array of (or reference to) the related model schema, for example{"type": "array", "items": {"$ref": "#/components/schemas/Post"}}.- A
belongsTorelation with a nullable foreign key is nullable.
Nested Resources and Collections
new TeamResource(...), PostResource::make(...) and PostResource::collection(...) become component references ($ref) to the nested resource schema. A nested resource wrapping a nullable belongsTo relation becomes anyOf: [$ref, {type: null}].
Conditional Keys and required
Every resource and model schema lists its required keys. Keys produced by conditional helpers are optional, everything else is required:
The schema of an optional key comes from the value passed to the helper:
whenCounted('posts') is an integer, whenNotNull($this->name) drops null from the type, and whenLoaded('posts') uses the relation.
parent::toArray()
return parent::toArray($request); and array_merge(parent::toArray($request), [...]) document the serialized model: its visible attributes typed from migrations and casts.
Hidden and Visible Attributes
Model schemas (andparent::toArray()) honour $hidden and $visible, as well as the #[Hidden] / #[Visible] model attributes. In the example above, email never appears in the User schema.
Keys whose value cannot be resolved statically fall back to
string and carry an x-geni-unresolved diagnostic. Run php artisan geni:analyze to list them.Response Envelopes
Responses are wrapped the way Laravel sends them.Single Resource
`UserResource`.
- Custom wrap key:
public static $wrap = 'team';documents{team: ...}instead of{data: ...}. - No wrapping:
public static $wrap = null;documents the resource schema itself (a bare$ref). with(): keys returned by the resource’swith()method are added next to the wrapped data and listed inrequired.additional():(new UserResource($user))->additional(['meta' => ['version' => '1']])adds a typedmetakey next todata.
Collections
{data: [...]} with items referencing UserResource; the description is Array of `UserResource` .
Paginated Collections
When the collection wrapspaginate(), simplePaginate() or cursorPaginate():
{data, links, meta} (all three required), described as Paginated set of `UserResource` :
data: array ofUserResourcereferenceslinks:first,last,prev,nextmeta: forpaginate(),current_page,from,last_page,links,path,per_page,to,total;simplePaginate()andcursorPaginate()get the meta keys of their own paginator (for examplenext_cursor/prev_cursorfor cursor pagination)
ResourceCollection Subclasses
A custom collection uses its $collects property to find the item resource:
Status Codes
200 next to a 201 or 204.
Raw Eloquent Models
Returning a model directly documents the serialized model (#/components/schemas/User, described as `User`), not a resource:
User::findOrFail(), User::query()->...->first() / firstOrFail(), and User::all() / ->get() (an array of User). Model schemas list all visible columns with their required keys.
Raw response()->json() Calls
@response and #[Response]
Naming a resource, model or Data class in @response SomeResource or #[Response(type: ...)] produces the same wrapped, fully inferred schema as returning it.
Component Reusability (#/components/schemas)
Every analyzed JsonResource, model and enum is registered once under components.schemas (for example #/components/schemas/UserResource, #/components/schemas/User, #/components/schemas/UserStatus) and referenced with $ref, keeping the document concise.