Ferry Booking Widget
Embed a full ferry booking experience on any website with two lines of HTML. The widget is a self-contained Web Component that searches, prices, and books across every FerryGate operator.
Quick Start
Going live takes four short steps. No build tooling, no framework, no npm install — just a script tag and a custom element.
Get a widget key
Every widget is tied to a widget key that starts with wgt_. Your FerryGate administrator provisions this key for your organisation, along with the list of origins (domains) allowed to load it. You do not generate it yourself.
Note:
Ask your administrator for both the widget key and the list of authorized origins. The key only works on those domains.
Load the bundle
Add the widget script to your page, ideally just before the closing body tag. The defer attribute keeps it from blocking your page render.
<script src="https://ferrygate.com/widget/default/latest.js" defer></script>Drop in the element
Place the custom element wherever you want the booking experience to appear. Pass your widget key and an optional language.
<ferrygate-booking
api-key="wgt_xxxxxxxxxxxxxxxx"
lang="fr"></ferrygate-booking>Checkout redirect (B2C, automatic)
In B2C mode, after the ticket is created the widget redirects the customer to your checkout page to collect payment. By default it targets your current origin + /checkout — nothing to configure. Override the path with checkout-path, or use a full checkout-url for a different domain.
<!-- Checkout auto-derived: <your-origin>/checkout (nothing to configure) -->
<ferrygate-booking
api-key="wgt_xxxxxxxxxxxxxxxx"
lang="fr"></ferrygate-booking>
<!-- Optional overrides: checkout-path="/pay" · checkout-url="https://other-domain.com/pay" -->Install by environment
The widget is a standard Web Component, so it runs in any stack. The script tag and the element never change — only the way you include them does. Pick your environment below.
The attributes (api-key, lang, and the optional checkout-path / checkout-url) are plain strings, so no framework-specific data binding is required.
HTML / static site
Paste the two lines anywhere in your markup — a CMS, a landing page, or a page builder. Nothing else is needed.
<script src="https://ferrygate.com/widget/default/latest.js" defer></script>
<ferrygate-booking api-key="wgt_..." lang="fr"></ferrygate-booking>WordPress
Enqueue the script from your theme, then drop the element into a Custom HTML block (or expose it through a shortcode).
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script(
'ferrygate-widget',
'https://ferrygate.com/widget/default/latest.js',
[], null, true // load in the footer
);
});<ferrygate-booking api-key="wgt_..." lang="fr"></ferrygate-booking>PHP (Laravel / Symfony)
It is plain server-rendered HTML: put the script in your layout and the element in any Blade or Twig template.
{{-- layout.blade.php (or base.html.twig) --}}
<script src="https://ferrygate.com/widget/default/latest.js" defer></script>
{{-- any page template --}}
<ferrygate-booking api-key="wgt_..." lang="fr"></ferrygate-booking>React / Next.js
Custom elements render natively in React. On Next.js, load the bundle with next/script and use the element directly.
import Script from 'next/script'
export default function BookingPage() {
return (
<>
<Script src="https://ferrygate.com/widget/default/latest.js" strategy="afterInteractive" />
<ferrygate-booking api-key="wgt_..." lang="fr" />
</>
)
}TypeScript: declare ferrygate-booking once in JSX.IntrinsicElements; the attributes are strings.
Vue / Nuxt
Tell Vue to treat ferrygate-booking as a custom element so it is not parsed as a Vue component, then use it like any tag.
export default defineNuxtConfig({
vue: {
compilerOptions: {
isCustomElement: (tag) => tag === 'ferrygate-booking'
}
},
app: {
head: {
script: [{ src: 'https://ferrygate.com/widget/default/latest.js', defer: true }]
}
}
})<ferrygate-booking api-key="wgt_..." lang="fr" />Plain Vue 3 (Vite): set the same isCustomElement in your vue() plugin options.
Angular
Register CUSTOM_ELEMENTS_SCHEMA so Angular accepts the unknown tag, then load the script from angular.json or index.html.
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA] // or in a standalone component
})
export class AppModule {}Then use <ferrygate-booking api-key="wgt_..." lang="fr"></ferrygate-booking> in any template.
Attributes
The element is configured entirely through HTML attributes. No JavaScript initialization is required.
| Attribute | Required | Description |
|---|---|---|
| api-key | Yes | Your widget key, prefixed with wgt_. Determines the operators, pricing, and mode available to the widget. |
| lang | No | Interface language: fr or en. Defaults to the visitor's browser language when omitted. |
| checkout-path | No | Optional. B2C only — path of your checkout page on the same origin. Defaults to /checkout. Final URL = current origin + this path. |
| checkout-url | No | Optional. B2C only — a full absolute URL for a checkout on a different domain. Overrides the origin-derived URL. |
Checkout URL resolution (B2C): the checkout-url attribute wins; otherwise the current page origin + checkout-path (default /checkout). It is resolved on the client. Allowed origins control where the widget may run, not the checkout destination.
Modes
The widget operates in one of two modes. The mode is carried by the widget key itself — you do not set it as an attribute. Ask your administrator which mode your key uses.
B2C mode
For selling to end customers. After the ticket is created the widget redirects the buyer to your merchant checkout to collect payment. B2B margins are never exposed to the browser.
Agent mode
For internal agents and resellers. After booking the widget redirects to the FerryGate SaaS confirmation page, where the agent manages payment and ticketing.
Allowed origins (CORS)
Each widget key carries an allowlist of origins. The widget only accepts requests coming from those domains. HTTPS is required in production; plain http is tolerated for local development.
A request from an origin that is not on the allowlist is rejected with a 403 response.
| Origin | Accepted |
|---|---|
| https://yoursite.com | Yes (production) |
| http://localhost:3000 | Yes (local only) |
| https://not-whitelisted.com | No (403) |
Warning:
Adding or changing an origin requires your administrator to update the key. Plan your domains before going live.
Bundle & isolation
~78 kB
Gzipped bundle size — lightweight enough to load on any page.
Shadow DOM
A real Web Component with an isolated Shadow DOM. Your host CSS and JavaScript stay untouched.
Zero dependencies
No shared libraries with the host site. The widget never pollutes the page's global scope or styles.
Note:
Because styles are scoped inside the Shadow DOM, the widget looks identical on every site regardless of the host's CSS framework.
Security
Opaque token. The widget key is an opaque identifier. It exposes no pricing, margin, or account internals to the browser.
Rate limited. Each token is capped at 120 requests per minute. Exceeding the limit returns a 429 response until the window resets.
Revocable. A compromised key can be revoked instantly by your administrator, immediately disabling the widget everywhere it is embedded.
Troubleshooting & FAQ
I get a 401 error
The widget key is invalid, mistyped, or has been revoked. Double-check the wgt_ value and ask your administrator to confirm the key is still active.
I get a 403 error
The current page's origin is not on the key's allowlist. Confirm the exact domain (including https) is whitelisted for your key.
The widget does not appear
Work through this checklist:
- Is the script tag loaded and reachable (check the network tab)?
- Is the <ferrygate-booking> element present in the DOM?
- Is the api-key attribute set to a valid wgt_ key?
I get a 429 error
You have exceeded 120 requests per minute on this token. Wait for the rate-limit window to reset before retrying.
How do I change the language?
Set the lang attribute to fr or en. Without it, the widget follows the visitor's browser language.