or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

app-server.mdapp-state-navigation.mdapp-state.mdconfiguration.mderror-handling.mdform-actions.mdhooks.mdindex.mdload-functions.mdnodejs-integration.mdrequest-handling.mdresponse-creation.mdservice-worker.mdvite-integration.md

form-actions.mddocs/

0

# Form Actions

1

2

SvelteKit provides utilities for handling form submissions with progressive enhancement, validation, and proper error handling.

3

4

## Capabilities

5

6

### Fail Function

7

8

Creates an ActionFailure object for form submission failures, typically used for validation errors or processing failures.

9

10

```typescript { .api }

11

/**

12

* Create an ActionFailure object. Call when form submission fails.

13

* @param status - HTTP status code (must be 400-599)

14

* @param data - Data associated with the failure (e.g. validation errors)

15

* @returns ActionFailure object

16

*/

17

function fail(status: number): ActionFailure<undefined>;

18

function fail<T>(status: number, data: T): ActionFailure<T>;

19

```

20

21

**Usage Examples:**

22

23

```typescript

24

import { fail } from '@sveltejs/kit';

25

26

// Basic validation failure

27

export const actions = {

28

default: async ({ request }) => {

29

const data = await request.formData();

30

const email = data.get('email');

31

32

if (!email) {

33

return fail(400, { message: 'Email is required' });

34

}

35

36

if (!isValidEmail(email)) {

37

return fail(400, { message: 'Invalid email format' });

38

}

39

40

// Process successful form...

41

return { success: true };

42

}

43

};

44

45

// Multiple validation errors

46

export const actions = {

47

register: async ({ request }) => {

48

const data = await request.formData();

49

const errors = {};

50

51

const email = data.get('email');

52

const password = data.get('password');

53

const confirmPassword = data.get('confirmPassword');

54

55

if (!email) errors.email = 'Email is required';

56

if (!password) errors.password = 'Password is required';

57

if (password !== confirmPassword) {

58

errors.confirmPassword = 'Passwords do not match';

59

}

60

61

if (Object.keys(errors).length > 0) {

62

return fail(400, { errors });

63

}

64

65

try {

66

await createUser({ email, password });

67

return { success: true };

68

} catch (error) {

69

return fail(500, { message: 'Registration failed' });

70

}

71

}

72

};

73

```

74

75

### Action Failure Type Guard

76

77

Checks whether an object is an ActionFailure returned by the `fail()` function.

78

79

```typescript { .api }

80

/**

81

* Checks whether this is an action failure thrown by fail().

82

* @param e - The object to check

83

* @returns Type predicate indicating if e is an ActionFailure

84

*/

85

function isActionFailure(e: unknown): boolean;

86

```

87

88

**Usage Examples:**

89

90

```typescript

91

import { isActionFailure, fail } from '@sveltejs/kit';

92

93

export const actions = {

94

process: async ({ request }) => {

95

try {

96

const result = await someAsyncOperation();

97

return result;

98

} catch (error) {

99

if (isActionFailure(error)) {

100

// Re-throw ActionFailure

101

throw error;

102

}

103

104

// Handle other errors

105

return fail(500, { message: 'Processing failed' });

106

}

107

}

108

};

109

```

110

111

## Action Patterns

112

113

### Basic Form Action

114

115

```typescript

116

// src/routes/contact/+page.server.js

117

import { fail } from '@sveltejs/kit';

118

119

export const actions = {

120

default: async ({ request }) => {

121

const data = await request.formData();

122

const name = data.get('name');

123

const email = data.get('email');

124

const message = data.get('message');

125

126

// Validation

127

if (!name || !email || !message) {

128

return fail(400, {

129

error: 'All fields are required',

130

name,

131

email,

132

message

133

});

134

}

135

136

// Process form

137

try {

138

await sendContactEmail({ name, email, message });

139

return { success: true };

140

} catch (error) {

141

return fail(500, { error: 'Failed to send message' });

142

}

143

}

144

};

145

```

146

147

### Multiple Named Actions

148

149

```typescript

150

// src/routes/admin/users/[id]/+page.server.js

151

import { fail, redirect, error } from '@sveltejs/kit';

152

153

export const actions = {

154

update: async ({ request, params }) => {

155

const data = await request.formData();

156

const user = await getUser(params.id);

157

158

if (!user) {

159

throw error(404, 'User not found');

160

}

161

162

try {

163

await updateUser(params.id, {

164

name: data.get('name'),

165

email: data.get('email')

166

});

167

168

return { success: true, message: 'User updated' };

169

} catch (err) {

170

return fail(400, { error: 'Update failed' });

171

}

172

},

173

174

delete: async ({ params }) => {

175

const user = await getUser(params.id);

176

177

if (!user) {

178

throw error(404, 'User not found');

179

}

180

181

try {

182

await deleteUser(params.id);

183

throw redirect(303, '/admin/users');

184

} catch (err) {

185

return fail(500, { error: 'Delete failed' });

186

}

187

}

188

};

189

```

190

191

### File Upload Action

192

193

