# Signage-as-Code > A category of digital signage where screens are frontends that read content from upstream systems and keep rendering when the network drops. Headless. Offline-first. Boring on purpose. Coined and maintained by Gibeon. --- ## Your screens are frontends. They keep rendering when you're not looking. Signage-as-Code is **headless** and **offline-first**. Content stays in the systems that already own it — your CRM, your PIM, your calendar, your warehouse. The screen reads, renders, and keeps rendering when the network drops. This document is the entire site as one markdown file. If you are an AI agent or developer, this is the full context. --- ## The manifesto — six principles for screens that don't go dark Signage drifted into the world of creative tools and never came back. We're putting it back where it belongs — next to your codebase, your CI, your data warehouse, your incident channel. ### 1. The screen reads. It does not store. Content lives in the systems that already own it — CRM, PIM, calendar, warehouse. The screen is a render-target, not a destination. If a number changes in your data, it changes on the wall. Once. ### 2. And keeps reading when it can't. Network drops are non-events. The player holds a snapshot of the world; the world changing doesn't break the player. Reconnect syncs the diff. A black screen during a fire drill is a bug with an owner. ### 3. Snapshot is the contract. Publish writes a complete, self-contained snapshot. Render reads only from snapshots. No live-build fallback, no half-applied changes, no "it worked on the demo unit." ### 4. Composable, not monolithic. Pick your data sources. Pick your layouts. Pick your delivery. The vendor doesn't own each layer. The CMS-of-old conflated render and content because it had nowhere else to put them. Today, content already lives somewhere. ### 5. Hardware is replaceable inventory. A screen has a name — `lobby-tv` — not a serial number. Swap the box behind the TV; nothing upstream changes. The file is the asset. The hardware is replaceable. ### 6. API or it doesn't exist. A feature you can only do in the dashboard is a feature only one person can do, slowly, while logged in. Every behaviour is automatable, scriptable, observable. From day one. For free. ### Coda: Boring — on purpose. The dramatic part of signage is the content on the glass. The plumbing should be dull, predictable, and ignored. That's the goal. --- ## Brand is a source too. Your marketing site has your brand. Your CRM knows what's happening today. So when twelve locations need to say *"Closed today"* by 09:00, you don't open a design tool. ```ts await g.comet('lobby-tv', { say: 'Closed today.' }) ``` We read your brand from where it already lives — your site, your logo, your tone. We read *"closed today"* from where *that* lives — your calendar, your CRM, your POS. The screen composes the two. We call this **a comet**. A short, branded message that lands on a screen without a designer in the loop. It's what headless looks like when your brand is upstream too. `g.bind(...)` is for the continuous things — listings, prices, KPIs that stream forever. `g.comet(...)` is for the one-shot things — today's closure, this morning's incident, the next hour's promo. --- ## Old way vs. new way Same medium. Two universes. It's 4:47pm on Friday. The lobby still says "Q3 All-Hands". The Q4 launch starts in fourteen minutes. The wifi is the wifi. ### The old way (CMS · drag · publish · pray) 1. Log in to the vendor portal 2. Find the right screen, in the right folder 3. Open the playlist editor 4. Upload the new asset (wrong size) 5. Re-export. Re-upload. 6. Drag to position 3, before "Coffee Break" 7. Set start time, set end time 8. Click Publish 9. **Wifi blips — screen goes black** 10. **Lobby still showing Q3 All-Hands** Time: 38 min. Clicks: 47. Single points of failure: 4. ### The new way (read · render · survive the wifi) 1. Send a comet: `say: 'Q4 launch · 17:00'` 2. Brand picked up from your marketing site 3. Wifi blips — screen keeps rendering the last snapshot 4. Wifi back — new snapshot syncs 5. Lobby shows Q4 launch Time: 90 sec. Lines of code: 3. Single points of failure: 0. --- ## The architecture — two phases, three layers, one contract ``` [Your systems] → [Snapshot] → [Screens] CRM, PIM, A complete, Read-only. calendar, self-contained Cached locally. warehouse, description of Survive the network. marketing site the world. 5-year lifetime. (publish writes, render reads.) ``` - **Publish is a write. Render is a read.** The two phases never talk directly. Publish writes a snapshot. Render reads a snapshot. No live-build fallback path; the absence of a snapshot is a 503 with a fixable message, not a silent half-render. - **The snapshot is the boundary.** On one side: your messy, evolving systems-of-record. On the other: deterministic playback for years. The snapshot is what makes signage durable. - **Failure is a declared route.** `on_error: fallback-loop`. `alert: #screens-pagerduty`. A screen has owners, escalation paths, and a known last good state. Signage stops being a special case and joins the rest of production. --- ## FAQ ### Isn't this just a headless CMS for signage? Yes — plus offline-first. A regular headless CMS assumes the client can call back to the API. A lobby TV in a basement on hotel wifi cannot make that assumption. Headless gets you the architecture; offline-first is what makes it survive the medium. ### What's a comet? A one-shot, branded message that lands on one or many screens without a designer in the loop. You write the sentence; the system reads your brand from upstream (your marketing site, your logo, your tone) and renders the scene. It's the natural consequence of treating brand as just another upstream source. ### Do my marketers have to learn to code? No. Marketers change content where they already do — the CRM, the PIM, the campaign tool. Signage-as-Code is the pipe between those systems and the glass. The people who maintain the pipe are the same people who maintain your website, your CI, your integration layer. ### What about WYSIWYG? I want to see what I'm changing. You still do. Every change renders a preview of every affected screen, at the right resolution and orientation. You approve a snapshot. You publish. The wall changes. WYSIWYG without the YG being a single point of failure. ### Five years in a lobby? Really? The medium has a long tail. A reception screen lives longer than the team that installed it, the laptop that configured it, and the vendor that sold it. The architecture has to match the medium — deterministic snapshots, replaceable hardware, named screens, owned failures. ### Who's doing this today? The category is new; the practice isn't. Teams have been gluing playlists, cron jobs, and integration scripts together for years. Signage-as-Code names the pattern. The reference implementation — the one this site is maintained by — is [Gibeon](https://gibeon.io). Others are welcome. ### How do I control a digital signage screen with code? Three steps. (1) Sign up free at [gibeon.io](https://gibeon.io) — the reference implementation. (2) Install the SDK: `npm install @gibeon/sdk`. (3) Bind a screen to a source: `g.bind('lobby-tv', { source: yourCRM.listings })`. The screen reads, renders, and keeps rendering when the network drops. No dashboard, no playlist editor, no upload step. ### What's the fastest way to put something on a screen? A comet: `g.comet('lobby-tv', { say: 'Closed today.' })`. One sentence, one line. The system reads your brand from upstream (your marketing site, your logo, your tone) and renders the scene. No design tool, no upload, no scheduling. You write what should be on the wall — it appears. --- ## Quickstart — free, three lines, your screen works The manifesto is free. The category is open. The reference implementation is one signup away — no credit card, no onboarding call, no "contact sales". ### Step 1 — Sign up free at gibeon.io The reference implementation. Free tier. No card. Get your SDK token from the dashboard. ### Step 2 — Install the SDK ```bash npm install @gibeon/sdk ``` ### Step 3 — Bind a screen, or send a comet ```ts import { Gibeon } from '@gibeon/sdk' const g = new Gibeon({ token: process.env.GIBEON_TOKEN }) // continuous: source streams forever await g.bind('lobby-tv', { source: yourCRM.listings }) // one-shot: branded message, no design tool await g.comet('lobby-tv', { say: 'Closed today.' }) ``` That's it. Coder, AI agent, or human at a keyboard — no thinking required. The plumbing should be dull, predictable, and ignored. --- ## Vocabulary — hold these consistent - **bind** — continuous data-binding between an upstream source and a screen. Example: `g.bind('lobby-tv', { source: yourCRM.listings, refresh: '5m' })`. For listings, prices, KPIs, calendars — anything streaming. - **comet** — one-shot branded message on one or many screens. Example: `g.comet('lobby-tv', { say: 'Closed today.' })`. The system reads your brand from upstream and renders. **Not** "AI scene generator" — the term is *comet*. - **snapshot** — a complete, self-contained description of what a screen should render at a given moment. Publish writes; render reads. --- ## Reference implementation [Gibeon](https://gibeon.io) — the canonical implementation of Signage-as-Code. Multi-tenant SaaS digital signage platform, multi-vertical (retail, hospitality, real estate, corporate, events). Workspace pricing (per team), not per-screen. Free tier exists. --- ## License A category coined and maintained by Gibeon. Free to adopt. Free to fork. Free to ignore.