# Workspace URL field

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

- Blocks: [organizations](https://bettersupabase.com/blocks/organizations.md), [reserved-slugs](https://bettersupabase.com/blocks/reserved-slugs.md)
- Kind: component
- Data: loaded on the server through the block's client
- Page: https://bettersupabase.com/recipes/workspace-url-field

## Install the blocks

```bash
npx better-supabase add organizations reserved-slugs
```

## Web (Next.js)

Add the shadcn components:

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

From `apps/examples/nextjs/src/features/organization/components/organization-form.tsx` (https://github.com/ScaleDockHQ/better-supabase/blob/main/apps/examples/nextjs/src/features/organization/components/organization-form.tsx):

```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>
  );
}
```

## Prompt

Paste this into a coding agent to build the recipe in your app.

### Web

```text
Build "Workspace URL field" in a React web app that uses shadcn/ui components. An organization form with a name and a URL slug, where reserved and taken slugs come back as field errors.

It uses the better-supabase `organizations` block (Organizations) with `reserved-slugs`. It loads its data on the server through the block's client.

Steps:

1. If the app doesn't have the blocks yet, add the SQL with `npx better-supabase add organizations reserved-slugs` and apply the migration.
2. Add the shadcn components the recipe uses: `npx shadcn add button field input`.
3. The recipe needs no glue: it calls the block's client on the server.
4. Write the component. Load the data on the server with the block's client, starting from the recipe's snippet (`apps/examples/nextjs/src/features/organization/components/organization-form.tsx`). Adapt it to the app's existing components, styling and data loading instead of copying it as is.
5. Check the result against the recipe and the block's docs: it typechecks, it reads the same block data, and it handles the loading and empty states the snippet handles.

Read the full code first:

- Recipe: https://bettersupabase.com/recipes/workspace-url-field.md
- Block docs: https://bettersupabase.com/docs/blocks/organizations
```
