Components
There is a second way to embed. Everything below assumes the four-element integration — a listing page, a vehicle page, a basket and a checkout, which is what
drivecars.aiitself runs. If you would rather run the whole booking on ONE page with no navigation, see One page, whole journey. Same bundles, same components, same money rules.
Five custom elements. Four are product surfaces; the fifth is a smoke test.
| Tag | Bundle | What it is |
|---|---|---|
<drivecars-search> | search.js | Search form plus results. mode="form" renders the form alone. |
<drivecars-cart> | cart.js | Cart contents, coupon, delivery address, continue-to-checkout. |
<drivecars-checkout> | checkout.js | Customer details, payment, confirmation. |
<drivecars-payment> | payment.js | Standalone pay-a-payment-link screen. |
<drivecars-hello> | hello.js | Smoke test. Renders a greeting; use it to prove the loader reaches your page, nothing more. |
search.js lazily pulls in map.js for the results map — another reason the
flat CDN layout matters.
Tags are lowercase. The loader’s tag map is keyed lowercase and never
matches otherwise, so <drivecars-Search> renders empty with no error.
How attributes behave
Each element declares the attributes it watches. Setting one that is not on its list does nothing — not an error, just no effect — so the tables below are the element’s real list, not a superset.
Attributes are also the only re-render trigger after first paint. Changing a watched attribute re-renders; changing anything else does not.
When you use the runtime, mount() sets these attributes for you, but only
where the element does not already carry one. An attribute you wrote in your
HTML always wins.
<drivecars-search>
The one most partners embed. Watches:
token, customer-token, country, city, date-from, date-to,
driver-age, car-group, office-sta, office-label, pickup-hour,
return-hour, base-url, environment, mode, results-url, pickup-type,
hide-pickup-toggle, display-currency, lang, maps-key, attrs,
data-dc-products, data-dc-default-product.
The ones you will actually set:
| Attribute | Value | Notes |
|---|---|---|
token | session token | Set by mount() if absent — or written a moment later on the partner-auth path, where the element mounts tokenless and carries data-dc-session-pending until the exchange resolves. |
environment | sandbox / production | Which DriveCars to talk to. Resolves to the API host for you — see below. |
base-url | API host | The escape hatch, for local development. Normally you set environment instead — see below. |
lang | en / fr / ar / es | Arabic renders right-to-left. |
results-url | a path on your site | See below. Effectively required for rentals. |
mode | form | Renders the form alone, with no results grid. Any other value renders form plus results. |
display-currency | a JSON string | See Display currency. |
attrs | comma-separated slugs | Pre-selects a vehicle-category filter, e.g. attrs="suv". |
hide-pickup-toggle | presence only | Present at all — even ="false" — hides the toggle. It is tested for presence, not value. |
country, city, date-from, date-to, driver-age, car-group, pickup-hour, return-hour, pickup-type | initial form values | Deep-link prefill. |
data-dc-products and data-dc-default-product are written by the SDK,
not by you — they are how init() tells a separately-bundled element what you
were sold. Do not set them by hand.
Naming an environment, not a hostname
Name an environment; do not write our hostname. environment and
base-url are both real attributes on all four business elements, and they
answer the same question — which API host this element talks to — at two very
different levels of commitment.
<drivecars-search environment="sandbox" token="…"></drivecars-search>sandbox and production are the two you want. (staging is accepted as a
synonym for sandbox; there is no third deployment behind it.) An environment
name we can never break is worth more to you than a hostname we might: your
go-live edit is sandbox → production in one place, not a find-and-replace
over every URL in your templates.
An unrecognised name — prod, say — is not used as a host. The element
warns on the console and falls back to production, rather than firing requests
at a hostname that does not exist.
| What you set | Where it goes |
|---|---|
base-url="http://localhost:3002" | exactly there |
environment="sandbox" | the sandbox API |
environment="production" | the production API |
| neither | production — see below |
base-url is the escape hatch, and it is for local development. Pointing
an element at http://localhost:3002 is the case no environment name can
cover, and it is the case base-url exists for. It wins over environment
when both are present, which is what makes it useful: leave environment in
your template and override the host in your dev build.
That precedence is also the trap. An attribute you wrote always beats what
init() would have set, so a base-url left behind from a local experiment
quietly survives your switch to environment and keeps pointing wherever it
pointed — including after go-live. Delete it when you stop needing it.
If you call init(), you need neither attribute: the SDK
resolves the host from its environment and writes base-url onto the
elements for you — both the ones you mount() and any others already in the
document. Set the attribute yourself when you use the elements standalone with
no init() at all, or when one element must point somewhere different from
the rest.
An element that knows no host waits
An element with neither base-url nor environment, and no init() to
sweep one in, renders normally but holds its first API calls rather than
issuing them.
This looks like an odd thing to do, and it is deliberate. An element upgrades
and paints before your init() has finished resolving, so its mount-time
config fetches would otherwise leave in that first tick — at the default host,
which is production. Those endpoints are public, so production answers 200
and nothing looks broken: a sandbox integration would silently read production
configuration on every page load, and you would have no symptom to chase. An
element that has not been told which DriveCars it is talking to must not guess
one.
The wait ends the moment anything answers the question — your attribute, or
the base-url the SDK sweeps in — and the held calls go out then, once. The
default is unchanged for everything else: an element that names nothing still
resolves to production for the calls your user actually triggers. If you see
an element sitting there having fetched nothing, it is telling you it was
never configured. Give it an environment.
results-url is what makes a card clickable
A rental result card is an <a href> built from results-url. Without it
the card renders an anchor with no href, and a click does nothing at all —
no navigation, no console error, no visible failure. The widget has no
car-detail page of its own, so you tell it where yours lives.
<drivecars-search environment="production" results-url="/cars"></drivecars-search>The element appends the search state as query parameters — location,
country, dateFrom, dateTo, pickupHour, returnHour, pickupType,
driverAge, carGroup, officeSta, officeLabel — and navigates relative to
your own origin. results-url doubles as the base for the per-car detail links.
In mode="form", submitting fires a cancelable dc:search event first. Call
preventDefault() to route client-side yourself; otherwise the element
navigates to results-url. See Events.
If you see 403 product_not_enabled, that is a commercial conversation, not a
bug. Your credentials are fine and the endpoint exists; the product is not on
your account. Talk to your account manager.
Display currency
display-currency is a JSON string, because a custom-element attribute
cannot carry an object:
<drivecars-search
display-currency='{"code":"GBP","symbol":"£","rateToPrimary":0.85,"symbolPosition":"before"}'>
</drivecars-search>Only symbol and rateToPrimary are required; symbolPosition defaults to
before. Read by search only — setting it on cart, checkout or payment does
nothing.
Malformed JSON, or JSON missing symbol or rateToPrimary, is ignored
rather than thrown. A wrong shape here shows up as prices in the default
currency, not as an error.
Through the runtime, pass the same thing as an object to init({ currency })
and mount() serializes it for you. Note it is not a bare code: the element
needs the symbol to render with and the rate to convert at, and neither is
derivable from "GBP" in a browser.
<drivecars-cart>
Watches token, customer-token, base-url, environment, lang,
pos-country.
| Attribute | Notes |
|---|---|
token, base-url, environment, lang | As above. |
pos-country | The buyer’s point-of-sale market. Read by cart and checkout only — setting it on search or payment does nothing. |
Reads the server-side cart for this session. The journey is search → cart → checkout.
<drivecars-checkout>
Watches token, customer-token, base-url, environment, mor-mode,
lang, pos-country.
| Attribute | Notes |
|---|---|
mor-mode | drivecars or partner_invoiced. Mirrors partner.mor_mode from your session. See Payments. |
pos-country | As on cart. |
The card form’s publishable key is not something you set: when DriveCars is merchant of record it arrives with the payment intent, and when you are, there is no card form. See Payments.
Needs a draft booking reference to do anything; with none it renders its empty state.
<drivecars-payment>
Watches payment-link-token, customer-token, base-url, environment,
lang.
| Attribute | Notes |
|---|---|
payment-link-token | Required. Without it the element renders Missing payment-link-token attribute in red. |
The card form’s publishable key arrives with the payment intent; there is nothing of yours to set.
This element does not take the session token. It authenticates with a
payment-link token instead, which is why it is the one element that never asks
for a session refresh and never emits tokenRefreshNeeded.
A payment link is minted per booking and sent to a customer who may have no
account at all — so there is nothing on the page to prefill it from. Links are
minted by DriveCars ops today; if you need to mint your own, ask us, as the
route is not part of the /v1 surface documented in the
API Reference.
<drivecars-hello>
Watches name — and nothing else. It takes no token, no environment, no
base-url and no lang. It calls no API, so it has no host to resolve and
never waits on one.
<drivecars-hello name="partner"></drivecars-hello>A smoke test, not a product surface. If it renders, the loader reached your page and is injecting bundles. If it does not, the problem is the script tag or the CDN path, not your session.
hello is also the one tag exempt from the entitlement check at mount(), since
it carries no business capability.