Customising BoothWall All guides ▾
A wall is yours once you’ve changed two things: one config file and a handful of images. You shouldn’t need to touch the app code.
The config file
packages/shared/src/config/boothwall.config.ts is read by the TV app, the web app and the API when they build. It’s checked against a schema every time, so a typo fails the build instead of your booth.
The copy in the repo holds example values. Change at least event and categories, then run pnpm boothwall setup: it creates the Cloudflare resources and fills in urls and deploy. Setup stops if a configured database ID does not match the exact database name in your selected account. It never silently adopts another target or clears that ID.
| Field | What it does |
|---|---|
event.name |
The event name on capture and display, also used as the logo accessibility label. |
event.hashtag |
Shown on the wall and photo cards. Starts with #. |
event.venue |
Optional venue text for the guest page. |
event.year |
Optional four-digit year beside the display logo. |
categories |
Configured category IDs and labels. IDs are stored with photos; preserve them when renaming a label. A catch-all makes category selection optional. |
settings |
File-authoritative values such as captionStyle, secondsPerPhoto, autoOpen, showWhoIsHere, autoApprove and uploadsClosedMessage. Control changes only durable playbackPaused/uploadsOpen state. |
theme.primary |
The main brand colour: buttons, hashtags, highlights and the glow around the selected photo. |
theme.secondary |
Category labels on polaroids, the category chip in the spotlight and the guest page. |
theme.tertiary |
A spare accent. The default design doesn’t use it; on the web it’s there as --color-tertiary. |
urls.api |
Your API. Filled in by pnpm boothwall setup. |
urls.web |
Your web app: the wall at /, the upload page at /snap (where the TV’s QR code points) and /control. Filled in by setup. |
urls.source |
Where your code lives, for the “Scan for the source” card on the TV. |
author.handle |
The small credit on the TV and the upload page. |
badge |
Optional. A small label beside the logo on the wall and in the phone header, like “Built on Vega OS” or “Booth 14”. Leave it out to show none. Up to 32 characters. |
credits |
Optional. false hides the source link under the phone pages; true also shows the “Scan for the source” card on the wall, which is off by default so guests only see the join code. |
demo |
true runs the TV and the web wall on the bundled photos, with no API or network. Setup turns it off. |
retentionDays |
Photos are deleted this many days after upload, unless marked keep through the API. The consent text quotes it. |
deploy.name |
The base name for your Cloudflare resources: Workers <name>-api and <name>, D1 database <name>, R2 bucket <name>-photos. Set it before the first setup. |
deploy.d1DatabaseId |
Filled in by setup. |
deploy.apiDomain, deploy.webDomain |
Custom domains on a zone in your Cloudflare account, or "" for workers.dev. Set them before setup. If you change them later, run pnpm boothwall deploy; it records discovered URLs and rebuilds guest links/CORS as needed. |
deploy.extraApiDomains |
Optional. Other domains the API keeps answering on after a move, because installed TV apps have the old address built in. |
tv.wallId, tv.displayCredential |
Optional. The TV app’s one wall (default unless your host serves others) and a display credential, only if your host’s wall resolver checks one. Both are fixed at build time: see Your own TV build. |
Use two separate subdomains for the API and the web app, like wall.example.com and wall-api.example.com. Cloudflare’s free certificate covers one level of subdomain, so api.wall.example.com would need a paid one.
Colours
Pick the colours you like. Text drawn on a brand colour is black or white, whichever reads better, and a brand colour used as text (a hashtag on the dark TV, a category on a white polaroid) is shaded just enough to stay readable.
The shared surface tokens distinguish guest/display pages from host tools. The standalone app exposes display, capture and basic Control. It contains no feed, recap, sample-sender or print-designer page.
Wall themes
The wall has four themes selected through settings.theme in configuration, shared by the TV and browser renderer. Universal is the wall as it ships: your brand colours over the night sky. Wedding is a warm evening with fairy lights, gold sprigs and drifting petals; party a club with neon shapes, lasers and falling confetti; conference a main stage with spotlights sweeping over a glowing floor. Each event template picks one.
A theme is four things: a palette (accent, partner colour, canvas and card tone) in packages/wall-ui/src/theme/tokens/palette.ts (WALL_PALETTES), its art, the words on its join card (JOIN_VOICE in packages/wall-ui/src/features/wall/constants/copy.ts) and its name in WALL_THEMES (packages/shared/src/settings/wall-settings/wall-settings.schemas.ts). The art is drawn in code by node scripts/themes/render.mjs (needs rsvg-convert and ImageMagick): a backdrop with the still ornaments painted in, plus the few layers the wall moves on top (features/wall/components/decor). Movement stays slow and native-driven: one texture sliding or turning per layer, so the Fire TV Stick never redraws.
To add one, give it a name in WALL_THEMES, a palette, its words, its art and the corresponding renderer configuration support; TypeScript then points at every place that needs a value, and a test checks its text stays readable. Components never read a theme colour directly: they use useWallTheme() or themedStyles() from theme/wallTheme.ts, so a theme switch costs the TV nothing per frame.
Brand images
Replace these with your own, keeping the file names. Any logo aspect ratio works.
| File | Used for | Size |
|---|---|---|
packages/wall-ui/src/assets/brand/logo.png |
The wall’s top bar (TV and web) | about 800×180, transparent |
packages/wall-ui/src/assets/brand/backdrop.jpg |
The wall’s background | 960×540, already darkened |
apps/tv-vega/assets/image/app_icon.png |
The Fire TV app icon | 512×512 |
apps/web/src/assets/brand/logo.png |
The upload page and phone header | about 800×180, transparent |
apps/web/src/assets/brand/glow.jpg |
The glow behind the phone pages | 1600×1000 |
apps/web/public/favicon.ico, favicon.svg, apple-touch-icon.png, pwa-*.png, site.webmanifest |
Browser, home-screen and install icons | 16 to 48 px ICO, 180×180, 192 and 512 px |
packages/wall-ui/src/assets/demo/demo-*.jpg |
Demo mode only | about 600×600 |
The logo, the backdrop’s twinkle layer and the glow are rendered from the SVGs in brand/ (see its README for the commands); the app icons are exports of the same symbol. The backdrop itself is a photograph. Keep it dark on the left, where the photos sit: the wall draws it as it is, with no dimming layer on top, and only two soft glows and the twinkle move over it. Keep it at 960×540 too. The TV scales it up anyway, and a full 1080p image only costs memory on a Stick.
The demo photos (demo-01 to demo-24) are booth photos with made-up captions. Their captions and categories are in packages/wall-ui/src/features/wall/data/demoPhotos.ts. Swap in your own there, or leave them; they only show while demo is on or with ?demo in the browser.
Examples
examples/ holds complete setups that mirror the repo layout. pnpm boothwall example conference-booth applies one, and git checkout -- apps packages undoes it. If you run BoothWall at your event, a pull request with your setup is welcome, as long as you’re allowed to share the artwork.
Existing saved settings
Fresh installations take presentation settings from the config. Existing plain database records retain their effective overrides until the explicit settings authority migration is reviewed and applied. Back up the database and inspect authenticated GET /settings and its authority header before an upgrade; do not delete saved settings to force a change. The basic Control page changes only durable playback and upload-intake state. See older review settings for unsupported-provider migration decisions.
Setup and safe reruns
pnpm boothwall setup validates configuration before contacting the provider, then shows the account ID, database, bucket, Worker names and custom domains before asking to continue. It checks exact database identity, bucket names and domain-zone ownership. If your login lists more than one account, set CLOUDFLARE_ACCOUNT_ID explicitly. The same account is pinned for every resource and deployment command.
Setup stores a local account/name binding in apps/api/.dev.vars.setup.json (gitignored). An interrupted run inspects and reuses matching resources; it does not delete data. A later run cannot silently switch the recorded account or wall name. Use a separate checkout for another stack. If the binding or a provider response is invalid, investigate before retrying rather than deleting the record to bypass the check.
The Control passcode stays in apps/api/.dev.vars, with owner-only permissions. Setup preserves existing values and unrelated lines; it never prints a generated passcode or overwrites one on a rerun. Read the file locally to obtain it. If an older local demo left an eight-digit passcode, explicitly replace it with a strong 12–256 character value before setup. Letters, digits and the documented safe punctuation are accepted; whitespace, quotes, backslashes and #/$ are not accepted for a new passcode. Existing quoted or colon assignments are read with the provider’s dotenv rules; multiple ADMIN_TOKEN definitions stop setup instead of choosing the last value. Provider deployment output that may contain secrets is withheld from terminal errors.
setup, deploy and remote migrate confirm their target. After inspecting that target, automation may pass --yes; validation and account/resource checks still run. migrate --local uses only local state and never falls back to a remote operation. Unknown or repeated flags are errors.
On success, setup/deploy print the exact display, /snap and /control URLs. Configuration changes while a confirmation is open or between commands stop the operation. Keep database/storage backups independently; the local binding is not a backup.
Before applying migration 0009 to an existing installation, follow the client fingerprint maintenance update. It requires a separate random server key and drained traffic; the CLI does not create that maintenance window.
For an existing installation, follow the saved-settings migration: preview first, review and deploy the exact config patch, then explicitly apply it. Migration 0010 protects the retained original record and runtime flags; deploy alone never switches an old record to file authority.
Serving the web app under a path
To put the wall on part of another site, give urls.web a path: https://example.com/wall. The web build then expects to be served from /wall/: its pages, /wall/snap and /wall/control, and every asset, link, redirect, install manifest and QR code carry the path. The API accepts requests from that site’s origin.
- Serve the files from
apps/web’s build output under that path, with a static host or a proxy, and answer any unknown address below it with the build’sindex.htmlso the pages load on reload. - Match the path case-insensitively. The TV’s “Scan for the source” QR is upper case, path included.
pnpm boothwall setupanddeploypublish the web app at the root of its own address, so they refuse aurls.webwith a path. Host the build yourself instead.
Your own TV build
The Vega TV app is source you build yourself. There is no store listing and no download: you build it with your own config and install it on your own TVs.
- It shows exactly one wall, the one in
tv.wallId, at the API inurls.api. There is no code to enter and no menu on the TV. To show a different wall, change the config and rebuild. - Everything in the config is bundled into the app. Anyone who has the built app can extract those values, including a display credential, so keep your build private and never publish the
.vpkg. - No build at all? Open the web wall in the TV’s browser or on an HDMI stick instead.
After a change
- API or web app:
pnpm boothwall deploy. - TV: rebuild and reinstall (
pnpm tv:build:release, thenvega run-app …). The config is bundled into the app, so a TV keeps the old values until you do.