Web SDKComponents

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.ai itself 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.

TagBundleWhat it is
<drivecars-search>search.jsSearch form plus results. mode="form" renders the form alone.
<drivecars-cart>cart.jsCart contents, coupon, delivery address, continue-to-checkout.
<drivecars-checkout>checkout.jsCustomer details, payment, confirmation.
<drivecars-payment>payment.jsStandalone pay-a-payment-link screen.
<drivecars-hello>hello.jsSmoke 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.

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:

AttributeValueNotes
tokensession tokenSet 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.
environmentsandbox / productionWhich DriveCars to talk to. Resolves to the API host for you — see below.
base-urlAPI hostThe escape hatch, for local development. Normally you set environment instead — see below.
langen / fr / ar / esArabic renders right-to-left.
results-urla path on your siteSee below. Effectively required for rentals.
modeformRenders the form alone, with no results grid. Any other value renders form plus results.
display-currencya JSON stringSee Display currency.
attrscomma-separated slugsPre-selects a vehicle-category filter, e.g. attrs="suv".
hide-pickup-togglepresence onlyPresent 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-typeinitial form valuesDeep-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 sandboxproduction 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 setWhere it goes
base-url="http://localhost:3002"exactly there
environment="sandbox"the sandbox API
environment="production"the production API
neitherproduction — 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.

AttributeNotes
token, base-url, environment, langAs above.
pos-countryThe 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.

AttributeNotes
mor-modedrivecars or partner_invoiced. Mirrors partner.mor_mode from your session. See Payments.
pos-countryAs 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.

AttributeNotes
payment-link-tokenRequired. 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.