Query and body parameters
Scribe supports multiple ways to describe query and body parameters. You can manually specify them with annotations, or Scribe can extract them from your validation rules.
Using annotations
To describe query or body parameters for your endpoint, use the @queryParam
/@bodyParam
tags (or #[QueryParam]
/#[BodyParam]
attributes) on the method handling it.
The @bodyParam
tag takes the name of the parameter, a type, an optional "required" label, and an optional description. The @queryParam
tag follows the same format, but the type is optional. If you don't specify a type, Scribe will try to figure out the type based on the parameter name (or fallback to string
).
tip
Valid types:
string
integer
/int
number
boolean
/bool
object
(see Array and object parameters below)file
(see File uploads below)
Additionally, you can append []
to a type any number of times to indicate an array field (integer[]
= array of integers).
note
array
is not a supported type, and may lead to unexpected errors. You should rather define the type of the array (eg int[]
, string[]
).
Examples:
- Docblock
- Attributes
/**
* @bodyParam user_id int required The id of the user. Example: 9
* @bodyParam room_id string The id of the room.
* @bodyParam forever boolean Whether to ban the user forever. Example: false
* @bodyParam another_one number This won't be added to the examples. No-example
*/
public function updateDetails()
{
}
/**
* @queryParam sort string Field to sort by. Defaults to 'id'.
* @queryParam fields required Comma-separated list of fields to include in the response. Example: title,published_at,is_public
* @queryParam filters[published_at] Filter by date published.
* @queryParam filters[is_public] integer Filter by whether a post is public or not. Example: 1
*/
public function listPosts()
{
// ...
}
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\QueryParam;
#[BodyParam("user_id", "int", "The id of the user.", example: 9)]
#[BodyParam("room_id", "string", "The id of the room.", required: false)]
#[BodyParam("forever", "boolean", "Whether to ban the user forever.", required: false, example: false)]
#[BodyParam("another_one", "number", "This won't be added to the examples.", required: false, example: "No-example")]
public function updateDetails()
{
}
#[QueryParam("sort", "string", "Field to sort by. Defaults to 'id'.", required: false)]
#[QueryParam("fields", "Comma-separated list of fields to include in the response.", example: "title,published_at,is_public")]
#[QueryParam("filters[published_at]", "Filter by date published.", required: false)]
#[QueryParam("filters[is_public]", "integer", "Filter by whether a post is public or not.", example: 1, required: false)]
public function listPosts()
{
// ...
}
tip
If you use form requests, you can place the annotations there instead of in your controller.
/**
* @queryParam lang required The language.
* @bodyParam title string The title of the post.
*/
#[BodyParam("body", "string", "The content of the post.")]
class CreatePostRequest extends \Illuminate\Foundation\Http\FormRequest
{
}
// in your controller...
public function createPost(CreatePostRequest $request)
{
// ...
}
Specifying or omitting examples
By default, Scribe will generate a random value for each parameter, to be used in the example requests and response calls. If you'd like to use a specific example value, you can do so by adding Example: <your-example>
to the end of the parameter description.
If you want Scribe to omit a certain optional parameter in examples and response calls:
- end the description with
No-example
if using a tag (@queryParam
/@bodyParam
) - pass
"No-example"
as theexample:
argument if using an attribute (#[QueryParam]
/#[BodyParam]
)
The parameter will still be present in the docs, but not included in examples.
For instance:
/**
* @queryParam sort Field to sort by. Defaults to 'id'. Example: published_at
* @queryParam fields required Comma-separated fields to include in the response. Example: title,published_at,id
* @queryParam filters[published_at] Filter by date published. No-example
*/
#[QueryParam("filters[title]", "Filter by title.", required: false, example: "No-example")]
public function endpoint()
gives:
Array and object parameters
Sometimes you have parameters that are arrays or objects. To handle them in @bodyParam
and @queryParam
, Scribe uses the following convention:
caution
If you can, you should avoid using query parameters that are arrays or objects. There isn't a standardised format for handling them, so the way your API clients set them may be different from what your server expects (and what Scribe generates).
Arrays
For arrays, use a single field with type <type of items>[]
. For instance, to denote an array cars
of elements of type integer
:
@bodyParam cars integer[]
@bodyParam cars integer[] This is a description. Example: [4, 6]
@bodyParam colors string[] Example: ["red", "blue"]
Objects
For objects, you need:
- a parent field with type
object
- an entry for each field, named with the dot notation
<parent name>.<field>
For instance, to denote an object cars
with a field name
of type string
and a field year
of type number
:
@bodyParam cars object
@bodyParam cars.name string
@bodyParam cars.year number
You can also add descriptions and examples to the parent or children fields if you wish:
@bodyParam cars object Car details. Example: {"name": "Carpenter", "year": 2019}
@bodyParam cars.name string Name of the car.
@bodyParam cars.year int Example: 1997
Arrays of objects
For an array of objects, you need:
- a parent field with type
object[]
- an entry for each field, named with the dot notation
<parent name>[].<field>
.
For instance, to denote an array of objects cars
with each item having field name
and year
:
@bodyParam cars object[] List of car details. Example: [{"name": "Carpenter", "year": 2019}]
@bodyParam cars[].name string Name of the car.
@bodyParam cars[].year int Example: 1997
If your entire request body is an array, just omit the field name:
@bodyParam [] object[]
@bodyParam [].name string
@bodyParam [].year int
Examples:
- Body parameters
- Query parameters
/**
* @bodyParam user object required The user details
* @bodyParam user.name string required The user's name
* @bodyParam user.age string required The user's age
* @bodyParam friend_ids int[] List of the user's friends.
* @bodyParam cars object[] List of cars
* @bodyParam cars[].year string The year the car was made. Example: 1997
* @bodyParam cars[].make string The make of the car. Example: Toyota
*/
/**
* @queryParam sort string Field to sort by. Defaults to 'id'.
* @queryParam fields string[] required Comma-separated list of fields to include in the response. Example:
title,published_at,is_public
* @queryParam filters object Fields to filter by
* @queryParam filters.published_at Filter by date published.
* @queryParam filters.is_public integer Filter by whether a post is public or not. Example: 1
*/
File uploads
To document file inputs with @bodyParam
, use the type file
. You can add a description and example as usual.
note
Adding a file parameter will automatically set the 'Content-Type' header in example requests and response calls to multipart/form-data
.
For files, your example should be the path to a file that exists on your machine. This path should be either:
- absolute, or
- relative to your project directory, or
- relative to your Laravel storage directory.
If you don't specify an example, Scribe will generate a fake file for example requests and response calls.
/**
* @bodyParam caption string The image caption
* @bodyParam image file required The image.
*/
Validation rules
If you use Laravel's validation functionality to validate your incoming request parameters, Scribe can use that information to extract information about your parameters as well as generate examples.
There are two supported options: using inline validators and form requests. Also, not all rules are supported.
These two approaches are very different:
- WIth form requests, Scribe will create an instance of the
FormRequest
class typehinted in your controller method, and execute itsrules()
method.note
Form requests are not initialized by Scribe in the same way as Laravel would, since there is no real HTTP request when generating. If you encounter strange behaviour, you can try customising the initialization process with the
instantiateFormRequestUsing()
hook. - With inline validators, Scribe will read and parse the validation code in your controller (
$request->validate()
,Request::validate
orValidator::make()
).
Since these rules only describe validation logic, Scribe allows you to provide extra information, like a description and example:
- For form requests, add a
bodyParameters()
/queryParameters()
method where you can add a description and example for each parameter. - For inline validators, add a comment above the parameter, specifying a description and example.
If you specify a description, Scribe will prepend your description to what it generates from your validation rules.
note
If you have rules that Scribe doesn't support, Scribe's generated value might not pass their validation checks. You can get around that by manually specifying an example (bodyParameters()
or comment).
Examples
- Inline ($request->validate())
- Inline (Request::validate())
- Inline (Validator::make())
- Form request
public function createPost($request)
{
$validated = $request->validate([
// Contents of the post
'content' => 'string|required|min:100',
// The title of the post. Example: My First Post
'title' => 'string|required|max:400',
'author_display_name' => 'string',
'author_homepage' => 'url',
'author_timezone' => 'timezone',
'author_email' => 'email|required',
// Date to be used as the publication date.
'publication_date' => 'date_format:Y-m-d',
// Category the post belongs to.
'category' => ['in:news,opinion,quiz', 'required'],
// This will be included in docs but not in examples. No-example
'extra' => 'string',
]);
return Post::create($validated);
}
public function createPost($request)
{
$validated = Request::validate([
// Contents of the post
'content' => 'string|required|min:100',
// The title of the post. Example: My First Post
'title' => 'string|required|max:400',
'author_display_name' => 'string',
'author_homepage' => 'url',
'author_timezone' => 'timezone',
'author_email' => 'email|required',
// Date to be used as the publication date.
'publication_date' => 'date_format:Y-m-d',
// Category the post belongs to.
'category' => ['in:news,opinion,quiz', 'required'],
// This will be included in docs but not in examples. No-example
'extra' => 'string',
]);
return Post::create($validated);
}
public function createPost($request)
{
$validator = Validator::make($request, [
// Contents of the post
'content' => 'string|required|min:100',
// The title of the post. Example: My First Post
'title' => 'string|required|max:400',
'author_display_name' => 'string',
'author_homepage' => 'url',
'author_timezone' => 'timezone',
'author_email' => 'email|required',
// Date to be used as the publication date.
'publication_date' => 'date_format:Y-m-d',
// Category the post belongs to.
'category' => ['in:news,opinion,quiz', 'required'],
// This will be included in docs but not in examples. No-example
'extra' => 'string',
]);
if ($validator->passes()) {
// ...
}
}
class CreatePostRequest extends FormRequest
{
public function rules()
{
return [
'content' => 'string|required|min:100',
'title' => 'string|required|max:400',
'author_display_name' => 'string',
'author_homepage' => 'url',
'author_timezone' => 'timezone',
'author_email' => 'email|required',
'publication_date' => 'date_format:Y-m-d',
'category' => ['in:news,opinion,quiz', 'required'],
'extra' => 'string',
];
}
public function bodyParameters()
{
return [
'content' => [
'description' => 'Contents of the post',
],
'title' => [
'description' => 'The title of the post.',
'example' => 'My First Post',
],
'publication_date' => [
'description' => 'Date to be used as the publication date.',
],
'category' => [
'description' => 'Category the post belongs to.',
],
'extra' => [
'description' => 'This will be included in docs but not in examples.',
'example' => 'No-example',
],
];
}
}
All of these lead to:
note
In inline validators, Scribe currently supports simple string rules and arrays of string rules, as well as custom closure and class rules with certain features (described below). Concatentation, interpolation, and dynamic expressions will be ignored, so it's best to specify rules as an array, so Scribe can ignore the rules it doesn't understand. For example:
$rules = [
// 👍 Supported
'param1' => 'rule1|rule2',
'param2' => ['rule1', 'rule2'],
// 👍 Supported (the third rule will be ignored if doesn't have a static "docs()" method)
'param3' => ['rule1', 'rule2', new SomeRule()],
// ❌ All rules are ignored because of concatenation
'param4' => 'rule1|rule2:'.$someValues,
// 😐 Only rule2 is ignored
'param4' => ['rule1', 'rule2:'.$someValues],
];
Inline validator parsing is also currently not supported in Closure routes.
Supported validation rules
There are three levels of support for validation rules:
- Full support: Based on the rule, Scribe can generate a description, type, and valid example for a parameter.
- Partial support: Scribe can generate a description and type, but the example generated may not pass validation. You can always specify your own examples.
- No support: Any rule not listed below. Scribe will simply ignore them. If you'd like support, you can raise a PR.
Full support
required
in
string
bool
/boolean
int
/integer
numeric
array
file
,image
alpha
,alpha_dash
,alpha_num
starts_with
,ends_with
email
,url
,ip
,json
,uuid
,regex
digits
,digits_between
timezone
,date
,date_format
before
,before_or_equal
,after
,after_or_equal
(Full support when the other date is a value, partial support when it's referencing another field)accepted
andaccepted_if
enum
Partial support
required_if
,required_unless
,required_with
,required_without
,required_with_all
,required_without_all
not_in
same
,different
exists
Custom validation rules
If you use laravel's custom class rules you can add a description and a default example for that rule with a docs
static method:
use Illuminate\Contracts\Validation\Rule;
class Kilobyte implements Rule
{
// ...
public static function docs(): array
{
return [
'description' => 'The data must be a valid Kilobyte representation',
'example' => '52KB', // Only used if no other supported rules are present
];
}
}
For closure rules you can provide a description of what it validates with a comment above it:
$validator->validate([
'kilobyte' => [
/** The value must be a valid kilobyte representation */
function ($attribute, $value, $fail) {
// ...
}
]
]);
Using validation rules for query parameters
By default, validation rules are interpreted as body parameters. If you use yours for query parameters instead, you can tell Scribe this by either:
- adding the text
"Query parameters"
anywhere in the form request docblock or in a comment above the inline validator call, or - (form requests only) adding a
queryParameters()
method (instead ofbodyParameters()
).
- Inline
- Form request
public function createPost($request)
{
// Query parameters
$validated = $request->validate([
// The page number. Example: 1
'page' => 'int',
]);
}
/**
* Query parameters
* You can still have other stuff in your docblock.
*/
class CreatePostRequest extends FormRequest
{
public function rules()
{
return [
'page' => 'int',
];
}
public function queryParameters()
{
return [
'page' => [
'description' => 'The page number',
'example' => 1
],
];
}
}