# Generate a form (remote functions)
Generates a form using SvelteKit remote functions instead of sveltekit-superforms.
Tags: sveltekit, remote-functions, validation, zod

$ vela generate form --remote contact name:text email:email message:editor

## Tutorials

- [SvelteKit remote functions - a validated form without form actions](https://velastack.dev/tutorials/sveltekit-remote-functions-form)
- [Form actions vs remote functions in SvelteKit, and how to generate either](https://velastack.dev/tutorials/sveltekit-form-actions-vs-remote-functions)

## src/routes/(app)/contact/+page.svelte

```svelte
<script lang="ts">
  import { submitContactForm } from "./form.remote";
  import { Button } from "$lib/components/ui/button";
  import { Input } from "$lib/components/ui/input";
  import { Textarea } from "$lib/components/ui/textarea";
</script>

<section data-role="content">
  <div class="flex justify-between items-center mb-4">
    <h1 class="text-3xl font-bold tracking-tight">Contact</h1>
  </div>
  <div class="bg-card rounded-lg shadow-sm border p-4">
    <form {...submitContactForm}>
      <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
        <div class="space-y-2 col-span-1">
          <label for="name" class="text-sm font-medium">Name</label>
          <Input id="name" {...submitContactForm.fields.name.as("text")} />
          {#each submitContactForm.fields.name.issues() as issue}
            <p class="text-destructive text-sm">{issue.message}</p>
          {/each}
        </div>
        <div class="space-y-2 col-span-1">
          <label for="email" class="text-sm font-medium">Email</label>
          <Input
            id="email"
            {...submitContactForm.fields.email.as("text")}
            type="email"
          />
          {#each submitContactForm.fields.email.issues() as issue}
            <p class="text-destructive text-sm">{issue.message}</p>
          {/each}
        </div>
        <div class="space-y-2 col-span-2">
          <label for="message" class="text-sm font-medium">Message</label>
          <Textarea
            id="message"
            {...submitContactForm.fields.message.as("text")}
          />
          {#each submitContactForm.fields.message.issues() as issue}
            <p class="text-destructive text-sm">{issue.message}</p>
          {/each}
        </div>
      </div>

      <div class="mt-4 flex gap-2 border-t pt-4 -mx-4 px-4">
        <Button type="submit">Submit</Button>
      </div>
    </form>
  </div>
</section>
```

## src/routes/(app)/contact/form.remote.ts

```ts
import { form, getRequestEvent } from "$app/server";
import { setFlash } from "sveltekit-flash-message/server";
import { contactSchema } from "$lib/schemas/contact";

export const submitContactForm = form(contactSchema, async (data) => {
  const { cookies } = getRequestEvent();
  setFlash({ type: "toast", message: "Form posted successfully" }, cookies);
  return { success: true };
});
```

## src/routes/(app)/contact/server.test.ts

```ts
import { beforeEach, describe, expect, it } from "vitest";

describe("/contact", () => {
  beforeEach(async (context) => {
    await context.agent.authenticateUser();
  });

  describe("GET /contact", () => {
    it("should return a 200 status code", async (context) => {
      const response = await context.agent.get("/contact");
      expect(response.status).toBe(200);
    });
  });
});
```

## src/lib/schemas/contact.ts

```ts
import { z } from "zod";

export const contactSchema = z.object({
  name: z.string().optional(),
  email: z.email().optional(),
  message: z.string().optional(),
});
```
