Skip to content

Commit 3027821

Browse files
ArmandPhilippotsarah11918florian-lefebvreyanthomasdev
authored
feat(astro:actions): add docs for missing public APIs (#12596)
Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> Co-authored-by: yanthomasdev <61414485+yanthomasdev@users.noreply.github.com>
1 parent 510aee8 commit 3027821

File tree

2 files changed

+379
-99
lines changed

2 files changed

+379
-99
lines changed

src/content/docs/en/guides/actions.mdx

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ You can use the provided `ActionError` to throw an error from your action `handl
207207

208208
#### Creating an `ActionError`
209209

210-
To throw an error, import the `ActionError()` class from the `astro:actions` module. Pass it a human-readable status `code` (e.g. `"NOT_FOUND"` or `"BAD_REQUEST"`), and an optional `message` to provide further information about the error.
210+
To throw an error, import the [`ActionError()` class](/en/reference/modules/astro-actions/#actionerror) from the `astro:actions` module. Pass it a human-readable status `code` (e.g. `"NOT_FOUND"` or `"BAD_REQUEST"`), and an optional `message` to provide further information about the error.
211211

212212
This example throws an error from a `likePost` action when a user is not logged in, after checking a hypothetical "user-session" cookie for authentication:
213213

@@ -301,6 +301,55 @@ export const server = {
301301
}
302302
```
303303

304+
### Using validators with form inputs
305+
306+
When your action is [configured to accept form data](/en/reference/modules/astro-actions/#accept-property), you can use any Zod validators to validate your fields (e.g. `z.coerce.date()` for date inputs). Extension functions including `.refine()`, `.transform()`, and `.pipe()` are also supported on the `z.object()` validator.
307+
308+
Additionally, Astro provides special handling under the hood for your convenience to validate the following types of field inputs:
309+
310+
- Inputs of type `number` can be validated using `z.number()`
311+
- Inputs of type `checkbox` can be validated using `z.coerce.boolean()`
312+
- Inputs of type `file` can be validated using `z.instanceof(File)`
313+
- Multiple inputs of the same `name` can be validated using `z.array(/* validator */)`
314+
- All other inputs can be validated using `z.string()`
315+
316+
When your form is submitted with empty inputs, the output type may not match your `input` validator. Empty values are converted to `null` except when validating arrays or booleans. For example, if an input of type `text` is submitted with an empty value, the result will be `null` instead of an empty string (`""`).
317+
318+
To apply a union of different validators, use the `z.discriminatedUnion()` wrapper to narrow the type based on a specific form field. This example accepts a form submission to either "create" or "update" a user, using the form field with the name `type` to determine which object to validate against:
319+
320+
```ts title="src/actions/index.ts" {7-21} "create" "update"
321+
import { defineAction } from 'astro:actions';
322+
import { z } from 'astro:schema';
323+
324+
export const server = {
325+
changeUser: defineAction({
326+
accept: 'form',
327+
input: z.discriminatedUnion('type', [
328+
z.object({
329+
// Matches when the `type` field has the value `create`
330+
type: z.literal('create'),
331+
name: z.string(),
332+
email: z.string().email(),
333+
}),
334+
z.object({
335+
// Matches when the `type` field has the value `update`
336+
type: z.literal('update'),
337+
id: z.number(),
338+
name: z.string(),
339+
email: z.string().email(),
340+
}),
341+
]),
342+
async handler(input) {
343+
if (input.type === 'create') {
344+
// input is { type: 'create', name: string, email: string }
345+
} else {
346+
// input is { type: 'update', id: number, name: string, email: string }
347+
}
348+
},
349+
}),
350+
};
351+
```
352+
304353
### Validating form data
305354

306355
Actions will parse submitted form data to an object, using the value of each input’s `name` attribute as the object keys. For example, a form containing `<input name="search">` will be parsed to an object like `{ search: 'user input' }`. Your action's `input` schema will be used to validate this object.
@@ -656,7 +705,7 @@ Actions are accessible as public endpoints based on the name of the action. For
656705

657706
To authorize action requests, add an authentication check to your action handler. You may want to use [an authentication library](/en/guides/authentication/) to handle session management and user information.
658707

659-
Actions expose the full `APIContext` object to access properties passed from middleware using `context.locals`. When a user is not authorized, you can raise an `ActionError` with the `UNAUTHORIZED` code:
708+
Actions expose [a subset of the `APIContext` object](/en/reference/modules/astro-actions/#actionapicontext) to access properties passed from middleware using `context.locals`. When a user is not authorized, you can raise an `ActionError` with the `UNAUTHORIZED` code:
660709

661710
```ts title="src/actions/index.ts" {6-8}
662711
import { defineAction, ActionError } from 'astro:actions';
@@ -679,7 +728,7 @@ export const server = {
679728

680729
Astro recommends authorizing user sessions from your action handler to respect permission levels and rate-limiting on a per-action basis. However, you can also gate requests to all actions (or a subset of actions) from middleware.
681730

682-
Use the `getActionContext()` function from your middleware to retrieve information about any inbound action requests. This includes the action name and whether that action was called using a client-side remote procedure call (RPC) function (e.g. `actions.blog.like()`) or an HTML form.
731+
Use the [`getActionContext()` function](/en/reference/modules/astro-actions/#getactioncontext) from your middleware to retrieve information about any inbound action requests. This includes the action name and whether that action was called using a client-side remote procedure call (RPC) function (e.g. `actions.blog.like()`) or an HTML form.
683732

684733
The following example rejects all action requests that do not have a valid session token. If the check fails, a "Forbidden" response is returned. Note: this method ensures that actions are only accessible when a session is present, but is _not_ a substitute for secure authorization.
685734

0 commit comments

Comments
 (0)