# Badge

The "Built with BuildBase" badge, with an optional referral code and any link params you need.

`` renders the "Built with BuildBase" badge. It is a wrapper around the hosted image at `buildbase.app/badge/v1/`, not a second renderer, so the badge in your React app, in a README, and in a plain HTML footer are the same bytes and cannot drift apart.

```tsx
import { BuildBaseBadge } from '@buildbase/sdk/react';

function Footer() {
  return ;
}
```

**No provider required.** It has no context, no hooks, and no network calls of its own, so it works in a server component and in a footer that sits outside `<SaaSOSProvider>`.

**You do not need the SDK at all to show a badge.** It is an image. If the page you want it on is Webflow, Framer, WordPress or hand-written HTML, copy the plain `<img>` snippet from [buildbase.app/badges](https://www.buildbase.app/badges) instead. This component is a convenience for apps that already have the SDK, not a requirement.

## Props

| Prop        | Accepts                                        | Default      |
| ----------- | ---------------------------------------------- | ------------ |
| `variant`   | `built-with`, `powered-by`, `mark`             | `built-with` |
| `theme`     | `auto`, `light`, `neutral`, `dark`             | `auto`       |
| `size`      | `sm`, `md`, `lg`                               | `md`         |
| `refCode`   | your referral code                             | none         |
| `params`    | any `{ key: value }` object, added to the link | none         |
| `alt`       | overrides the default alt text                 | per variant  |
| `className` | applied to the wrapping anchor                 | none         |
| `link`      | `false` renders the image with no anchor       | `true`       |

```tsx
<BuildBaseBadge variant="powered-by" theme="dark" size="sm" />
```

`theme="auto"` carries both palettes and follows the reader's system setting with no JavaScript on the page. Pick a fixed theme when the surrounding surface does not change with the OS setting, and pick one for a README specifically: GitHub proxies images through a cache that keeps a single rendering, so `auto` freezes on whichever theme it saw first.

The image ships explicit `width` and `height` with `loading="lazy"` and `decoding="async"`, so it reserves its own box and contributes nothing to layout shift.

Set `link={false}` only where the badge already sits inside another anchor. Nesting anchors is invalid HTML and browsers recover from it unpredictably.

## Referral code

Set `refCode` and the badge links back with a `?ref=` param, so visits it sends can be credited to you.

```tsx
<BuildBaseBadge refCode="acme-studio" />
```

```
https://www.buildbase.app/?utm_source=badge&utm_medium=built-with&ref=acme-studio
```

The code is coerced rather than validated: lowercased, narrowed to letters, numbers, hyphens and underscores, and truncated at 64 characters.

```tsx
<BuildBaseBadge refCode="Acme Studio" />   // → ref=acme-studio
<BuildBaseBadge refCode="!!!" />           // → no ref, plain badge link
```

That is the rule for the whole component: **a bad value costs the attribution, never the render.** This runs in the render path of your footer, so a code someone typed with a space in it must never throw inside your layout.

If you let users enter their own code, `coerceBadgeRef` is exported so you can show them what it will become before they save it:

```tsx
import { coerceBadgeRef } from '@buildbase/sdk/react';

coerceBadgeRef('My Studio'); // 'my-studio'
coerceBadgeRef('!!!'); // undefined
```

### Why the prop is `refCode` and not `ref`

React intercepts a prop named `ref` on React 18 and never passes it to a function component. React 18 is still in this package's peer range, so a prop spelled `ref` would work on React 19 and silently send no attribution at all for every React 18 consumer, with the badge rendering perfectly the whole time.

The query param is `ref`. Only the prop is renamed.

### `refCode` sets `ref` and nothing else

It does not also write `utm_campaign` for you. A referral is _who sent someone_; a campaign is _which piece of marketing they clicked_. Deriving one from the other turns campaign reporting into a list of customer names and overwrites the name of a real campaign that happens to carry a referral too. If you want both, send both:

```tsx
<BuildBaseBadge refCode="acme" params={{ utm_campaign: 'spring-launch' }} />
```

```
...&ref=acme&utm_campaign=spring-launch
```

## Affiliate links

If you are on an affiliate program, the tooling generates links with `?via=`. That param is accepted too, and it stays separate from `ref`:

```tsx
<BuildBaseBadge params={{ via: 'dharmendra' }} />
<BuildBaseBadge refCode="acme" params={{ via: 'dharmendra' }} />
```

```
...&via=dharmendra
...&ref=acme&via=dharmendra
```

The two mean nearly the same thing, which is exactly why they do not merge. `ref` is ours, from a badge or a hand-built link. `via` belongs to an affiliate program with its own ledger. Which one a visit arrived on is what decides where the credit gets settled.

## Any other params

`params` is appended to the badge's link verbatim. Nothing is filtered for being unrecognised — what we choose to interpret on arrival is our business, not something that should limit what you put in a link on your own site.

```tsx
<BuildBaseBadge params={{ plausible_id: 'footer', anything_at_all: 'yes' }} />
```

```
...&plausible_id=footer&anything_at_all=yes
```

Keys and values go through `URLSearchParams`, so there is no character you have to avoid or escape first:

```tsx
<BuildBaseBadge params={{ note: 'a&b=c d?e#f', emoji: 'hi 🌍' }} />
```

```
...&note=a%26b%3Dc+d%3Fe%23f&emoji=hi+%F0%9F%8C%8D
```

`null` and `undefined` entries are skipped, so you can build the object conditionally without littering the URL with empty params:

```tsx
<BuildBaseBadge
  params={{ a: 1, b: true, skipped: null, alsoSkipped: undefined }}
/>
```

```
...&a=1&b=true
```

Params are written last, so they can deliberately override the badge's own `utm_source` and `utm_medium` if your naming scheme needs it:

```tsx
<BuildBaseBadge variant="mark" params={{ utm_source: 'partner-site' }} />
```

```
https://www.buildbase.app/?utm_source=partner-site&utm_medium=mark
```

## What reaches us

Sending a param and us acting on it are two different things. On arrival we read an allowlist and ignore everything else:

| Param                                                                                                       | What it says                        |
| ----------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| `ref`                                                                                                       | Who sent the visitor, from a badge  |
| `via`                                                                                                       | The affiliate partner who sent them |
| `utm_source`, `utm_medium`, `utm_campaign`, `utm_id`, `utm_content`, `utm_term`, and the GA4 additions      | Which piece of marketing it was     |
| `gclid`, `gbraid`, `wbraid`, `fbclid`, `msclkid`, `li_fat_id`, `ttclid`, `twclid`, `rdt_cid` and the others | The ad network's click id           |

Your own params still reach your analytics through the URL; they are simply not something we interpret.

Nothing about this runs on your page. The badge is an image tag: no JavaScript, no cookie, no tracking pixel. The referral code travels on the link only, never on the image URL, which is what keeps the image the same cached file for everyone.

## Version

`refCode` and `params` were added in **0.0.59**. Earlier versions accept `variant`, `theme`, `size`, `alt`, `className` and `link` only, and silently drop anything else.

`coerceBadgeRef` needs **0.0.60**. It was documented in the 0.0.59 notes but not actually exported from `/react` in that release, so the import above does not resolve on 0.0.59 and there is no workaround short of upgrading.
