> ## Documentation Index
> Fetch the complete documentation index at: https://docs.frogbot.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# URL Field

> Store web addresses as text, check them on save and show them as links everywhere.

`urlField` returns a [Text field](https://docs.frogbot.ai/fields/text.md) that stores a web address as typed. Every save checks that the value is a web address, the admin panel shows it as a link that opens in a new tab, and agents and generated types are told what the text means. You don't name any components or write a validator.

```ts theme={"theme":"tokyo-night"}
import { urlField } from 'frogbot'
import type { CollectionConfig } from 'frogbot'

export const Companies: CollectionConfig = {
  slug: 'companies',
  fields: [
    { name: 'name', type: 'text', required: true },
    urlField({ name: 'website', label: 'Website' }),
  ],
}
```

The result is a plain `text` field, so it works anywhere a field does, including inside groups, arrays, tabs and blocks.

## Config options

| Option | Type | Default | Description |
| - | - | - | - |
| `name` | `string` | Required | Property name used in stored records and APIs. See [field names](https://docs.frogbot.ai/fields/overview.md#field-names). |
| `minLength` | `number` | — | Minimum length, checked by the built-in validator. |
| `maxLength` | `number` | Root `defaultMaxTextLength` (`40000`) | Maximum length, checked by the built-in validator. |
| `label` | `string \| Record<string, string> \| (({ i18n, t }) => string) \| false` | Generated from `name` | Label shown in the admin panel. Use an object keyed by language code for translations, or `false` to hide the label. |
| `required` | `boolean` | `false` | Rejects an empty value. |
| `unique` | `boolean` | `false` | Creates a unique database index for the field path. See [indexes](https://docs.frogbot.ai/database/indexes.md). |
| `index` | `boolean` | `false` | Creates a database index for faster queries. `unique` already creates one. |
| `localized` | `boolean` | `false` | Stores one value per configured locale. Ignored without [localization](https://docs.frogbot.ai/configuration/localization.md) or inside a localized parent. |
| `defaultValue` | `string` or `({ locale, req, user }) => value` | — | Value used when the field has no value. See [default values](https://docs.frogbot.ai/fields/overview.md#default-values). |
| `validate` | `Validate` | — | Runs after the built-in validation and the web address check, and adds to them rather than replacing them. See [validation](https://docs.frogbot.ai/fields/overview.md#validation). |
| `hooks` | `{ beforeValidate, beforeChange, afterChange, afterRead, beforeDuplicate }` | — | Arrays of `FieldHook` functions for each phase. See [field hooks](https://docs.frogbot.ai/hooks/fields.md). |
| `access` | `{ create, read, update }` | Allowed | `FieldAccess` functions for each operation. See [field access control](https://docs.frogbot.ai/access-control/fields.md). |
| `hidden` | `boolean` | `false` | Removes the field from API responses and the admin panel while keeping stored data. |
| `saveToJWT` | `boolean \| string` | `false` | In an auth-enabled collection, copies the value into the user's token. A string sets the token key. See [token data](https://docs.frogbot.ai/authentication/token-data.md). |
| `virtual` | `boolean \| string` | `false` | `true` skips database storage; a string reads a value through a relationship path. Virtual fields are read-only in the admin panel unless `admin.readOnly` is `false`. See [virtual fields](https://docs.frogbot.ai/fields/overview.md#virtual-field-configuration). |
| `disableDuplicate` | `boolean` | `false` | Leaves the value out when a document is duplicated, so the copy uses `defaultValue`. |
| `admin` | `object` | — | Admin-panel options. See [Admin options](#admin-options). |
| `custom` | `Record<string, any>` | — | Server-only metadata for your own code and plugins. Use `admin.custom` for data the browser also needs. |
| `jsonSchema` | `Array<({ jsonSchema }) => JSONSchema4>` | — | Functions that modify the JSON Schema used by [type generation](https://docs.frogbot.ai/typescript/generating-types.md) and agents. They run after the URL field's own entry. See [Agents and generated types](#agents-and-generated-types). |

A URL field holds one address, so `hasMany`, `minRows` and `maxRows` are not accepted. TypeScript rejects them, and setting any of them from JavaScript fails at startup with an error naming the field:

```text theme={"theme":"tokyo-night"}
urlField "website": hasMany is not supported
```

## Accepted addresses

Every save checks the value, whether it comes from the admin panel, the REST API, GraphQL, the Local API or an agent. A web address has one of two forms:

* **Full:** it starts with `http://` or `https://`, in any letter case, and has a host. No dot is needed, so `http://localhost:3000` is accepted.
* **Bare:** it has no scheme and starts with a letter or digit, and its host contains a dot. A port, path, query and fragment are allowed.

Any value with a space or a control character is rejected, and so is every other scheme.

| Value | Result |
| - | - |
| `https://example.com`, `HTTPS://Example.com/Path`, `http://localhost:3000` | Accepted, full |
| `example.com`, `www.example.com/pricing`, `example.com:8080/x`, `192.168.0.1:8080` | Accepted, bare |
| `javascript:alert(1)`, `data:text/html,hi`, `mailto:a@b.com`, `ftp://example.com`, `tel:5551234567` | Rejected: not a web address |
| `a b.com`, `https://exa mple.com` | Rejected: contains a space |
| `localhost:3000`, `localhost` | Rejected: a bare host needs a dot. Use `http://localhost:3000`. |
| `user@example.com`, `user:pass@example.com` | Rejected: a bare address can't contain a user name |
| `//example.com`, `/pricing`, `https://` | Rejected |

A rejected value fails with:

```text theme={"theme":"tokyo-night"}
Enter a web address such as https://example.com.
```

An empty value skips the check; set `required` to reject it.

## Admin options

URL fields support the standard [field admin options](https://docs.frogbot.ai/fields/overview.md#admin-options), plus the Text field's input options:

| Option | Type | Default | Description |
| - | - | - | - |
| `placeholder` | `string \| Record<string, string>` | — | Placeholder text. Use an object keyed by language code for translations. |
| `autoComplete` | `string` | — | Value of the input's `autocomplete` attribute. |
| `rtl` | `boolean` | Follows the locale | `true` forces right-to-left text. `false` keeps left-to-right text in a right-to-left locale. When unset, right-to-left locales render right-to-left. |

FrogBot marks the field as a URL in `admin.custom.frogbot.kind`. Your own `admin.custom` keys, and other keys under `admin.custom.frogbot`, are kept.

## Display and editing

A URL shows as typed, as a link that opens in a new tab without giving the new page access to the admin window. A full address opens as stored, and a bare one opens with `https://` in front, so `example.com/pricing` opens `https://example.com/pricing`. A long address is cut to one line with an ellipsis, and hovering shows the whole address.

Links work in the [List](https://docs.frogbot.ai/admin/overview.md), on [Board](https://docs.frogbot.ai/admin/collection-board.md) cards and in [Calendar](https://docs.frogbot.ai/admin/collection-calendar.md) events. Clicking a link on a card opens only the link, not the card. In the List's first column, which already links to the document, the address shows as text inside the document link.

An empty value shows nothing: no placeholder and no dash. A stored value that isn't an accepted address, for example one saved before the field became a URL field, shows as plain text and is never turned into a link. If a URL field is the first List column, a row with an empty value has no link to open the document from that column.

In the document view, a URL field uses the normal text input, and a read-only field shows the address in a read-only text input.

## Stored value

Addresses are stored exactly as typed. The REST API, GraphQL and the Local API return the stored text, and the generated TypeScript type is the same as a Text field's, such as `website?: string | null`.

> [!WARNING]
>
> Stored values may lack a scheme, such as `example.com`. Code that opens or follows them should handle both forms. [`getUrlHref`](#formatting-in-your-code) turns either form into a full address.

## Agents and generated types

A URL field describes itself to agents using FrogBot's [MCP tools](https://docs.frogbot.ai/plugins/mcp.md) and in [generated types](https://docs.frogbot.ai/typescript/generating-types.md):

```text theme={"theme":"tokyo-night"}
Web address, for example https://example.com or example.com
```

With a string `admin.description`, or a translated one with an `en` entry, the URL text is added after it in parentheses. The admin panel still shows only your description.

```ts theme={"theme":"tokyo-night"}
urlField({ name: 'website', admin: { description: 'Company home page' } })
```

```text theme={"theme":"tokyo-night"}
Company home page (web address, for example https://example.com or example.com)
```

A function `admin.description` isn't copied, so the URL text stands alone. The text is English only. Generated types show the text as a comment above the property, and virtual fields don't appear in MCP tool inputs.

To change the text, add your own `jsonSchema` entry. It runs after the URL field's entry, so it can adjust or replace the description:

```ts theme={"theme":"tokyo-night"}
urlField({
  name: 'website',
  jsonSchema: [({ jsonSchema }) => ({ ...jsonSchema, description: 'Full https:// address' })],
})
```

```text theme={"theme":"tokyo-night"}
Full https:// address
```

## Formatting in your code

`getUrlHref` is the rule URL fields use for both the save check and the links. Import it from `frogbot/fields`, which is safe to use in browser code:

```tsx theme={"theme":"tokyo-night"}
'use client'

import { getUrlHref } from 'frogbot/fields'

export function Website({ value }: { value: string | null }) {
  const href = getUrlHref({ value })

  return href ? <a href={href}>{value}</a> : <span>{value}</span>
}
```

| Argument | Type | Default | Description |
| - | - | - | - |
| `value` | `unknown` | Required | The stored address. |

`getUrlHref` returns the address to link to: a full address as stored, or `https://` followed by a bare address. It returns `undefined` for an empty value and for any value that isn't an [accepted address](#accepted-addresses).

## Custom components

The URL cell is `UrlCell` from `@frogbotai/ui/admin`. Your own `admin.components.Cell` replaces it, and `admin.components.Field` and other components such as `Label`, `Description`, `Error`, `beforeInput` and `afterInput` work as they do on a Text field.

A custom Cell can draw the standard link inside its own markup with [`FieldCell`](https://docs.frogbot.ai/fields/overview#cell), as shown for [Money fields](https://docs.frogbot.ai/fields/money.md#custom-components). As with any custom Cell, it appears only in the List view. Board cards and Calendar events keep the standard link.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.