HG Hulo Global

Geo Block — User Manual

Overview

Geo Block gives you per-channel control over who can reach your Vendure storefront. Pick from 37 region presets, add countries manually, soft-block markets you don't ship to (so they can still browse), allow specific IPs to bypass all rules, and audit every block decision.

Two storefront integration modes:

  • Site-config polling — Storefront calls /geo-block/site-config once on boot, caches the resolved rules, and decides client-side. Cheapest on the server.
  • Per-request check — Storefront calls /geo-block/check on entry, gets a fresh decision per visitor (also logs to the audit table). Use when you want stats.

Install

curl -sSL https://huloglobal.com/vendure-plugins/geo-block/install.sh | bash

Or by hand:

# 1. Install yarn add @huloglobal/vendure-plugin-geo-block # 2. Register in vendure-config.ts import {{ GeoBlockPlugin }} from '@huloglobal/vendure-plugin-geo-block'; export const config: VendureConfig = {{ plugins: [ GeoBlockPlugin.init({{ publicBaseUrl: 'https://shop.example.com', licenceKey: process.env.HULO_LICENCE_KEY_GEO_BLOCK, // Optional: a one-shot maintenance lockdown window maintenanceWindow: {{ startsAt: '2026-07-15T02:00:00Z', endsAt: '2026-07-15T04:00:00Z', allowedIps: ['203.0.113.0/24'], }}, }}), ], }}; # 3. Migration yarn migration:generate AddGeoBlockTables yarn migration:run

Admin UI tour

The Site Access page is mounted at /admin/extensions/geo-block. Five tabs:

Rules

Site access — Rules tab Admin panel mockup. Channel: Elite [GEO-BLOCK ON] [Full block] — Mode — Full block (selected) / Soft block (browse-only) — Strategy — Allow specific places (selected) / Worldwide except blocked — Allowed regions (3 picked) — UK only ✓ / British Isles ✓ / EU / EEA / EFTA / Schengen / Nordic / DACH — G7 / G20 / NATO / OECD / Commonwealth / English-speaking / … — UK subdivisions — England ✓ / Wales ✓ / Scotland / Northern Ireland Vendure Admin Site access — Rules tab Channel: Elite [GEO-BLOCK ON] [Full block] ModeFull block (selected) Soft block (browse-only) StrategyAllow specific places (selected) Worldwide except blocked Allowed regions (3 picked)UK only ✓ British Isles ✓ EU EEA EFTA Schengen Nordic DACHG7 G20 NATO OECD Commonwealth English-speaking UK subdivisionsEngland ✓ Wales ✓ Scotland Northern Ireland
Rules tab — pick mode, strategy, regions, sub-regions. Live preview shows resolved country list at the bottom.

Block page

Customise the block page per channel: a free-text message, optional redirect URL (when set, blocked visitors are 302'd there instead), optional logo URL.

IP allowlist

Add specific IPs or IPv4 CIDR ranges (203.0.113.0/24) that bypass every rule. Use for your office, oncall engineers, payment processor probes, monitoring.

Simulate

Site access — Simulate tab Admin panel mockup. Test a hypothetical visitor — Input: Country code: US — Input: UK region (optional): — — Input: IP address (optional): 8.8.8.8 — [Run simulation] — Result — 🚫 Blocked (country-not-allowed) Vendure Admin Site access — Simulate tab Test a hypothetical visitor Country code: US UK region (optional): — IP address (optional): 8.8.8.8 [Run simulation] Result🚫 Blocked (country-not-allowed)
Simulate tab — try any country / IP combination without saving anything to production.

Stats

Last N days of block decisions: total blocks, soft blocks, unique blocked IPs, top blocked countries, daily series, breakdown by reason.

Region presets

The plugin ships 37 hand-curated presets, grouped in the picker by kind:

GroupPresets
EverywhereWORLDWIDE
GeographyUK_ONLY, BRITISH_ISLES, UK_CROWN_DEPENDENCIES, EUROPE, NORDIC, BALTIC, BENELUX, IBERIA, BALKANS, NORTH_AMERICA, CENTRAL_AMERICA, CARIBBEAN, SOUTH_AMERICA, LATAM, OCEANIA, ANZ, MENA, APAC, EAST_ASIA, SOUTH_ASIA, AFRICA
Trade blocsEU, EEA, EFTA, GCC, ASEAN
Political / economicSCHENGEN, G7, G20, BRICS, OECD, NATO, FIVE_EYES
Language / culturalDACH, ENGLISH_SPEAKING, COMMONWEALTH

Fetch the live catalogue (with country counts + descriptions):

GET /geo-block/presets

Storefront integration

Polling site-config (cheap)

// On storefront boot, once per channel const res = await fetch('https://shop.example.com/geo-block/site-config', {{ headers: {{ 'vendure-token': CHANNEL_TOKEN }}, }}); const cfg = await res.json(); // Cache cfg.geoBlock for ~60s, decide client-side based on the visitor's country.

Per-request check (with logging)

// In your storefront middleware const res = await fetch('https://shop.example.com/geo-block/check', {{ headers: {{ 'vendure-token': CHANNEL_TOKEN, 'cf-ipcountry': req.headers['cf-ipcountry'], // proxy already resolved }}, }}); const verdict = await res.json(); if (!verdict.allowed) {{ if (verdict.redirectUrl) return res.redirect(302, verdict.redirectUrl); return res.render('blocked', {{ message: verdict.message, mode: verdict.mode }}); }}
In soft mode the storefront should render the full site but include a banner explaining you don't ship to the visitor's country, and hide the checkout button. The plugin returns mode: 'soft' on the verdict to make this decision easy.

HTTP endpoints

MethodPathDescription
GET/geo-block/site-configPublic: resolved channel rules (cached client-side)
GET/geo-block/checkPublic: per-request decision (logs to audit table)
GET/geo-block/presetsPublic: 37-preset catalogue
GET/geo-block/admin/channelsAdmin: list channels + rules
POST/geo-block/admin/saveAdmin: save a channel's rules
GET/geo-block/admin/statsAdmin: block totals + top countries
POST/geo-block/admin/simulateAdmin: dry-run a visitor
POST/geo-block/admin/gcAdmin: prune old audit rows

Troubleshooting

Every visitor is blocked but my rules look right

Check the Rules tab's "Resolved allow-list" preview at the bottom — it shows the exact list that would be applied on save. If the list says nothing is allowed, you've probably picked a preset and then blocked every country in it.

Cloudflare IP appears as the visitor IP

The plugin reads the upstream proxy headers in this order: cf-connecting-iptrue-client-ipx-real-ipx-forwarded-for[0]req.ip. Make sure Cloudflare's cf-connecting-ip is reaching your Vendure server (it's a default Cloudflare header).

Stats panel is empty

Stats are populated by the per-request /check endpoint, not by /site-config. If your storefront only polls site-config, no audit rows are written. Either switch to /check or call /check as well in your storefront middleware.