Labrador Setup Guide
Overview
Labrador is a React-based clinical trial study finder widget that can be embedded into any website. It provides a comprehensive interface for searching, filtering, and viewing clinical trial information. This guide will walk you through the setup and integration process.
Table of Contents
Prerequisites
Before setting up Labrador, ensure you have:
- An API key from WeTrials Console that can call the Labrador Study, Learning Center and Support APIs, restricted to your website — see the API Key Guide
- Basic knowledge of HTML/JavaScript
Installation Method
CDN Integration
The recommended way to integrate Labrador is by including it via CDN in your HTML:
CDN Base URLs
| Environment | Base URL |
|---|---|
| Development | https://assets.dev.wetrials.com/labrador/v2/ |
| Production | https://assets.wetrials.com/labrador/v2/ |
Basic Integration
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Your Website with Labrador</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Google Fonts (Required) -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<!-- Labrador Styles -->
<link rel="stylesheet" href="https://assets.wetrials.com/labrador/v2/styles.css" />
</head>
<body>
<!-- Container for Labrador widget -->
<div id="study-finder-widget"></div>
<!-- Modal container (required for popups) -->
<div id="labrador-modal-root"></div>
<!-- Labrador Script (Production) -->
<script src="https://assets.wetrials.com/labrador/v2/index.js"></script>
<!-- Initialize Labrador -->
<script>
window.addEventListener('load', () => {
Labrador({
shadowRoot: document.getElementById('study-finder-widget'),
shadowModalRoot: document.getElementById('labrador-modal-root'),
env: 'production', // 'development' or 'production'
orgId: 'your-organization-id',
apiKey: 'your-api-key-here',
// Optional: enables crawlable path-based URLs instead of the
// default hash-based ones. Requires a server catch-all route and
// a dynamic canonical tag — see "URL Routing & SEO" before setting
// this.
// basePath: '/patients/brain-cancer/resources',
});
});
</script>
</body>
</html>Advanced Integration with Shadow DOM
For better style isolation and to prevent CSS conflicts, you can use Shadow DOM:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Your Website with Labrador (Shadow DOM)</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Google Fonts (Required) -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
</head>
<body>
<!-- Container for Labrador widget -->
<div id="wetrials-shadow-dom"></div>
<!-- Labrador Script -->
<script type="module" crossorigin src="https://assets.wetrials.com/labrador/v2/index.js"></script>
<!-- Shadow DOM Setup and Initialize -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const cssURL = 'https://assets.wetrials.com/labrador/v2/styles.css';
// Helper function to append stylesheet to shadow root
const appendStylesheet = (root, href) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
root.appendChild(link);
};
// Create modal shadow DOM
const createModalShadowDOM = (id, position = null) => {
const host = document.createElement('div');
host.id = id;
if (position) {
host.style.position = position;
host.style.zIndex = '9999999';
}
const shadowRoot = host.attachShadow({ mode: 'open' });
appendStylesheet(shadowRoot, cssURL);
document.body.appendChild(host);
return shadowRoot;
};
// Create modal shadow root
const shadowModalRoot = createModalShadowDOM('wetrials-modal-shadow-dom', 'absolute');
// Set global font size (optional)
const globalStyle = document.createElement('style');
globalStyle.textContent = 'html { font-size: 16px !important; }';
document.head.appendChild(globalStyle);
// Create main shadow root
const mainContainer = document.getElementById('wetrials-shadow-dom');
const mainShadowRoot = mainContainer.attachShadow({ mode: 'open' });
appendStylesheet(mainShadowRoot, cssURL);
// Initialize Labrador with shadow roots
Labrador({
shadowRoot: mainShadowRoot,
shadowModalRoot: shadowModalRoot,
env: 'production', // 'development' or 'production'
orgId: 'your-organization-id',
apiKey: 'your-api-key-here',
// Optional: enables crawlable path-based URLs instead of the
// default hash-based ones. Requires a server catch-all route and
// a dynamic canonical tag — see "URL Routing & SEO" before setting
// this.
// basePath: '/patients/brain-cancer/resources',
});
});
</script>
</body>
</html>Configuration
Required Configuration Parameters
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
shadowRoot | HTMLElement | Yes | Main container element for the widget | document.getElementById('study-finder') |
shadowModalRoot | HTMLElement | Yes | Container for modal popups | document.getElementById('modal-root') |
env | string | No | Environment setting: 'production' (default) or 'development' | 'development' or 'production' |
orgId | string | Yes | Your organization identifier | 'wetrials' |
apiKey | string | Yes | API authentication key | 'your-api-key-here' |
basePath | string | No | Pathname of the page hosting the widget (leading slash; a trailing slash is accepted and stripped automatically). Enables path-based (crawlable) URLs | '/patients/brain-cancer/resources' |
routing | string | No | 'hash' (default), 'path' (needs basePath + server catch-all), or 'query' (crawlable, no server changes). See URL Routing & SEO | 'query' |
routeParam | string | No | Query key that carries the route in 'query' mode. Default 'wt'. Change only if the host page already uses wt | 'wt' |
manageCanonical | boolean | No | Whether Labrador manages the page's <link rel="canonical"> and og:url. Takes effect only in 'query' mode, where it defaults to true | true |
basePathis opt-in. If omitted, Labrador uses hash-based URLs (#/...) — this is the default and requires no server changes. SettingbasePathrequires both of the following, not just the first: your server must meet the catch-all requirement below (without it, deep links 404 before Labrador ever loads), and your CMS must emit a route-aware canonical tag per the Canonical tags section (without it, deep links resolve but aren't reliably indexed — Labrador's client-side canonical sync is a best-effort supplement, not a substitute for this).basePathmust be an absolute path (leading slash). It's matched as a prefix of the page's actual pathname at load time — an exact match or a nested path under it (e.g. a deep-linked article URL, since that's the whole point ofbasePath) both count; if the pathname doesn't start withbasePath(a typo, or the same config reused on a differently-prefixed page), Labrador logs a console error and falls back to hash-based URLs rather than rendering a blank widget — SEO doesn't improve, but the widget stays usable.
Environment-Specific Configuration
Development Environment
Labrador({
shadowRoot: document.getElementById('study-finder'),
shadowModalRoot: document.getElementById('modal-root'),
env: 'development',
orgId: 'wetrials-dev',
apiKey: 'dev-api-key',
// Optional — see "URL Routing & SEO" before setting this.
basePath: '/patients/brain-cancer/resources',
});Production Environment
Labrador({
shadowRoot: document.getElementById('study-finder'),
shadowModalRoot: document.getElementById('modal-root'),
env: 'production',
orgId: 'wetrials',
apiKey: 'prod-api-key',
// Optional — see "URL Routing & SEO" before setting this.
basePath: '/patients/brain-cancer/resources',
});URL Routing & SEO
By default (no basePath), Labrador uses hash-based URLs (#/...). These never touch your server's routing and require no setup, but search engines generally do not index distinct hash fragments as separate pages — internal Labrador content stays effectively invisible to search.
You have two ways to get crawlable URLs:
routing: 'query'(recommended for most partners) — the whole route rides in one query parameter,?wt=/study/x. Nothing else changes: your server keeps resolving the one page the widget is embedded on and returns 200 whatever follows the?, so there is no server catch-all to configure and no CMS canonical change — Labrador manages the canonical itself. The only trade-off is appearance:?wt=/study/xin the address bar instead of/study/x. Same page, same content, still indexable.basePath+routing: 'path'— real path segments (/…/resources/study/x). Reads marginally better in results, but requires both of the two things below — skipping either means you pay the operational cost (server rewrites, more surface area) without the SEO benefit.
Query-based URLs — no server changes
Set routing: 'query' (no basePath needed). Navigation then produces URLs like:
| View | URL |
|---|---|
| Study Finder | /find-trials?wt=/study |
| Study detail | /find-trials?wt=/study/NCT07215910 |
| Learning Center video | /find-trials?wt=/learning-center/video/title--entryId |
| Support article | /find-trials?wt=/support/program-name--entryId |
The app's own query params (filters, ?tab=) and any hash are encoded inside wt; your page's own query params (utm_source, fbclid, …) stay as normal query params and are never touched. Legacy #/study/x links are rewritten to ?wt=/study/x on load, so shared links keep working. Labrador emits a single self-referencing <link rel="canonical"> / og:url carrying ?wt= (tracking params and ?tab= stripped), replacing any static canonical your CMS shipped — so no CMS-side canonical work is required.
Migrating from basePath: replace the basePath line with routing: 'query' and remove the server catch-all rule after confirming deep links still resolve.
How path-based URLs work
For a widget embedded at /patients/brain-cancer/resources with basePath set, navigation produces URLs such as:
| View | URL |
|---|---|
| Learning Center hub | /patients/brain-cancer/resources/learning-center |
| Blog article | /patients/brain-cancer/resources/learning-center/blog/article-title--id |
| Podcast episode | /patients/brain-cancer/resources/learning-center/podcast/episode--id |
| Support hub | /patients/brain-cancer/resources/support |
| Support program detail | /patients/brain-cancer/resources/support/program-name--id |
| Study Finder | /patients/brain-cancer/resources/study |
Query parameters (category filters, pagination) are appended as normal query strings, e.g. ?categoryId=cancer-types&page=2.
Canonical tags
Most CMS platforms (WordPress/Yoast, Drupal, AEM, etc.) emit a self-referencing <link rel="canonical"> on every page — pointing at the host page's own URL. Left as a static self-reference, that tag tells search engines every Labrador URL is a duplicate of the hub page, and they get collapsed out of the index — even with the catch-all requirement below correctly configured. This is the actual gating factor for the "search engines can index individual pages" goal; the catch-all only stops those URLs from 404ing, it doesn't make them indexable.
The reliable fix is on your CMS, not in Labrador. Configure your CMS/SEO plugin to emit a canonical tag matching the current deep-link URL in the server-rendered HTML — the HTML a crawler sees before any JavaScript runs. Most SEO plugins support a dynamic/programmatic canonical for this. If you don't do this, only the hub page will reliably be indexed; deep Labrador URLs may resolve correctly (with the catch-all in place) but won't be picked up as distinct pages.
When basePath is set, Labrador additionally keeps an existing <link rel="canonical"> tag in sync client-side, as a best-effort supplement — not a substitute for the CMS-side fix above:
- It updates the tag via a React effect after each route change, which only runs once Labrador's JavaScript has executed in the browser. A crawler or tool that doesn't execute JavaScript — and Google itself documents client-inserted/modified
rel=canonicaltags as unreliable — will still see whatever canonical your server actually sent. Don't rely on this alone if indexing matters; it helps secondary tools and JS-rendering crawlers, it doesn't replace a route-aware canonical in the server HTML. - It only updates a canonical tag that's already present in the page; it never creates one.
- The query string is intentionally excluded from the synced URL — filter/pagination/tracking parameters (
categoryId,page,utm_source, ...) consolidate to the same canonical page rather than fragmenting the index. - If your CMS's SEO plugin also updates the canonical tag on client-side navigation (rare, but some do), the two will race — Labrador's effect runs after route changes, so in practice it wins, but this doesn't change the point above: the tag your server sends for the initial request is still what most crawlers see.
Labrador does not add a canonical tag via Helmet or otherwise duplicate the existing one — a page with two canonical tags is treated by search engines as having none, which is worse than a single stale one.
Server-side catch-all requirement
Because routing is handled client-side, your server must return the same HTML page with a 200 status for every URL under basePath — this is a wildcard/catch-all route in your CMS or framework, not a static-file fallback. Without this, users who land directly on a deep-linked URL (from a search result, shared link, or on reload) get a 404 before Labrador ever loads.
The exact mechanism depends entirely on how your page is served — Labrador is embedded via <script> into a page your own CMS or framework renders, it is not a standalone static-file deployment, so generic try_files/.htaccess SPA-fallback recipes usually do not apply as-is (they assume an index.html file exists on disk at that path, and can shadow your framework's own routes for the whole subtree). Talk to whoever owns the host page's routing about the right approach for that stack, for example:
- WordPress / Drupal / AEM (or other server-rendered CMS): add a rewrite/routing rule that maps the entire
basePathsubtree to the same page/template, without touching real pages outside that subtree. - Next.js: an optional catch-all segment —
app/…/resources/[[...slug]]/page.tsx— rendering the same page that hosts the widget. - Purely static site (no server-side framework): a genuine SPA fallback, e.g. Netlify
_redirectsor a Vercel rewrite mappingbasePath/*→ the page's HTML with a 200 (not a redirect).
Legacy hash URLs
Labrador automatically migrates any existing hash-based bookmark to the equivalent path-based URL on first load, when basePath is set — no action required. This only runs on the initial load of a hash-based URL; it never migrates away from hash routing when basePath is unset. For example, a user visiting:
/patients/brain-cancer/resources/?utm_source=newsletter#/learning-center/blog/slug--idwill be silently redirected (via history.replaceState, no page reload) to:
/patients/brain-cancer/resources/learning-center/blog/slug--id?utm_source=newsletterThe outer page's query string (e.g. UTM/campaign params) is preserved and merged with any query string on the hash route itself. (The trailing slash on /resources/ in the example above is fine — basePath matching is prefix-based, and a normalized basePath of /patients/brain-cancer/resources matches a pathname of either /patients/brain-cancer/resources or /patients/brain-cancer/resources/....)
Analytics: virtual pageviews
Once basePath or routing: 'query' is set, internal navigation inside the widget pushes real browser history entries onto the host page (history.pushState). Analytics tools that listen for history changes (GA4, Adobe Analytics, etc.) will start recording a pageview per in-widget navigation, not just per real page load. If you track pageviews on this page, expect that count to increase after adopting either mode, and adjust dashboards/alerts accordingly. Conversely, any GA/GTM trigger keyed on hash-change virtual pageviews stops firing once you leave hash mode. Neither happens under the default hash-based routing.
Support and Resources
Getting Help
- Documentation: This guide and the Labrador API Reference
- Email Support: platform@wetrials.com
Additional Resources
- Labrador API Reference — the endpoints the widget calls, for building your own integration
- API Key Guide — creating API keys and restricting them to your website
Screenshot: Complete Labrador integration in a production website
Last Updated: October 2026 Maintained By: WeTrials Platform Team