Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,40 @@ jobs:
- name: Run ESLint (astro-payload-richtext-lexical)
run: cd astro-payload-richtext-lexical && pnpm lint

test-geocoding:
runs-on: ubuntu-latest
needs: format
name: test-geocoding

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Setup environment variables
run: |
echo "PAYLOAD_SECRET=test-secret-not-for-production" > geocoding/dev/.env
echo "SQLITE_URL=file:./payload-test.db" >> geocoding/dev/.env
echo "NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=test-api-key" >> geocoding/dev/.env

- name: Setup pnpm
uses: pnpm/action-setup@v5
with:
version: ^9.0.0

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install:all

- name: Run tests
run: cd geocoding && pnpm test:sqlite
env:
PAYLOAD_DATABASE: sqlite

test-pages-localized:
runs-on: ubuntu-latest
needs: format
Expand Down
6 changes: 6 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
"options": {
"requirePragma": true
}
},
{
"files": "*.md",
"options": {
"parser": "markdown"
}
}
]
}
9 changes: 8 additions & 1 deletion geocoding/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.3.0

- **BREAKING**: Replaced `react-google-places-autocomplete` with the Google Maps Places API (New) `AutocompleteSuggestion` API. This resolves the deprecation warning for `google.maps.places.AutocompleteService` (deprecated as of March 2025). The `react-google-places-autocomplete` dependency has been removed.
- **BREAKING**: The **Places API (New)** must be enabled in your Google Cloud project. Enable it at: https://console.developers.google.com/apis/api/places.googleapis.com/overview
- **BREAKING**: The `_googlePlacesData` JSON field has been renamed to `_meta` and now stores a flat `{ name, formattedAddress, googlePlaceId, types }` object instead of the previous `react-google-places-autocomplete` format. Existing documents will need to be migrated — see [`migrations/migrate-to-0.3.0.ts`](./migrations/migrate-to-0.3.0.ts).
- **BREAKING**: The `geoDataFieldOverride` config option has been renamed to `locationMetaOverride`.

## 0.2.0

- **BREAKING**: The Google Maps API key is now a required plugin configuration option:
Expand All @@ -12,7 +19,7 @@ plugins: [payloadGeocodingPlugin({})]
plugins: [
payloadGeocodingPlugin({
googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!,
})
}),
]
```

Expand Down
89 changes: 78 additions & 11 deletions geocoding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ plugins: [
To use this plugin, you'll need a Google Maps API key. To get one, follow these steps:

1. Set up a Google Cloud account and create a project
2. Enable the Maps JavaScript API in your Google Cloud project
3. Create an API key with `Maps JavaScript API`, `Places API` and `Geocoding API` access
2. Enable the **Maps JavaScript API** and the **Places API (New)** in your Google Cloud project
3. Create an API key with access to these APIs
4. Pass the API key to the plugin configuration (as shown above).

Note: Since this API key is exposed to the frontend (Payload Admin panel), it is strongly recommended to restrict its usage by setting up domain restrictions and only enabling the `Maps JavaScript API`, `Places API`, and `Geocoding API` in the Google Cloud Console under API Keys & Credentials
Note: Since this API key is exposed to the frontend (Payload Admin panel), it is strongly recommended to restrict its usage by setting up domain restrictions in the Google Cloud Console under API Keys & Credentials.

## Usage

Expand All @@ -44,41 +44,108 @@ geocodingField({
})
```

This will add a `location_googlePlacesData` JSON field to the collection. This field will store the raw geocoding data from the Google Places API.
This will add a `location_meta` JSON field to the collection that stores metadata about the selected location (display name, formatted address, and Google Place ID (`googlePlaceId`)).

If needed you can adjust the `location_googlePlacesData` field name by passing a `name` property to the `geocodingField` function.
You can customize the metadata field by passing a `locationMetaOverride` option:

```ts
geocodingField({
pointField: {
name: 'location',
type: 'point',
},
geoDataFieldOverride: {
label: 'JSON Geodata Field',
locationMetaOverride: {
label: 'Location Metadata',
access: {
read: () => true,
update: () => true,
create: () => true,
},
admin: {
readOnly: false,
description: 'This field stores the geocoding data from the Google Places API.',
},
},
}),
```

## Usage with AI Agents / API

The default UI-based autocomplete requires a browser, which makes it unusable for AI agents and other API consumers. The plugin provides two server-side mechanisms to solve this.

### Geocoding Search Endpoint

The plugin registers a `GET /api/geocoding-plugin/search` endpoint that geocodes addresses server-side. It is authenticated by default (requires a logged-in user), and supports a custom access function:

