Skip to main content

Overview

Geni infers response schemas from what your controller actions return. It reads the toArray() 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:
Geni registers #/components/schemas/UserResource:
The enum is registered once as #/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 $ref to 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_at or (int) ... use the cast type.
  • Nested arrays become objects with their own properties and required lists; 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->posts or $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 belongsTo relation 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 (and parent::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

The response description is `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’s with() method are added next to the wrapped data and listed in required.
  • additional(): (new UserResource($user))->additional(['meta' => ['version' => '1']]) adds a typed meta key next to data.

Collections

Documented as {data: [...]} with items referencing UserResource; the description is Array of `UserResource` .

Paginated Collections

When the collection wraps paginate(), simplePaginate() or cursorPaginate():
Geni documents Laravel’s paginated resource envelope {data, links, meta} (all three required), described as Paginated set of `UserResource` :
  • data: array of UserResource references
  • links: first, last, prev, next
  • meta: for paginate(), current_page, from, last_page, links, path, per_page, to, total; simplePaginate() and cursorPaginate() get the meta keys of their own paginator (for example next_cursor / prev_cursor for cursor pagination)

ResourceCollection Subclasses

A custom collection uses its $collects property to find the item resource:

Status Codes

An operation only gets the success response it actually returns; there is no extra default 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:
Supported patterns include a type-hinted route parameter, 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

Geni reconstructs an object schema from the literal keys and value types.

@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.