Fields
Point Field
The point field captures geographic coordinates and stores them as an object with lat and lng number properties. By default, point fields are automatically synced to a spatial index powered by @convex-dev/geospatial, enabling efficient bounding-box and nearest-neighbor queries.
The underlying Convex validator is v.object({ lat: v.number(), lng: v.number() }), wrapped in v.optional() unless marked as required.
Setup
Point fields with spatial indexing (the default) require the @convex-dev/geospatial package:
1. Install the package
npm install @convex-dev/geospatial 2. Register in your Convex config
// convex/convex.config.ts
import { defineApp } from "convex/server";
import geospatial from "@convex-dev/geospatial/convex.config";
import vextro from "vextro/convex.config";
const app = defineApp();
app.use(geospatial);
app.use(vextro);
export default app; 3. Pass the component to your admin module
// convex/admin.ts
import { createVextroAdminModule } from "vextro/convex/admin";
import { components } from "./_generated/api";
const admin = createVextroAdminModule({
query,
mutation,
components,
geospatialComponent: components.geospatial,
collectionDefinitions: vextro.collectionDefinitions,
}); If you use point fields with spatialIndex: false on all of them, the geospatial package is not required.
Config options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
required | boolean | false | Makes the field required in the schema and admin UI | |
readOnly | boolean | false | Renders the field as non-editable | |
label | string | function | Field name | Custom label for the admin UI | |
description | string | Help text displayed below the field label | ||
showMap | boolean | true | Show an interactive map picker | |
defaultZoom | number | 10 | Default zoom level for the map | |
defaultCenter | { lat: number; lng: number } | Default map center when no value is set | ||
spatialIndex | boolean | true | Sync to @convex-dev/geospatial spatial index on create/update/delete | |
filterKeys | Record<string, string> | {} | Map of metadata key to top-level document field name for spatial query filtering | |
sortKey | string | Top-level document field name to use as the spatial index sort key | ||
condition | FieldCondition | Condition for showing or hiding this field | ||
sidebar | boolean | false | Place this field in the document sidebar | |
listColumn | boolean | false | Show as a default column in list views |
Example usage
import { f, defineVextroCollection } from "vextro";
export const stores = defineVextroCollection({
slug: "stores",
label: "Stores",
collectionType: "content",
tableName: "stores",
fields: {
name: f.text({ required: true }),
category: f.select({
required: true,
options: ["restaurant", "retail", "service"],
}),
rating: f.number({ min: 0, max: 5 }),
location: f.point({
required: true,
label: "Store Location",
showMap: true,
defaultZoom: 14,
defaultCenter: { lat: 39.8283, lng: -98.5795 },
description: "Click the map or enter coordinates manually",
// Spatial index with filterable metadata
filterKeys: { category: "category" },
sortKey: "rating",
}),
// Display-only coordinate — no spatial indexing
headquartersPin: f.point({
spatialIndex: false,
label: "HQ Coordinates",
description: "For display on the about page only",
}),
},
}); Stored data format
The point field stores coordinates as a plain object:
{
"lat": 27.9506,
"lng": -82.4572
} Spatial queries
For bounding-box queries, nearest-neighbor search, and other spatial operations using point field data, see the Geospatial Queries guide.
Admin options
When showMap is true (the default), the field renders an interactive map where editors can click to place a marker. The coordinate values update automatically. Manual lat/lng inputs are always available alongside the map for precise entry.
When showMap is false, only the manual latitude and longitude number inputs are shown. This is useful when map display is unnecessary or when embedding a map would add unwanted weight to the admin page.
The defaultCenter option sets the initial map viewport when the field has no value. Set this to the geographic center of your service area for a better editing experience.
Conditional display
deliveryPoint: f.point({
label: "Delivery Coordinates",
condition: { field: "fulfillmentType", equals: "delivery" },
showMap: true,
defaultZoom: 15,
})