```ts
plugins: [
payloadGeocodingPlugin({
googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!,
// Optional: customize who can access the endpoint
geocodingEndpoint: {
access: ({ req }) => Boolean(req.user),
},
}),
]
```

An agent can then search for locations and use the results to populate fields:

```bash
# 1. Search for an address
GET /api/geocoding-plugin/search?q=Alexanderplatz,+Berlin

# Response:
{
"results": [
{
"name": "Alexanderplatz",
"formattedAddress": "Alexanderplatz, 10178 Berlin, Germany",
"googlePlaceId": "ChIJp1l4uWBRqEcR2SPNRBMhtAI",
"location": { "lat": 52.5219, "lng": 13.4132 },
"types": [...]
}
]
}

# 2. Use the result to create/update a document
POST /api/pages
{
"title": "My Page",
"location": [13.4132, 52.5219],
"location_meta": {
"name": "Alexanderplatz",
"formattedAddress": "Alexanderplatz, 10178 Berlin, Germany",
"googlePlaceId": "ChIJp1l4uWBRqEcR2SPNRBMhtAI",
"types": ["point_of_interest", "establishment"]
}
}
```

### Server-Side Address Geocoding (beforeChange Hook)

Every `geocodingField` automatically includes a hidden `{fieldName}_address` text field. When an address string is submitted via the API, a `beforeChange` hook geocodes it server-side and populates the point and meta fields.

An agent can simply submit an address string — the coordinates and metadata are resolved automatically:

```bash
POST /api/pages
{
"title": "My Page",
"location_address": "Alexanderplatz, Berlin"
}
```

The hook geocodes the address, sets the `location` point field to `[lng, lat]`, and populates `location_meta` with the location metadata.

## About this plugin

This plugin uses the [react-google-places-autocomplete](https://www.npmjs.com/package/react-google-places-autocomplete) library to provide a Select/Search input for finding an address. The result of the Google Places API request is stored in a JSON field and the coordinates are stored in a Point Field.
This plugin uses the Google Maps Places API (New) `AutocompleteSuggestion` API to provide a search input for finding an address. The selected location's metadata is stored in a JSON field and the coordinates are stored in a Point Field.

## Roadmap

> ⚠️ **Warning**: This plugin is actively evolving and may undergo significant changes. While it is functional, please thoroughly test before using in production environments.

- Use the native Payload `SelectField` instead of the field provided by `react-google-places-autocomplete`
- Extend the field config to accept `GooglePlacesAutocomplete` options like debounce time, API options, etc.
- Extend the field config to accept options like debounce time, API options, etc.
- Add support for other geocoding services (Mapbox, HERE, etc.)

Have a suggestion for the plugin? Any feedback is welcome!
Expand Down
3 changes: 2 additions & 1 deletion geocoding/dev/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -370,4 +370,5 @@ $RECYCLE.BIN/
/media
*.db
tsconfig.tsbuildinfo
/dist
/dist
src/test/databaseAdapter.ts
16 changes: 11 additions & 5 deletions geocoding/dev/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "payload-plugin-test-app",
"description": "A test app for the plugin",
"name": "payload-geocoding-plugin-test-app",
"description": "A test app for the geocoding plugin",
"version": "0.0.1",
"license": "MIT",
"type": "module",
Expand All @@ -12,14 +12,19 @@
"start": "cross-env NODE_OPTIONS=--no-deprecation next start",
"format": "prettier --write src",
"payload": "payload",
"generate:types": "payload generate:types",
"generate:types": "cross-env PAYLOAD_DATABASE=sqlite payload generate:types",
"generate:schema": "payload-graphql generate:schema",
"generate:importmap": "payload generate:importmap"
"generate:importmap": "payload generate:importmap",
"test": "pnpm test:sqlite",
"test:sqlite": "cross-env PAYLOAD_DATABASE=sqlite vitest run",
"test:watch": "vitest watch"
},
"dependencies": {
"@jhb.software/payload-geocoding-plugin": "workspace:*",
"@payloadcms/db-mongodb": "^3.81.0",
"@payloadcms/db-sqlite": "^3.81.0",
"@payloadcms/next": "^3.81.0",
"@payloadcms/richtext-lexical": "3.81.0",
"@payloadcms/ui": "^3.81.0",
"next": "15.4.11",
"payload": "^3.81.0",
Expand All @@ -30,6 +35,7 @@
"copyfiles": "^2.4.1",
"cross-env": "^10.1.0",
"dotenv": "^17.4.1",
"typescript": "5.9.3"
"vite": "^8.0.0",
"vitest": "^4.1.0"
}
}
30 changes: 0 additions & 30 deletions geocoding/dev/plugin.spec.ts

This file was deleted.

Loading