Locations — Reference
The Locations surface is the physical-location plane for an SGEN site — the source of truth for a multi-location business's storefronts, branches, clinics, dealerships, restaurants, satellite offices, or service areas. It owns the location records themselves (each one a named place with an address, map coordinates, opening hours, and contact details), the per-location service offerings and staff rosters, the map-rendering configuration that lets a "find us" page draw the locations on a tile map, and the structured data feed that hands the location set to search engines for local-business rich results.
This page is a reference for platform engineers and integrators who need to understand the surface before extending it, scripting against it, or migrating a legacy multi-location Google Business Profile set into SGEN. Customer-facing walkthroughs for adding a location and editing opening hours live in the customer docs set; this page describes the shape of the surface, not the editorial flow.
Overview
Locations live under the Locations module in SG-Admin. The module renders four primary views — the locations list, the location create / edit form, the per-location staff and services panel, and the map view (locations laid out on an interactive tile map) — and exposes a write surface for create, update, duplicate, bulk import, soft delete, restore, permanent delete, regenerate structured-data feed, and CSV export.
The module is paired by convention: one half renders the views and prepares the data (location list, detail view, staff roster, services list, map data, feed payload), the other half handles writes and returns AJAX responses (create, update, hours change, staff assignment, delete). The pairing matches the broader SG-Admin convention used across Pages, Posts, Forms, Media, Users, and Events.
Where it lives in SG-Admin:
- Sidebar: SG-Admin → Locations
- URL prefix:
/sg-admin/locations/ - View templates:
application/views/Admin/Locations/
Locations are paired with the Pages module: every active location optionally generates a location-specific landing page at /locations/ that renders the anchor record, the hours, the staff, the services, and the gallery in a templated layout. The landing page is auto-created on location create but can be edited as a normal page record afterwards.
┌──────────────────────────────────────────────────────────────────────┐│ SG-Admin → Locations [+ New location]│├──────────────────────────────────────────────────────────────────────┤│ Name City Phone Staff Status ││ ───────────────────── ───────────── ───────────── ────── ─────││ Main store Manila +63 2 8000… 12 active││ Quezon City branch Quezon City +63 2 8500… 9 active││ Makati branch Makati +63 2 8888… 7 active││ Cebu satellite Cebu City +63 32 4100… 4 active││ Pop-up — May launch Various — 3 draft ││ Closed branch (Q1) Pasay — 0 trash ││ ││ [⋯ Edit] [⋯ Hours] [⋯ Staff] [⋯ Services] [⋯ Map] [⋯ Delete] │└──────────────────────────────────────────────────────────────────────┘Actions
The Locations surface exposes the following operations. Each is described by what it does to the data and to the public-facing rendering, not by its internal method name.
List and search locations
Returns the location records on the site, paginated, with name, city, phone, staff count, and status columns. Supports column sort, free-text filter across name and address, and status filter (active / draft / trash). Map view is a parallel layout of the same records, pinned on a tile map for visual confirmation of geographic spread.
Create location
Opens an empty location form. Required fields at minimum: name, address, status. Optional fields cover map coordinates (auto-geocoded from address on save if left blank), contact phone, contact email, primary service category, cover image, gallery, region grouping, time zone, and structured-data overrides. On submit, the surface validates uniqueness of the slug, geocodes the address if coordinates are blank, persists the record, generates the location landing page at /locations/, and returns the new identifier.
Edit location
Loads an existing location into the same form shape used for create, pre-populated. Submit replaces the stored anchor record. Address edits trigger a re-geocode if coordinates are still derived from the address; manually set coordinates are preserved across address edits.
Slug changes are reversible inside a 30-day window. The prior slug is held in a redirect-style alias table so the old /locations/ URL resolves to the new page with a 301. After 30 days the prior slug is released.Edit opening hours
A focused editor scoped to the per-day schedule. Each day of the week carries open/closed flag plus open-time and close-time windows. Multi-window days (open 09:00–13:00 and 17:00–21:00) are supported. Holiday overrides are dated entries that replace the standard schedule for a specific date — used for public holidays, special events, and one-off closures.
Manage staff
Lists the operators assigned to the location, their role at that location (manager, lead, specialist, support), their working schedule (subset of the location's opening hours), and their public-facing display. Add, edit, reorder, and remove operations are available. Staff records reference user IDs from the Users module, so renaming an operator updates every location they are assigned to.
Manage services
Lists the service offerings available at the location — service name, description, duration, price, available-at-this-location flag, and per-service photo. Services can be defined globally (under Settings → Services) and then enabled per-location, or defined location-specific. Used by the "what we offer here" section of the location landing page and by any booking flow that filters services by location.
Bulk import locations
Accepts a CSV of locations and creates the records in one operation. Each row maps to the location form fields. Validation runs per row: slug collisions report as "skip", address geocoding failures report as "warn — coordinates missing", missing required fields report as "skip — missing field". The output is a per-row success / skip / warn summary.
Soft delete
Marks the location as trashed. Trashed locations disappear from the public locations directory, the map view, the structured-data feed, and the staff-assignment surface. The location landing page returns a 410 response. Staff and service attachments remain in place — they re-emerge if the location is restored.
Restore
Returns a trashed location to its prior status. The landing page resumes responding, the location reappears on public surfaces, and attachments rebind without manual reconfiguration.
Permanent delete
Hard-removes the location record. Available only after a soft delete. The landing page is removed from the Pages module entirely (not trashed). Staff assignments at the location dissolve; the user records themselves are untouched. The 301 redirect from the old landing-page URL drops; subsequent requests return a 410.
Regenerate structured-data feed
The Locations module publishes a structured-data feed (LocalBusiness per schema.org) that search engines crawl for local-business rich results. The regenerate action forces a fresh feed build. Under normal operation the feed regenerates on every location write, but a force-regenerate is useful after bulk imports or after a structured-data override is changed site-wide.
Export locations CSV
Produces a CSV of locations for an offline pass — backup, audit, or migration. Columns: name, slug, address, latitude, longitude, phone, email, opening hours (encoded per-day), services, staff count, status. Reciprocal to the bulk-import format — a CSV exported here can be re-imported elsewhere.
Set primary location
A single-write action that flags one location as primary. The primary location surfaces as the default in single-location contexts (single contact phone in the footer, single address in the page metadata, single map pin on a "contact us" page that doesn't itemize all locations).
Data model
A location record carries the following fields. Field names below are the conceptual shape — the on-disk column names match closely but are not contractually stable across releases.
| Field | Type | Notes |
|---|---|---|
id | integer | Primary key. Stable across edits. |
name | string | Display name. Unique within the active set. |
slug | string | URL segment under /locations/. Unique across all locations. |
address_line_1 | string | Required. |
address_line_2 | string | Optional (suite, unit, floor). |
city | string | Required. |
region | string | State, province, or admin region. |
postal_code | string | Optional. |
country | string | ISO 3166-1 alpha-2 code. |
latitude | decimal | Auto-geocoded from address on save if blank. |
longitude | decimal | Same. |
contact_phone | string | Optional. E.164 format preferred. |
contact_email | string | Optional. |
timezone | string | IANA time zone identifier. Defaults to site primary. |
region_group | string | Optional. Groups locations into clusters for filtered map views. |
primary_flag | boolean | Exactly one record carries true at any time. |
cover_image_id | integer | References a Media record. |
gallery | JSON | Array of Media IDs. |
landing_page_id | integer | Foreign key to the auto-generated page. |
structured_data_overrides | JSON | Per-location schema.org overrides. |
status | enum | draft, active, trash. |
created_at | timestamp | Immutable. |
updated_at | timestamp | Touched on save. |
| Field | Type | Notes |
|---|---|---|
id | integer | Primary key. |
location_id | integer | Foreign key to the location. |
day_of_week | enum | mon through sun. |
is_open | boolean | True for open, false for closed. |
windows | JSON | Array of {open, close} time pairs (multi-window days). |
| Field | Type | Notes |
|---|---|---|
id | integer | Primary key. |
location_id | integer | Foreign key to the location. |
effective_date | date | The date the override applies. |
is_open | boolean | True for special open, false for special closed. |
windows | JSON | Replacement time pairs for that date. |
label | string | Optional. Renders on the public hours block ("Christmas day", "Anniversary"). |
| Field | Type | Notes |
|---|---|---|
id | integer | Primary key. |
location_id | integer | Foreign key to the location. |
user_id | integer | Foreign key to the user record. |
role_at_location | string | Manager, lead, specialist, support. |
public_display | boolean | True if the staff member appears on the public landing page. |
sort_order | integer | Display order on the landing page. |
status = trash is the soft-deleted state. Trashed locations retain their hours, holiday overrides, staff assignments, and gallery. Permanent delete cascades to the hours records and the staff assignments (the user records themselves are untouched).Slug uniqueness: unique across all locations. Collisions with Pages or Blogs slugs surface as warnings at save time — the locations directory at /locations is reserved as a Locations-owned prefix.
Primary flag. Exactly one location carries primary_flag = true at any time. Setting a new primary clears the prior one in the same transaction. If the primary is soft-deleted, the next active location by creation order is promoted automatically; the auto-promotion emits an Activity Log entry.
Structured-data overrides. The base schema.org LocalBusiness payload is generated from the anchor record + hours. Per-location overrides let an operator specialize the payload — for example, marking a specific location as a Restaurant subtype instead of the site-wide default LocalBusiness. Overrides are JSON-merged, not replaced wholesale.
LOCATION RECORD RELATED RECORDS┌─────────────────────────┐│ id │ ──────► OPENING_HOURS (one row per day)│ name, slug │ ──────► HOLIDAY_OVERRIDES (date-specific)│ address, lat/lng │ ──────► STAFF_ASSIGNMENTS (per user)│ contact phone / email │ ──────► SERVICES_AVAILABLE (join)│ region_group │ ──────► PAGE (auto-generated landing)│ primary_flag │ ──────► MEDIA (cover + gallery)│ structured_data_over... ││ status, timestamps │ ◄────── STRUCTURED-DATA FEED└─────────────────────────┘ (regenerated on write)on permanent delete:├─ OPENING_HOURS cascade-deleted├─ HOLIDAY_OVERRIDES cascade-deleted├─ STAFF_ASSIGNMENTS dissolved (users untouched)├─ SERVICES_AVAILABLE dissolved (services untouched)└─ LANDING_PAGE removed from Pages moduleon primary_flag delete:└─ next active location by creation orderauto-promoted to primaryPermissions
Access to the Locations surface is gated at two layers.
Layer 1 — admin gate. Every action under SG-Admin passes through the standard admin access check at request entry. Unauthenticated requests never reach the Locations surface. Public surfaces (the locations directory, the per-location landing page, the structured-data feed) are served without authentication but only render status = active records.
Layer 2 — per-action capability. Within SG-Admin, each Locations action checks a capability on the calling operator's role. The default role configuration ships with three roles — Administrator, Editor, Viewer — and a Location Manager role is a common custom addition on multi-location businesses where local staff manage their own location's hours and services. The capability map is:
| Capability | Administrator | Editor | Location Manager | Viewer |
|---|---|---|---|---|
| List locations | ✔ | ✔ | ✔ | ✔ |
| Create location | ✔ | ✔ | — | — |
| Edit location (any) | ✔ | ✔ | — | — |
| Edit location (own) | ✔ | ✔ | ✔ | — |
| Edit opening hours (own) | ✔ | ✔ | ✔ | — |
| Manage staff (own) | ✔ | ✔ | ✔ | — |
| Manage services (own) | ✔ | ✔ | ✔ | — |
| Bulk import | ✔ | — | — | — |
| Soft delete | ✔ | ✔ | — | — |
| Restore | ✔ | ✔ | — | — |
| Permanent delete | ✔ | — | — | — |
| Regenerate feed | ✔ | ✔ | — | — |
| Export CSV | ✔ | ✔ | — | — |
| Set primary | ✔ | — | — | — |
Self-protection rules. The primary location cannot be soft-deleted without first promoting another location to primary. The last active location cannot be soft-deleted — the surface returns a structured rejection prompting the operator to add another location first. Address edits that would move a location outside its declared country require an explicit confirmation token to prevent accidental cross-border edits.
Audit. Every write — create, edit, hours change, staff assignment, service change, bulk import, soft delete, restore, permanent delete, feed regeneration, set-primary — emits an Activity Log entry. The log records the acting operator, the location, and the change shape (field-level diff for edits, batch summary for bulk imports, transfer pair for primary swaps). Geocoding lookups against the external geocoder are not in the Activity Log — they are infrastructural and logged separately for cost tracking.
REQUEST → /sg-admin/locations/...│▼┌────────────────────────┐│ Admin gate │ unauth → reject└──────────┬─────────────┘│ authed▼┌────────────────────────┐│ Role capability check │ role lacks cap → check per-location│ (platform-wide) │└──────────┬─────────────┘│▼┌────────────────────────┐│ Per-location manager │ no manager grant + no role →│ check (scoped writes) │ reject└──────────┬─────────────┘│ allowed▼┌────────────────────────┐│ Self-protection rules │ primary-without-promote /│ │ last-active / cross-border →│ │ reject└──────────┬─────────────┘│ passes▼write executes│▼Activity Log entry│▼Geocoding lookups bypassActivity Log; they log toa separate infrastructureledger for cost tracking.Related references
- Pages — Reference. Each active location auto-generates a landing page at
/locations/. Slug collisions are validated at save time. - Users — Reference. Staff assignments reference user IDs. Per-action capability checks resolve against Users.
- Events — Reference. Events can reference a venue, and venues can reference a location for the underlying address record.
- Forms — Reference. Contact forms on a location landing page can route submissions to that location's contact email by default.
- Media — Reference. Cover images, gallery photos, and staff portraits reference Media records.
- Settings — Reference. Default country, default time zone, global services catalogue, structured-data site-wide payload, and geocoder configuration live in Settings.
- SEO — Reference. Per-location structured-data overrides feed the SEO module's overall structured-data composition.
- Email — Reference. Per-location contact emails route through the Email module's default deliverability path.
/docs/locations.Source-walk verified (2026-05-28)
List — /sg-admin/locations
- H1: "Locations" · subtitle: "Manage all locations and details."
- Columns: Location · Address · Author · Created At
- Data handler: POST
/Admin_Locations_Actions/do_table
Create — /sg-admin/locations/create
- H1: "Create Location"
- Form action: POST
/Admin_Locations_Actions/do_location(48 fields) - Top-level:
title _metas[phone_number]·_metas[street_address]·_metas[city]·_metas[state]·_metas[zip_code]·_metas[country]·_metas[place_id](Google Place ID) ·_metas[gbp_link](Google Business Profile link) ·_metas[date_opened]- Hours of operation per day:
_metas[hours_of_operation][— 21 fields (7 days × 3 attrs)][open_hours|time_from|time_to]
Settings — /sg-admin/locations/settings
- H1: "Location Settings" · subtitle: "Selector behaviour and GMB attributes."
- Form action: POST
/Admin_Locations_Actions/update_settings(15 fields) - Picker config:
locations_picker_style·locations_picker_display·locations_picker_card_detail·locations_picker_auto_prompt·locations_picker_auto_prompt_scope·locations_picker_required·locations_picker_remember_days - GMB attributes JSON:
gmb_attributes_json(free-text JSON for Google Business Profile attribute overrides)
