# Workspace URL field

> An organization form with a name and a URL slug, where reserved and taken slugs come back as field errors.

Source: https://bettersupabase.com/docs/registry/ui-recipes/workspace-url-field

{/* Generated by scripts/build-recipes.ts from the example apps. Edit the examples or the script, then run `pnpm recipes`. */}

It loads its data on the server through the block's client; the [organizations](https://bettersupabase.com/blocks/organizations), [reserved-slugs](https://bettersupabase.com/blocks/reserved-slugs) blocks must be installed (`npx better-supabase add organizations reserved-slugs`). Its page on bettersupabase.com is [/recipes/workspace-url-field](https://bettersupabase.com/recipes/workspace-url-field).

Web, with shadcn components:

```bash
npx shadcn add button field input
```

```tsx title="apps/examples/nextjs/src/features/organization/components/organization-form.tsx"
"use client";

import { useActionForm } from "better-supabase/react";
import { useExtracted } from "next-intl";
import { useId } from "react";
import { toast } from "sonner";

import { Button } from "@/components/ui/button";
import {
  Field,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { useErrorMessage } from "@/lib/use-error-message";

import { updateOrganization } from "../organization-actions";

export function OrganizationForm({
  name,
  slug,
  canEdit,
}: {
  name: string;
  slug: string;
  canEdit: boolean;
}) {
  const t = useExtracted("organization");
  const fieldId = useId();
  const errorMessage = useErrorMessage();

  const form = useActionForm(updateOrganization, {
    resetOnSuccess: false,
    onSuccess: () => {
      toast.success(t("Organization saved"));
    },
  });

  const error =
    form.error === undefined
      ? null
      : form.error.kind === "conflict"
        ? t("That URL is taken. Pick another one.")
        : errorMessage(form.error);

  return (
    <form {...form.formProps} className="space-y-6">
      <fieldset disabled={!canEdit}>
        <FieldGroup>
          <Field>
            <FieldLabel htmlFor={`${fieldId}-organization-name`}>
              {t("Name")}
            </FieldLabel>
            <Input
              id={`${fieldId}-organization-name`}
              name="name"
              defaultValue={name}
              required
              minLength={2}
              maxLength={80}
            />
          </Field>
          <Field data-invalid={error ? true : undefined}>
            <FieldLabel htmlFor={`${fieldId}-organization-slug`}>
              {t("URL")}
            </FieldLabel>
            <Input
              id={`${fieldId}-organization-slug`}
              name="slug"
              defaultValue={slug}
              required
              pattern="[a-z0-9][a-z0-9\-]*[a-z0-9]"
              maxLength={48}
              aria-invalid={error ? true : undefined}
            />
            <FieldDescription>
              {t("Lowercase letters, numbers and dashes.")}
            </FieldDescription>
            {error ? <FieldError>{error}</FieldError> : null}
          </Field>
        </FieldGroup>
      </fieldset>
      {canEdit ? (
        <Button type="submit" disabled={form.pending}>
          {t("Save")}
        </Button>
      ) : (
        <p className="text-sm text-muted-foreground">
          {t("Only owners and admins can change the organization.")}
        </p>
      )}
    </form>
  );
}
```