For full design control: your own login button
Integrate Upod with a fully custom login button — no <upod-button> custom element required
If you want the Upod client library's session and profile helpers but not the <upod-button> custom element, you can wire up your own button and call client.login() yourself. This gives you full control over markup, styling, copy, and placement.
When to use this
- Your design system or CSP policy doesn't allow custom elements
- You're in an SSR framework where custom elements are awkward
- Your brand team needs pixel-perfect control over the button
- You want multiple login triggers across the page (banner, nav, settings) sharing one styled component
Step 1: Load the client library
Include the Upod client bundle in your page:
<script src="https://cdn.upod.eu/client/bundle.browser.js"></script>Step 2: Initialize the client
Initialize Upod with the client_id we issued you:
const client = upod.initialize({
authority: 'https://account.upod.eu',
client_id: 'your-client-id',
redirect_uri: location.origin + location.pathname,
post_logout_redirect_uri: location.origin + location.pathname,
});Step 3: Add your own button and wire the click
Use any markup you like. Wire its click handler to client.login():
<button class="my-cookie-button" type="button">Continue with Upod</button>document.querySelector('.my-cookie-button').addEventListener('click', () => {
client.login();
});That's the entire difference from the <upod-button> path — you own the markup and the click handler.
Step 4: (Optional) Use the Upod logo
If you want to keep the visual Upod cue without the custom element, our logo is served from account.upod.eu:
- Dark variant (for light backgrounds):
https://account.upod.eu/static/logo.svg?variant=dark - Light variant (for dark backgrounds):
https://account.upod.eu/static/logo.svg?variant=light
<button class="my-cookie-button" type="button">
<img
src="https://account.upod.eu/static/logo.svg?variant=dark"
alt=""
aria-hidden="true"
/>
Continue with Upod
</button>Accessibility notes
- Use
<button type="button">, not<a>— clicking triggers a JS action, not navigation. - Give the button an accessible name via its text content (e.g. "Continue with Upod"). If the button is icon-only, add
aria-label. - Mark decorative logos with
alt=""andaria-hidden="true"so screen readers don't announce them twice.
Step 5: Handle the callback on the redirect URI
After login, Upod redirects the browser back to your redirect_uri with ?code=…&state=… query parameters. Call client.handleCallbackIfNeeded() on every page load at that URI — it's a no-op when there's nothing to handle, and consumes the code when there is:
client.handleCallbackIfNeeded().then(() => {
client.isAuthenticated().then(loggedIn => {
if (loggedIn) {
hideCookieBanner();
loadPersonalizedContent();
} else {
showCookieBanner();
}
});
});Always call handleCallbackIfNeeded() on the page registered as your
redirect_uri — without it, the user lands back on your site with login
params in the URL but no active session. It's safe to call on every page
load.
You can also react to login and logout via callbacks:
client.onLogin(() => {
hideCookieBanner();
loadPersonalizedContent();
});
client.onLogout(() => {
showCookieBanner();
});Step 6: Use the profile data
Once authenticated, fetch the anonymized profile and use it for personalisation:
function loadPersonalizedContent() {
client
.fetch({
path: '/storage/profile',
method: 'get',
query: { purpose: 'personalisation' },
})
.then(profile => {
// profile.gender: 'male' | 'female' | 'other'
// profile.ageRange: [number, number | null]
// profile.residence: string (e.g. 'Amsterdam')
renderArticles(profile);
});
}See Purpose values for the list of allowed purpose strings, and the Anonymized profile data reference for the full schema.
Complete example
<!doctype html>
<html>
<head>
<title>My site with a cookie banner</title>
<script src="https://cdn.upod.eu/client/bundle.browser.js"></script>
<style>
.my-cookie-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.25rem;
border: 1px solid currentColor;
border-radius: 6px;
background: white;
cursor: pointer;
font: inherit;
}
.my-cookie-button img {
height: 1.25em;
width: auto;
}
</style>
</head>
<body>
<div class="cookie-banner" id="cookie-banner">
<p>How would you like to continue?</p>
<button id="accept-cookies" type="button">Accept cookies</button>
<button id="pay-for-privacy" type="button">Pay for privacy</button>
<button class="my-cookie-button" type="button">
<img
src="https://account.upod.eu/static/logo.svg?variant=dark"
alt=""
aria-hidden="true"
/>
Continue with Upod
</button>
</div>
<main id="content"></main>
<script>
const client = upod.initialize({
authority: 'https://account.upod.eu',
client_id: 'your-client-id',
redirect_uri: location.origin + location.pathname,
post_logout_redirect_uri: location.origin + location.pathname,
});
document
.querySelector('.my-cookie-button')
.addEventListener('click', () => {
client.login();
});
function hideCookieBanner() {
document.getElementById('cookie-banner').style.display = 'none';
}
function loadPersonalizedContent() {
client
.fetch({
path: '/storage/profile',
method: 'get',
query: { purpose: 'personalisation' },
})
.then(profile => {
document.getElementById('content').textContent =
`Personalised for ${profile.gender} in ${profile.residence}`;
});
}
// Handle the redirect back from Upod
client.handleCallbackIfNeeded().then(() => {
client.isAuthenticated().then(loggedIn => {
if (loggedIn) {
hideCookieBanner();
loadPersonalizedContent();
}
});
});
client.onLogin(() => {
hideCookieBanner();
loadPersonalizedContent();
});
</script>
</body>
</html>Next steps
- Want the styling work done for you? Use
<upod-button>in your cookie banner instead - Want to skip the Upod client library entirely? See Federated login via OIDC
- Read the API Reference for
client.fetch()and the full client API