```typescript

194

import { fail } from '@sveltejs/kit';

195

import { writeFile } from 'fs/promises';

196

import { join } from 'path';

197

198

export const actions = {

199

upload: async ({ request }) => {

200

const data = await request.formData();

201

const file = data.get('file');

202

203

if (!file || !(file instanceof File)) {

204

return fail(400, { error: 'No file uploaded' });

205

}

206

207

if (file.size > 1024 * 1024) { // 1MB limit

208

return fail(400, { error: 'File too large' });

209

}

210

211

const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];

212

if (!allowedTypes.includes(file.type)) {

213

return fail(400, { error: 'Invalid file type' });

214

}

215

216

try {

217

const buffer = Buffer.from(await file.arrayBuffer());

218

const filename = `${Date.now()}-${file.name}`;

219

const filepath = join('uploads', filename);

220

221

await writeFile(filepath, buffer);

222

223

return { success: true, filename };

224

} catch (error) {

225

return fail(500, { error: 'Upload failed' });

226

}

227

}

228

};

229

```

230

231

## Client-Side Integration

232

233

### Basic Form Handling

234

235

```svelte

236

<!-- src/routes/contact/+page.svelte -->

237

<script>

238

import { enhance } from '$app/forms';

239

240

export let form;

241

</script>

242

243

<form method="POST" use:enhance>

244

<input

245

name="name"

246

placeholder="Name"

247

value={form?.name ?? ''}

248

required

249

/>

250

251

<input

252

name="email"

253

type="email"

254

placeholder="Email"

255

value={form?.email ?? ''}

256

required

257

/>

258

259

<textarea

260

name="message"

261

placeholder="Message"

262

value={form?.message ?? ''}

263

required

264

></textarea>

265

266

<button type="submit">Send Message</button>

267

268

{#if form?.error}

269

<p class="error">{form.error}</p>

270

{/if}

271

272

{#if form?.success}

273

<p class="success">Message sent successfully!</p>

274

{/if}

275

</form>

276

```

277

278

### Enhanced Form with Loading State

279

280

```svelte

281

<script>

282

import { enhance } from '$app/forms';

283

284

export let form;

285

286

let loading = false;

287

</script>

288

289

<form

290

method="POST"

291

use:enhance={() => {

292

loading = true;

293

294

return async ({ result, update }) => {

295

loading = false;

296

297

if (result.type === 'success') {

298

// Optional: reset form or show success message

299

}

300

301

await update();

302

};

303

}}

304

>

305

<!-- form fields -->

306

307

<button type="submit" disabled={loading}>

308

{loading ? 'Sending...' : 'Send Message'}

309

</button>

310

</form>

311

```

312

313

### Multiple Actions

314

315

```svelte

316

<script>

317

export let data;

318

export let form;

319

</script>

320

321

<!-- Update user -->

322

<form method="POST" action="?/update">

323

<input name="name" value={data.user.name} />

324

<input name="email" value={data.user.email} />

325

<button type="submit">Update</button>

326

</form>

327

328

<!-- Delete user -->

329

<form method="POST" action="?/delete">

330

<button type="submit" onclick="return confirm('Are you sure?')">

331

Delete User

332

</button>

333

</form>

334

335

{#if form?.error}

336

<p class="error">{form.error}</p>

337

{/if}

338

```

339

340

## Types

341

342

### ActionFailure Interface

343

344

```typescript { .api }

345

interface ActionFailure<T = undefined> {

346

/** HTTP status code (400-599) */

347

status: number;

348

/** Data associated with the failure */

349

data: T;

350

}

351

```

352

353

### Action Function Type

354

355

```typescript { .api }

356

type Action<

357

Params = Record<string, string>,

358

OutputData = Record<string, any> | void

359

> = (event: RequestEvent<Params>) => Promise<OutputData> | OutputData;

360

361

type Actions<

362

Params = Record<string, string>,

363

OutputData = Record<string, any> | void

364

> = Record<string, Action<Params, OutputData>>;

365

```

366

367

### Action Result Types

368

369

```typescript { .api }

370

type ActionResult<

371

Success = Record<string, unknown> | undefined,

372

Failure = Record<string, unknown> | undefined

373

> =

374

| { type: 'success'; status: number; data?: Success }

375

| { type: 'failure'; status: number; data?: Failure }

376

| { type: 'redirect'; status: number; location: string }

377

| { type: 'error'; status?: number; error: any };

378

```

379

380

## Best Practices

381

382

1. **Always validate input**: Check form data before processing

383

2. **Preserve form data on failure**: Return submitted values so users don't lose their input

384

3. **Use appropriate status codes**: 400 for validation errors, 500 for server errors

385

4. **Provide helpful error messages**: Users should understand what went wrong

386

5. **Handle file uploads carefully**: Validate file types, sizes, and sanitize filenames

387

6. **Use progressive enhancement**: Forms should work without JavaScript

388

7. **Redirect after successful mutations**: Use 303 redirects after successful POST/PUT/DELETE

389

8. **Consider security**: Validate CSRF tokens, sanitize input, check permissions