URL field
f.url() is a string field for web addresses, a varchar(2048) with automatic URL-format validation and host/protocol-aware filter operators.
f.url() stores a web address. It produces a varchar(2048) column, derives a Zod schema that enforces a valid URL (z.string().url()), and exposes the string filter operators plus URL-aware ones (host, protocol) on the typed where clause. Reach for it whenever a field holds a link, a website, a webhook target, a canonical URL, and you want the format checked for free.
Prerequisites: read Fields first, this page covers only the
urltype. Thefproxy, the chain-modifier model, and shared modifiers like.required()/.localized()/.default()are taught there.
What it does
- Stores a URL string, a
varchar(2048)column by default (the larger default size accommodates long query strings), resizable via the constructor argument. - Validates the format automatically, the derived schema is
z.string().url(), so a malformed value is rejected at validation time. No.url()call needed. - Filters with URL-aware operators, every string operator (
eq,contains,ilike, …) plushost,hostIn, andprotocolfor matching the host or scheme of a URL. - Renders a URL input in the admin form.
Quick start
Use f.url() inside a .fields() callback. The positional argument is the max character length; chain modifiers to refine it.
import { collection } from "#questpie/factories";
export const links = collection("links").fields(({ f }) => ({
website: f.url().required(), // varchar(2048), NOT NULL, validated as a URL
webhook: f.url(500), // varchar(500), still URL-validated
}));That gives links a website column typed string on read, required on insert, rejected unless it parses as a URL, and filterable with the operators below, no schema or migration written by hand.
Constructor argument
f.url() takes one optional argument: the column's max character length.
| Call | Column | Notes |
|---|---|---|
f.url() | varchar(2048) | Default. maxLength defaults to 2048, wider than f.text()'s 255, since URLs run long. |
f.url(n) | varchar(n) | Sized varchar; n is also applied as a Zod .max(n). |
The resulting field data is always string. The derived Zod schema is z.string().url().max(maxLength), the .url() refinement is added unconditionally, so every value is validated as a URL regardless of the length you pass.
Chained methods
These methods are specific to url (on top of the shared modifiers every field has). Each returns a new immutable field, so they chain in any order.
| Method | Effect |
|---|---|
.min(n) | Minimum string length (Zod .min(n)). |
.max(n) | Maximum string length (Zod .max(n)). |
collection("integrations").fields(({ f }) => ({
endpoint: f
.url()
.required()
.min(12), // reject trivially short strings, in addition to URL-format validation
}));`.min()` / `.max()` here are STRING LENGTH, not value bounds
On a url field, .min(n) / .max(n) constrain the number of characters (they map to minLength / maxLength), exactly as on f.text(). On f.number() the same method names constrain the numeric value. Same names, different meaning, sized by the field's type.
URL validation is always on; tighten it with `.zod()`
The .url() refinement is appended automatically (derive-schema.ts:131), you don't add it and you can't turn it off through a field method. To go further (e.g. allow only https, or restrict hosts), use the .zod() escape hatch: f.url().zod((s) => s.refine((v) => v.startsWith("https://"))).
Filtering, operators
url uses the URL operator set (urlOps), which is the string operator set extended with three URL-specific operators. Every url field is filterable with these in a where clause.
The inherited string operators (operand string, or string[] for in / notIn):
| Operator | Operand | Matches |
|---|---|---|
eq / ne | string | Exact equal / not equal. |
in / notIn | string[] | Value is (not) in the list. |
like / notLike | string | SQL LIKE (case-sensitive, your own %). |
ilike / notIlike | string | Case-insensitive LIKE. |
contains | string | Substring (LIKE '%value%'). |
startsWith / endsWith | string | Prefix / suffix match. |
isNull / isNotNull | boolean | Column is (not) null. |
The URL-specific additions:
| Operator | Operand | Matches |
|---|---|---|
host | string | Rows whose URL contains ://value (case-insensitive), i.e. the given host. e.g. "example.com". |
hostIn | string[] | Rows matching any host in the list. |
protocol | string | Rows whose URL begins value:// (case-sensitive), i.e. the given scheme. e.g. "https". |
// All links pointing at a given host
const { docs } = await app.collections.links.find({
where: { website: { host: "questpie.com" } },
});
// Only secure (https) endpoints
const secure = await app.collections.links.find({
where: { webhook: { protocol: "https" } },
});`host`/`protocol` are pattern matches, not a parsed URL
These operators match against the raw stored string with LIKE/ILIKE, they don't parse the URL. host looks for ://value anywhere in the string (so it also matches a host that appears inside a path), and protocol is a case-sensitive prefix match on value://. They're convenient filters, not a strict URL parser.
For the full query language, combining field filters with AND / OR / NOT, pagination, and orderBy, see Fields → filtering. Relation filters and hydration are covered in Relations.
When to use it
f.url(), any field that holds a web address you want format-checked: a site URL, a webhook endpoint, a canonical link, an avatar URL, an external profile.- For a plain string with no URL validation (a path, a slug, an arbitrary identifier), use
f.text(). - For an email address, use
f.email(), the same string shape withz.string().email()validation anddomainoperators instead (see Email field). - To upload a file and store a link to it, reach for the Upload field (
f.upload()), or the collection-level.upload()builder, not aurlfield.
Multiple values
Chain .array() to store a list of URLs in a single jsonb column, and bound it with .minItems(n) / .maxItems(n). This is a shared modifier, not url-specific:
links: f.url().array().maxItems(5), // string[] of URLs stored as jsonbEach element is still validated as a URL.
TypeScript
A url field contributes a string to the generated row, insert, and where types, no annotation needed. After questpie generate, pull the shapes off the collection or the app types:
type Link = typeof links.$infer.select;
// ^? { id: string; website: string; webhook: string | null; ... }.required() makes the field non-null and required on insert; without it the column is nullable and the insert field optional. See Fields → inferred types for how modifiers flow into the generated types.
Related
- Fields, the
fproxy, the chain-modifier model, and the shared modifiers (.required(),.localized(),.default(),.array(), …). - Text field, the plain-string sibling and the
stringOpsoperator seturlextends. - Validation, the auto-derived Zod schema and the
.zod()escape hatch for stricter URL rules.