---
title: "Venues and near-me search — Events for Laravel | Roundly"
description: "Reusable venues with addresses, coordinates and timezones, who may edit them, opt-in geocoding and an exact near-me distance in SQL."
url: https://roundly-consulting.com/open-source/docs/events-for-laravel/venues
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Events for Laravel](https://roundly-consulting.com/open-source/docs/events-for-laravel.md)

# Venues and near-me search

A venue is reusable across events, and its timezone decides how every wall-clock time of its events is read:

```php
$venue = Events::venues()->make(['en' => 'City Park', 'sk' => 'Mestský park'])
    ->address(street: 'Námestie slobody', city: 'Bratislava', region: 'BA', postalCode: '811 06', country: 'SK')
    ->formattedAddress('Námestie slobody, 811 06 Bratislava')   // optional; else built from the parts
    ->coordinates(48.1530, 17.1130)                             // or a Coordinates
    ->timezone('Europe/Bratislava')
    ->capacity(500)
    ->ownedBy($team)                                            // "my venues"
    ->meta(['floor' => 1])                                      // private: never in API output
    ->by($user)                                                 // checks the venue policy
    ->create();
```

A name is required (per locale), the country is an ISO 3166-1 alpha-2 code (upper-cased), the timezone a valid IANA name (else events.venues.default\_timezone, else the app’s), coordinates within range and capacity ≥ 0 — informational, since the event’s own capacity counts seats. Blank address parts are stored as null.

## Updating and the other operations

```php
use RoundlyConsulting\Events\DataTransferObjects\AddressData;
use RoundlyConsulting\Events\DataTransferObjects\UpdateVenueData;
use RoundlyConsulting\Events\DataTransferObjects\VenueData;

Events::venues()->by($user)->update($venue, new UpdateVenueData(capacity: 600));

// In PHP an address is one value: it replaces all six address columns, so pass every part you keep.
// (The API's PATCH changes each address field on its own.)
Events::venues()->update($venue, new UpdateVenueData(address: new AddressData(
    street: 'Hlavná 1', city: 'Košice', postalCode: '040 01', country: 'SK',
)));

// A DTO may carry its own actor; on a by() accessor it is that actor (another one is refused).
Events::venues()->create(new VenueData(name: 'Studio', owner: $team, actor: $user));
Events::venues()->by($user)->create(new VenueData(name: 'Studio', owner: $team));   // the same check
```

```php
$venues = Events::venues()->by($user);   // checks the VenuePolicy on every following write

$venues->geocode($venue);               // look the coordinates up now (only missing ones unless force: true)
$venues->delete($venue);                // soft; refused (venue_in_use) while an unfinished event uses it
$venues->restore($venue);

Events::venues()->find(7);
Events::venues()->ownedBy($team)->get();          // an organizer's own list
Events::venues()->near($here, km: 10)->get();     // venues within 10 km

$venue->coordinates;            // ?Coordinates
$venue->formattedAddress();     // the stored one-line address, else the parts joined
```

- update() changes only the fields given. A new name, address or coordinates raise the calendar SEQUENCE of the venue’s announced events that are not over, so subscribed calendars take the new LOCATION and GEO.
- delete() is a soft delete, refused while a draft, scheduled, postponed or rescheduled event uses the venue. Past events keep their trashed venue ($event->venue includes trashed venues) and the API renders it with deleted: true.

## Who may change a venue

Venues are physical places: any organizer may hold an event at any venue, owned or shared. Ownership decides only who edits it:

| Venue | Created by | Updated, geocoded, deleted, restored by |
| --- | --- | --- |
| Owned (ownedBy($team)) | Someone the owner allows (allowsEventManagementBy(); for a plain model, the model itself), or an admin. | The owner’s approved users and admins. |
| Shared (no owner) | Admins and holders of events.authorization.abilities.shared\_venues. | The same. |

Policies\\VenuePolicy has viewAny and view (anyone), create (authenticated, plus events.authorization.abilities.venues when set), update, delete, restore and geocode (the rules above) and manageShared (admins and abilities.shared\_venues; null means admins only). Swap it with events.authorization.venue\_policy. Over the API a venue created without an owner is the caller’s own — shared: true asks for a shared one. Trusted PHP (no by()) is never checked, so seeders and imports still create shared venues.

## Geocoding

Geocoding goes through the address pipeline of [Geolocation for Laravel](https://roundly-consulting.com/open-source/docs/geolocation-for-laravel.md), and only when you ask for it:

- \->geocode() on the builder, or Events::venues()->geocode($venue) — now.
- events.venues.geocode\_on\_create = on — every new venue without coordinates, queued after commit (Jobs\\GeocodeVenueJob). A rate limit releases the job for a retry after the provider’s retryAfterSeconds(); a deleted venue is skipped.
- php artisan events:geocode in bulk, and POST /api/event-venues/{venue}/geocode, which always re-geocodes.

```bash
php artisan events:geocode                 # venues without coordinates (--missing, the default)
php artisan events:geocode --venue=7       # one venue (skipped when it has coordinates, unless --force)
php artisan events:geocode --force         # every venue again
php artisan events:geocode --chunk=200     # venues per batch (default 100)
```

The lookup sends formattedAddress() to geolocation’s locateAddress(); a venue without any address is a failure. Only missing coordinates are filled unless forced, and the timezone is never touched. A failure keeps the venue and fires VenueGeocodingFailed — also when the pipeline returns its trailing default location, which would put every venue on the same spot. Success fires VenueGeocoded.

## Near me

```php
use RoundlyConsulting\Geolocation\DataTransferObjects\Coordinates;
use RoundlyConsulting\Geolocation\Facades\Geolocation;

$here = Geolocation::locateRequest($request)?->coordinates()   // the visitor's IP — see Privacy
    ?? new Coordinates(48.1486, 17.1077);

Events::browse($viewer)
    ->upcoming()
    ->near($here, km: 25)        // a bounding box on the venue's indexed coordinates, then the exact distance
    ->withDistance($here)        // adds distance_km (null for an event without a venue)
    ->orderByDistance($here)     // nearest first; events without a venue last in either direction
    ->paginate(20);
```

- km must be in (0, events.geo.max\_radius\_km\] (500); omitted means events.geo.default\_radius\_km (25). Online-only events never match near().
- withDistance() keeps the existing select (events.\* when there is none); its bindings sit in the select bucket and the sort in the order bucket, so pagination counts stay correct on every engine. The scopes take named arguments — near($here, km: 25).
- The distance is the same great-circle formula as geolocation’s Coordinates::distanceTo() (R = 6371.0 km, clamped so antipodal points never error), so SQL and PHP agree within a metre on MySQL and PostgreSQL. SQLite — tests only — uses a registered function, events\_distance\_km().
- Geolocation::locateRequest($request) sends the visitor’s IP address to geolocation’s IP providers — mention it in your privacy notice, or ask for the location instead.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/events-for-laravel.md)
