Embeddable widgets
Put live CivicGate data on your own page. There are two ways to do it and they render the same widgets: an iframe embed (one HTML tag, no JavaScript) or the script bundle / SDK (widgets mounted into your own DOM, so they inherit your layout). Most widgets need no key and no account, and every one links back to the source data. The examples below are live — edit the code and the widget beside it re-renders.
1 · Script bundle / SDK
One script tag, one configure() call, and a marker element per widget. Widgets mount into your own DOM.
CivicGate widgets rendered into your own DOM.
- Inherits your layout — it flows and grows with its content, so no height guessing.
- Configure once — one endpoint and key for every widget on the page.
- One shared client, cache and refresh loop across every widget, instead of one page load each.
- Programmable —
CivicGate.mount()after injecting markup,CivicGate.refresh()when you know the data changed.
Pick this when the widget should look like part of the page, or several widgets share one.
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script>
<script>
CivicGate.configure({ apiKey: "cg_live_…" }); // only needed for a following feed
</script>
<!-- a person card, by the slug in their CivicGate URL — no key needed -->
<div data-cg-widget="person" data-slug="bernard_sanders"></div>
<!-- one bill's public wall — no key needed -->
<div data-cg-widget="wall" data-type="bill" data-id="hr-22-119"></div>
<!-- report a civic issue for a place, and list what is already reported —
no key, and anonymous reporting works with no account at all -->
<div data-cg-widget="issue-report" data-zip="19104"></div>
<div data-cg-widget="issue-topics" data-zip="19104" data-limit="10"></div> Available in the bundle: Person card, Subject wall, Following feed, Report an issue, Place locator, Place wall — Latest for a place, Ballot — what a place is voting on, Place Discussion, Login with CivicGate. The map and graph widgets are iframe-only — shipping a choropleth renderer in the bundle would cost every page that only wants a card.
Attributes every widget understands (4)
| Attribute | Accepted values | Default | What it does |
|---|---|---|---|
data-cg-widget | person | wall | feed | issue-report | place | place-wall | ballot | issue-topics | login | — (required) | Which widget to mount. This attribute is what makes the element a CivicGate widget. |
data-cg-key | A read-only API key (cg_live_…). | the key from CivicGate.configure() | Per-element override of the page key. Only the following feed needs one. |
data-cg-gateway | An absolute GraphQL endpoint URL. | https://www.civicgate.org/graphql | Per-element endpoint override, for a self-hosted CivicGate. |
data-show-loading | 1 | 0 | 0 | Show a “Refreshing…” line during background refresh. Off by default — a sidebar widget should not flicker. |
data-cg-widget is carried by every widget. The three client attributes
configure a widget's own connection to the gateway, so they apply to the widgets that
render into your DOM — Person card, Subject wall, Following feed, Place wall — Latest for a place.
The two issue widgets mount a CivicGate-origin iframe that holds no client and no
credential, so they ignore those three and take
data-cg-site instead; each widget's own table says which it accepts.
JavaScript API (4)
| Call | What it does |
|---|---|
CivicGate.configure({ apiKey, gatewayUrl, site }) | Sets the API key, endpoint and CivicGate origin for every widget on the page, then re-scans the document. Per-element data-cg-key / data-cg-gateway / data-cg-site override it. site is read by the framed widgets — the two issue widgets and the login widget — as the origin their iframe loads from. |
CivicGate.mount() | Scans the document for un-mounted markers. Call it after injecting widget markup dynamically. |
CivicGate.refresh() | Forces every mounted widget to re-fetch now, instead of waiting for its background refresh. |
CivicGate.takeLoginVerifier() | On your OAuth callback page: returns { state, verifier, clientId, redirectUri } — what the login widget minted on your origin before sending the reader to CivicGate — or null. Read-once: it clears the value, because a verifier is good for exactly one exchange and one left in storage is a secret with no remaining use. |
Using the SDK directly (bundlers)
An app that already has a build step can talk to the same API through @cg/sdk — the same client the bundle uses, so it can never rely on an undocumented query. One kernel per page owns the endpoint, the credential, one cache and one request de-duplicator, so two widgets asking for the same person cost one request.
import { createKernel } from "@cg/sdk";
const cg = createKernel({ gatewayUrl: "https://www.civicgate.org/graphql" });
// people: cached, de-duplicated, resolves slug | alias slug | bioguide id
const person = await cg.people.get("bernard_sanders");
// a public wall — no credential
const wall = await cg.client.subjectFeed("bill", "hr-22-119", 10); 2 · iframe embed
Every widget with an /embed/… URL can be dropped into a page as a single iframe. No script, no build step.
A CivicGate-hosted page in a box on your site.
- Isolated — our CSS and JS cannot touch your page, and yours cannot break ours.
- No JavaScript required — works in a CMS field, a blog post, anywhere HTML goes.
- You must set a height — a cross-origin frame cannot size itself.
- Does not inherit your fonts or theme; it follows the reader's system light/dark preference.
Pick this when you want the fastest integration, or you don't control the page's JavaScript.
<iframe src="https://www.civicgate.org/embed/person/bernard_sanders" width="100%" height="150" style="border:0" loading="lazy" title="CivicGate widget"></iframe> Available as an iframe: Federal funding map, In-state funding by district, Member connections graph, Person card, Subject wall, Following feed, Report an issue, Place locator, Place wall — Latest for a place, Ballot — what a place is voting on, Place Discussion, Login with CivicGate.
Credentials — what needs a key
Data ownership decides the credential, and the rule is identical in both integration modes.
Who owns the data decides the credential. Public data needs none, in either mode. Exactly one widget shows an account's own view, and only that one takes a key.
| Widget | Whose data | Credential |
|---|---|---|
| Federal funding map | Public | Nothing |
| In-state funding by district | Public | Nothing |
| Member connections graph | Public | Nothing |
| Person card | Public | Nothing |
| Subject wall | Public | Nothing — a key passed alongside a subject is IGNORED |
| Following feed | One account's own subscriptions | That account's read-only API key |
| Report an issue | Public to read; the report is the reader's own | Nothing — a configured key is IGNORED, and anonymous reporting is fully supported |
| Reported issues | Public | Nothing — a configured key is IGNORED |
| Login with CivicGate | The reader's own identity, released to YOUR application only by their consent | No API key. An application mode needs a registered client id + redirect URI, which are public values, not secrets |
- Keys are read-only. The gateway rejects every mutation on a key-authenticated request, so a key pasted into a public page cannot write to the account it belongs to. Create and revoke them under Settings → API keys; see API keys for the full contract.
- A key never widens visibility. A key-authenticated request is treated as ANONYMOUS, so an embedded key shows a reader no more than an anonymous visitor would see.
- A wall ignores a key. Passing one alongside a subject spends someone's rate limit on data that needed no identity, so the widget drops it deliberately.
- No key appears on this page. The live editors below read public data with no credential; the one editor that needs an identity uses your own CivicGate session, because on civicgate.org the gateway is same-origin. That path is not available from your site — there you pass a key.
Identity — what a widget can know about your reader
A ladder, best-available first. The top rung is not always reachable, and the honest limit is stated rather than worked around.
Silent cross-site identity is not available on first contact, and it is not coming
back. A widget on example.org cannot quietly recognise a CivicGate member
the first time they arrive. That is not a gap in the implementation — it is the thing browsers
are deliberately removing, because a mechanism that "just knows" who you are on an unrelated
origin is third-party tracking. We say so here so that nobody spends an afternoon
filing it as a bug.
Anonymous works in every case. Reporting an issue, reading a wall, reading a listing and reading a person card all work with no identity at all, so identity is an enhancement in CivicGate's widgets and never a gate. What identity changes is whether a report carries an author's name, and whether the reader sees their own saved place instead of typing a ZIP.
The ladder, best available first. A widget takes the highest rung it can reach and reports which one answered:
| # | Path | Works when | Silent? |
|---|---|---|---|
1 | Same-origin session | The widget is on civicgate.org itself. | Yes |
2 | FedCM | The browser supports navigator.credentials.get({ identity }) and the reader has a live CivicGate session. | Yes — one tap, no popup |
3 | Identity iframe + postMessage | Storage is unpartitioned, or the Storage Access API grants access. Your origin must also be on the allow-list. | Sometimes |
4 | Sign-in window | Always. Needs a click — it cannot be triggered without a user gesture. | No |
5 | Anonymous | Always. A first-class case here, not a degraded one. | n/a |
- There is no session cookie to read. A CivicGate session is a bearer token in
localStorage, which is origin-scoped — so a third-party page cannot read it, and even as a cookieSameSiteplus third-party-cookie deprecation would end it. We will never probe for one from a hidden iframe: that is precisely the pattern being removed, and it fails silently as browsers ship the change, which is the worst possible failure mode. - “Unknown” is not “anonymous”, and the difference is load-bearing. Inside a third-party frame the storage bucket is partitioned, so a missing session means we could not tell — not there is no session. Reporting that as signed-out would show a signed-in member a sign-in button forever, so every widget offers the sign-in affordance on
unknownand never asserts absence. - Rung 3 is partitioned by default in current Chrome and Safari: an iframe from civicgate.org on your page gets its own bucket unless the Storage Access API grants otherwise, which needs a user gesture and prior first-party interaction. Your origin must also be on the allow-list — a deliberate gate, not an oversight, because silent identity to an arbitrary origin is exactly what we must not offer.
- After a sign-in, the token is scoped to YOUR site. It does not silently appear on the next site the reader visits. FedCM is the closest thing to real cross-site SSO that still exists, and it is a browser-mediated one tap — not a silent read.
- The bridge sends a profile, never a token. A third-party page gets enough to personalise ({ signedIn, userId, displayName, username, zip, state }) and nothing that could act as the user. Both directions are origin-checked.
- An API key must never widen identity. A key-authenticated request is treated as anonymous, so a widget holding a key gets anonymous reads and must not present itself as a signed-in member.
- Your page has to do nothing. Include the bundle and rungs 1, 2, 4 and 5 all work as they are. Rung 3 — the silent one — is the only thing that needs the allow-list.
The sign-in button — whose name is on it
The identity server publishes its own name and icon, so every sign-in surface renders the same thing.
Wherever a widget offers sign-in it renders “Sign in with CivicGate” with CivicGate's icon. Neither string is hard-coded in the widget: the identity server publishes its own identity and every surface reads it.
| Field | Example | What it is for |
|---|---|---|
slug | civic-gate | Machine id. Never shown; safe to key a cache on. |
name | CivicGate | The full label. |
shortName | CivicGate | Optional abbreviation for a narrow button. The server says what it is; the widget decides when to use it, because only the widget knows how wide its box is — and in an embed that is your column, not the screen. |
iconUrl | …/icon-192.png | Absolute, because it renders on origins that are not ours. |
methods | ["password","oidc","fedcm"] | What this deployment actually serves. A method absent here is never offered — a dead sign-in button is worse than none. |
Served publicly at https://auth.civicgate.org/api/v1/provider (and /.well-known/auth-provider), CORS-open and cached for five minutes.
- The label degrades, the accessible name does not. As the button narrows it goes full name → short name → simply “Sign in”, but the
aria-labelstays complete, so a screen reader always hears which provider it is. - It paints before the identity arrives. The point is signing in, not seeing a logo; the name and icon fill in a moment later rather than blocking the button.
- Self-hosting? This is one provider per auth-server installation — the server is the provider. Set
AUTH_PROVIDER_NAME,AUTH_PROVIDER_SHORT_NAMEandAUTH_PROVIDER_ICON_URLand every surface follows, so your deployment never advertises somebody else's brand. An unconfigured install reads as generic rather than as CivicGate, which is the failure that gets noticed and fixed.
How a widget behaves
The lifecycle every CivicGate widget follows — so a page with three of them has three that behave the same way.
Every client-rendered CivicGate widget follows the same sequence. Learn one and you have learned all of them.
- Skeleton — first paint is a shaped placeholder occupying the box the loaded widget will fill, so your page does not reflow under the reader's cursor.
- Cache read — cached data paints before any network round trip, so a returning reader sees content immediately. A cache entry is scoped to the credential that produced it and is discarded when the key changes.
- Background refresh — revalidates on mount, on an interval and on tab focus, and pauses while the tab is hidden. A failed refresh never blanks a working widget.
- Update only on change — identical data is not re-rendered, so scroll position and expanded rows survive a refresh.
- Self-contained errors — a widget renders its own failure, in its own box, in plain words. It never prints a raw API error into your page and never throws into your error handler.
- Controls —
CivicGate.refresh()forces a revalidation;data-show-loading="1"makes a background refresh visible (off by default).
| State | What the reader sees | What it means |
|---|---|---|
| Loading | A shaped skeleton | First paint, or a refresh with nothing cached. |
| Empty | “No recent updates.” | Not an error. Nothing has happened for this subject recently — we say so rather than showing a blank box. |
| Not found | “We don't have a record for this yet.” | The id or slug is wrong, or that entity is not ingested. Check your markup. |
| Not public | “This isn't public.” | The subject is private. No further detail is given, deliberately. |
| Error | “Couldn't load this right now.” + retry | Transient. The technical detail goes to your browser console, not to your readers. |
Every attribute and prop each widget accepts is listed on that widget below, and the shared value lists live in Reference.
Basic
Login, place and reader state. The other widgets integrate these automatically — you rarely need to place them yourself unless you want the control on the page.
Place locator
Names the reader's place and lets them change it — country down to ZIP. Every other widget on the page follows the place chosen here.
Available as: iframe (/embed/place) · script bundle (data-cg-widget="place")
See every place level and what each one needs →
Any page — CMS field, blog post, plain HTML. No build step.
data-view="inline" Default. Label + dropdown ladder + inline ZIP edit.
data-view="header" One row, ZIP · District. The /place page header.
data-view="tree" The whole ladder. Click a rung to re-scope; ↗ opens its page.
This place
- Country
- Statenone
- Countynone
- Citynone
- Districtnone
- ZIPnoneCurrent
- Blocknone
- Addressnone
With no data-view set, all four views render from the same attributes — they share one place service, so changing the ZIP above moves every one of them. Set data-view to preview a single rendering.
Also embeddable as an iframe — no JavaScript:
<iframe src="https://www.civicgate.org/embed/place">. Options are the iframe parameters below.
iframe parameters (6)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
zip | A 5-digit US ZIP. Non-digits are stripped and the value is cut to 5. | — (the reader's own place, then nothing) | The place to start from. Given here it wins over the reader's saved location — a ZIP in the URL is a more deliberate statement than a saved address. |
mode | browse | navigate | browse | browse EMITS a cg:place-change event and shows an “Update results” button once the selection has drifted; your page decides when to refetch. navigate sends the reader to the level's own CivicGate page. The default is browse because navigating away would throw away the page the widget is embedded in. |
locked | 1 | 0 | 0 | Pin the place. The widget renders as a STATEMENT — no caret, no reset, no locate — and the service refuses writes rather than accepting them and being overridden on the next render. Use it when reports belong to your own jurisdiction rather than to wherever the visitor happens to live. |
compact | 1 | 0 | 0 | One row — ZIP and district side by side with an inline edit — and no tree. The shape CivicGate's own /place header uses. |
level | federal | state | county | city | district | zip | block | address | zip when a ZIP resolved, else the narrowest rung that did | Which rung to start on. Clamped to what actually RESOLVED: a rung whose fields we do not hold cannot be selected, though it is still shown in the tree (greyed, reading “none”) so the ladder is the same length everywhere. NOTE the top rung's token is federal — the tree DISPLAYS it as “Country”, but country is not an accepted value. |
levels | A comma-separated subset of: federal | state | county | city | district | zip | block | address. Unknown names are ignored. Again: the top rung is federal, not country. | the whole ladder | Restrict which rungs the tree draws. A typo yields the full ladder rather than an empty one — a silently empty picker is the worse failure. An explicit subset DOES hide what it excludes: narrowing the ladder on purpose is respected, whereas we never narrow it on your behalf. |
Script-bundle attributes (10)
| Attribute | Accepted values | Default | What it does |
|---|---|---|---|
data-zip | A 5-digit US ZIP. | — (the reader's own place) | The place to start from. |
data-mode | browse | navigate | browse | Emit and let you refetch, or navigate to the level's CivicGate page. |
data-locked | 1 | 0 (valueless means 1) | 0 | Pin the place. A BOOLEAN across every view, not a view of its own — so "the header shape, pinned" is expressible. Writes are refused by the service regardless. |
data-compact | 1 | 0 (valueless means 1) | 0 | Alias for data-view="header". |
data-view | inline | header | tree | inline | Which rendering. Views share all state; only the markup differs. |
data-edit-mode | inline | tree | inline | inline edits the ZIP where it stands. tree opens the ladder and edits its RUNGS — the only way to change a state or a county, since no ZIP means "Ohio". Ignored when data-view is already tree: the ladder is on screen, so it becomes editable rather than opening a second copy. |
data-hover | expand | none | none | expand opens the ladder as an overlay on hover. It floats — never reflows the page under the control being pointed at — and auto-hides 500ms after the pointer leaves, which is the grace the pointer needs to reach it. |
data-level | federal | state | county | city | district | zip | block | address | zip when resolved | Starting rung. |
data-levels | Comma-separated subset of the ladder. | all resolvable | Restrict the ladder. |
data-cg-widget | person | wall | feed | issue-report | place | place-wall | ballot | issue-topics | login | — (required) | Which widget to mount. This attribute is what makes the element a CivicGate widget. |
The greyed row is the marker every widget carries. This widget mounts a
CivicGate-origin iframe, which holds no client and no credential, so
the shared client attributes — data-cg-key, data-cg-gateway, data-show-loading —
have no effect here and are deliberately not listed. Use
data-cg-site to point at a different CivicGate deployment.
Component props (8) — the Preact / Astro path
| Prop | Accepted values | Default | What it does |
|---|---|---|---|
view | inline | header | tree | locked | inline | Which rendering. Views share all state — only the markup differs. |
gatewayUrl | An absolute GraphQL endpoint. | — | Required. |
zip | A 5-digit US ZIP. | the reader's saved place | Starting place. |
level | federal | state | county | city | district | zip | block | address | the narrowest rung that resolved | Starting rung. Clamped to what resolved. |
levels | A subset of the ladder. | the whole ladder | Restrict which rungs are drawn. |
mode | browse | navigate | navigate | browse emits and waits for your refetch; navigate opens the rung's CivicGate page. The bundle and iframe default to browse. |
compact | boolean | false | Alias for view="header". |
locked | boolean | false | Alias for view="locked". |
Emitted events (2)
| Event | Fires | event.detail |
|---|---|---|
cg:place-change | Every selection — a new ZIP, a new rung, locate, reset. | The whole PlaceState: { level, resolution: { zip, state, stateName, district, city, county, … }, source, status, locked }. |
cg:place-commit | browse mode only, when the reader presses “Update results”. | The same PlaceState. Use this if refetching is expensive; use cg:place-change if it is not. |
All on window. A framed widget re-posts its events to the host, where the bundle re-fires them — so one listener works whether you used the component, the bundle or an iframe.
Examples (2)
- Subscribe to the place
window.addEventListener("cg:place-change", (e) => { console.log(e.detail.level, e.detail.resolution.zip); }); // browse mode: the reader pressing “Update results” window.addEventListener("cg:place-commit", (e) => refetch(e.detail));Works for all three integrations: a framed widget re-posts its events and the bundle re-fires them on your window.
- Pinned to one place
<div data-cg-widget="place" data-view="locked" data-zip="19104" data-level="county"></div>A statement, not a control — for when the place is yours rather than the visitor's.
Notes (6)
- No credential. It resolves a public ZIP into public geography.
- “Use my current location” is hidden when signed out — the gateway refuses to spend the billable geocoding key anonymously.
- Unresolved rungs render greyed as “none”, never omitted: “not covered” and “not found” must be distinguishable.
- Address is never publishable. A reader may resolve their own, but any record snaps to the ZIP.
- All CivicGate widgets on a page share one place service, so a change here moves the rest with no wiring.
- The rung persists per browser tab (sessionStorage). An explicit level always wins over a remembered one.
<iframe src="https://www.civicgate.org/embed/place" width="100%" height="300" style="border:0" loading="lazy" title="CivicGate widget"></iframe> Login with CivicGate
A branded sign-in button. Signs a reader into your own application through CivicGate, or into CivicGate itself so the other widgets on the page know who they are.
Available as: iframe (/embed/login) · script bundle (data-cg-widget="login")
See every scope and what it gives your application →
Any page — CMS field, blog post, plain HTML. No build step. With data-client-id set, the bundle also mints a PKCE pair on YOUR origin before the button paints.
Checking your CivicGate sign-in…
This preview runs in CIVICGATE SIGN-IN mode, because an application preview would need your own registered client id and redirect URI. It is not in a frame, so it can see your real CivicGate session — on your own site that storage is partitioned and the widget shows the sign-in offer instead, which is the state most of your readers will see. Add data-client-id and data-redirect-uri and the button becomes a live link to YOUR application's authorization request. data-theme is frame-only and does nothing here.
Also embeddable as an iframe — no JavaScript:
<iframe src="https://www.civicgate.org/embed/login">. Options are the iframe parameters below.
Integration walkthrough (5 steps)
- Register an application and get a client id
An application is the record that says which redirect URIs may receive your readers' authorization codes. Create one under Settings → Applications; you get a client id, and — if you asked for a confidential client — a client secret shown exactly once. If that tab is not there for your account yet, registration is still administrator-only: ask us to register it, with the values in step 2. Registering a redirect URI is functionally granting someone the ability to receive other people's sessions, which is why it is a deliberate step rather than a form anybody can post to.
Settings → Applications →Ask us to register one →
A PUBLIC client (a browser app, no secret) must use PKCE, and every client requires it by default. Keep that in mind at step 4 — it decides which exchange you write.
- Add your redirect URI — exactly
List every URL that will receive the redirect: your production callback, your staging one, and http://localhost:PORT/... for development. Matching is EXACT. No wildcards (registration refuses them), no prefixes, no fragments, and https everywhere except localhost and 127.0.0.1. A trailing slash is part of the string. If the URI does not match, the reader is not redirected at all — the request stops at CivicGate with “redirect_uri is not registered for this client”, which is the correct behaviour and also the first thing to check.
https://example.org/auth/callback https://staging.example.org/auth/callback http://localhost:5173/auth/callback - Drop the widget into your page
One script tag and one element. The bundle mints a PKCE pair on YOUR origin, keeps the verifier in your page's sessionStorage, and passes only the challenge into the widget — so the secret never crosses an origin. Nothing else on your page has to change.
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script> <div data-cg-widget="login" data-client-id="your-app" data-redirect-uri="https://example.org/auth/callback" data-scope="openid profile email"></div>Using a raw iframe instead? Then no verifier is minted for you — pass your own ?code_challenge=, or register a confidential client without PKCE.
- Handle the redirect and exchange the code
The reader comes back to your redirect URI with ?code= and ?state= (or ?error= and ?error_description= if something went wrong — handle that branch, it is where a misconfigured scope or a declined consent lands). Compare the state, take the verifier, and POST the exchange. The authorization code is single-use and lives for TWO MINUTES, so exchange it on arrival rather than queueing it.
// Browser (public client, PKCE). Runs on your callback page. const q = new URLSearchParams(location.search); if (q.get("error")) throw new Error(q.get("error_description") || q.get("error")); const saved = CivicGate.takeLoginVerifier(); // read-once: a verifier is good for one exchange if (!saved || q.get("state") !== saved.state) throw new Error("state mismatch — do not continue"); const res = await fetch("https://auth.civicgate.org/api/v1/oauth/token", { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code: q.get("code"), redirect_uri: saved.redirectUri, // must equal the one in the authorization request client_id: saved.clientId, code_verifier: saved.verifier, }), }); const { access_token, id_token, refresh_token, scope } = await res.json();Server-side instead? Send client_id + client_secret (form fields or HTTP Basic) in place of code_verifier — and never put a client secret in a page. Ask for offline_access if you want a refresh token.
- Read the member, and know what the widget shows
The ID token identifies the member (`sub`); GET the userinfo endpoint with the access token for the claims your granted scopes cover. From here the session is YOURS — CivicGate does not hold it and the widget cannot see it. That is why the widget reports the CivicGate session it can see and never claims to know whether your own login completed: on your site its storage is partitioned, so it shows the sign-in offer, which is the honest answer rather than a wrong one.
const me = await fetch("https://auth.civicgate.org/api/v1/oauth/userinfo", { headers: { authorization: `Bearer ${access_token}` }, }).then((r) => r.json()); // → { sub, name, preferred_username, email, picture, … } — as your granted scopes allow
iframe parameters (11)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
client_id | The OIDC client id of your registered application. | — (absent = CivicGate sign-in mode) | Selects the mode. Present, the button starts an authorization-code hand-off to your application. Absent, it signs the reader into CivicGate itself — no registration, nothing to configure — which is the mode this page's own preview runs in. |
redirect_uri | An absolute URL, registered on that application. | — (required whenever client_id is set) | Where the identity server sends the reader back with the code. Matched EXACTLY — not a prefix, not a wildcard. An unregistered value is refused at the authorize step and the reader is never redirected, which is deliberate: a prefix match on https://you.example/cb also accepts https://you.example/cb.attacker.net, and that hands your readers' sessions to whoever registered that host. |
scope | openid | profile | email | offline_access | civic:location | civic:interests | civic:positions | civic:activity — space-separated. | openid profile email | What your application is asking for. openid is required and is added if you leave it out. A scope the server does not publish is SILENTLY DROPPED rather than rejected, so a typo costs you the data without an error — check the granted scope in the token response. |
state | Any string. | — (a random one is sent) | Echoed back verbatim on the redirect. GENERATE IT ON YOUR SIDE and store it: a value this widget invented is one your callback has never seen, so it can be echoed but compared to nothing. The script bundle does generate one and keeps it where your callback can read it — see the walkthrough. |
nonce | Any string. | none | Copied into the ID token, so your callback can prove the token was minted for this request and not replayed from another. |
code_challenge | The base64url SHA-256 of your own code_verifier. | — (the script bundle mints one; a raw iframe sends none) | PKCE. The method is always S256 — the server refuses plain at the authorize step, so there is nothing to choose. The widget never mints this itself: the matching verifier is a secret only your callback may hold, and this page runs on CivicGate's origin, so a verifier minted here could never reach it. |
size | sm | md | lg | md | Control density. sm suits a header row, lg a dedicated sign-in panel. |
variant | primary | brand | outline | primary | primary uses CivicGate's own accent; brand paints the background and foreground the identity server publishes for itself (so a self-hosted deployment gets its colours, not ours); outline is a bordered, transparent button for a busy page. brand falls back to primary when the server publishes no colours. |
avatar | 1 | 0 | 1 | Show the member's profile photo in the signed-in state. With 0 the state is name and text only — the right choice in a dense header. |
label | Any text. An EMPTY value means “use the provider's own label”. | empty | Replace the button text outright. Left alone, the label is the provider's and it SHORTENS as the box narrows (full name → short name → “Sign in”) while the accessible name stays complete. A fixed label opts out of that, so pick one that fits your narrowest column. |
theme | auto | dark | light | auto | Which palette the frame paints in. auto follows the reader's system preference. Applied before the widget hydrates, so a light embed never flashes the dark default. Frame-only: it has no effect on the in-page preview above, which inherits this page's theme. |
Script-bundle attributes (16)
| Attribute | Accepted values | Default | What it does |
|---|---|---|---|
data-client-id | The OIDC client id of your registered application. | — (absent = CivicGate sign-in mode) | Selects the mode — see the iframe table. |
data-redirect-uri | An absolute URL, registered on that application. | — (required whenever data-client-id is set) | Where the reader comes back to. EXACT match against a registered URI. |
data-scope | openid | profile | email | offline_access | civic:location | civic:interests | civic:positions | civic:activity — space-separated. | openid profile email | What your application is asking for. openid is added if you leave it out. |
data-pkce | 1 | 0 (valueless means 1) | 1 | Mint a PKCE pair ON YOUR ORIGIN, send the challenge, and keep the verifier in your page's sessionStorage for CivicGate.takeLoginVerifier(). This is a REAL switch, not a preference: a challenge your server never redeems fails the exchange, so set 0 only for a confidential client registered without PKCE. Ignored when you supply data-code-challenge. |
data-code-challenge | The base64url SHA-256 of your own code_verifier. | — (minted for you, unless data-pkce="0") | Use your own PKCE pair. Nothing is then stored on your behalf — the verifier is yours and the widget never sees it. |
data-state | Any string. | — (one is generated and stored for you) | Echoed back on the redirect. The generated one is kept alongside the verifier, so your callback can compare it. |
data-nonce | Any string. | none | Copied into the ID token. |
data-size | sm | md | lg | md | Control density. |
data-variant | primary | brand | outline | primary | Button skin. brand uses the identity server's own published colours. |
data-show-avatar | 1 | 0 (valueless means 1) | 1 | Show the member's photo in the signed-in state. |
data-label | Any text. An EMPTY value means “use the provider's own label”. | empty | Replace the button text. Opts out of the responsive shortening — see the iframe table. |
data-theme | auto | dark | light | auto | Which palette the frame paints in. |
data-height | A positive integer (pixels). | 120 | The height BEFORE the frame reports its own. The widget measures itself and posts its real height as soon as it paints — and again whenever it changes, so the box grows when the duplicate-check panel opens instead of clipping it. This value only stops the frame popping open from nothing, and is superseded within a frame or two. The host script clamps a reported height to 4000px. |
data-width | Any CSS length — 100%, 32rem, 420px. | 100% | Frame width. max-width is pinned to 100% alongside it, so an explicit width can never overflow a narrow host column. |
data-cg-site | An absolute CivicGate origin. | the site from CivicGate.configure({ site }), else https://www.civicgate.org | Which CivicGate deployment the frame loads from. For these two widgets this replaces data-cg-gateway: the frame is a CivicGate page, so it reaches the gateway itself rather than being handed an endpoint. |
data-cg-widget | person | wall | feed | issue-report | place | place-wall | ballot | issue-topics | login | — (required) | Which widget to mount. This attribute is what makes the element a CivicGate widget. |
The greyed row is the marker every widget carries. This widget mounts a
CivicGate-origin iframe, which holds no client and no credential, so
the shared client attributes — data-cg-key, data-cg-gateway, data-show-loading —
have no effect here and are deliberately not listed. Use
data-cg-site to point at a different CivicGate deployment.
Component props (13) — the Preact / Astro path
| Prop | Accepted values | Default | What it does |
|---|---|---|---|
clientId | string | null | null | The registered application's OIDC client id. null selects CivicGate sign-in mode. |
redirectUri | string | null | null | Where the identity server sends the reader back. Required with clientId; without it the widget says so in words rather than rendering a button that cannot work. |
scope | string | "openid profile email" | Space-separated scopes. openid is added if absent. |
state | string | null | null (a random one is used) | Passed through verbatim. |
nonce | string | null | null | Passed through verbatim; copied into the ID token. |
codeChallenge | string | null | null | Base64url SHA-256 of YOUR code_verifier. The component never mints one — see the iframe table for why. |
size | sm | md | lg | md | Control density. |
variant | primary | brand | outline | primary | Button skin. |
showAvatar | boolean | true | Show the member's photo in the signed-in state. |
label | string | "" | Replace the button text. Empty keeps the provider's own responsive label. |
site | An absolute CivicGate origin. | "" (same-origin, relative links) | Used only for links that must LEAVE an iframe — the CivicGate sign-in window. Empty means relative, which is what you want on a CivicGate page. |
authOrigin | An absolute identity-server origin. | PUBLIC_AUTH_URL, else https://auth.civicgate.org | Whose identity is rendered and where the reader is sent. Only a self-hosted deployment changes this. |
frameWidget | string | "login" | The name this widget posts its height under. Only meaningful inside an /embed/… frame, and it must match the name the host script listens for. |
Examples (6)
- iframe — CivicGate sign-in, no application
<iframe src="https://www.civicgate.org/embed/login" width="100%" height="150" style="border:0" loading="lazy" title="Sign in with CivicGate"></iframe>No client id, so nothing to register. This signs the reader into CivicGate itself — useful beside other CivicGate widgets, which then see their saved place and name.
- iframe — sign in to YOUR application
https://www.civicgate.org/embed/login?client_id=your-app&redirect_uri=https%3A%2F%2Fexample.org%2Fauth%2Fcallback&scope=openid+profile+emailURL-encode the redirect URI. A raw iframe sends no PKCE challenge, so either register a confidential client without PKCE or add your own &code_challenge=.
- iframe — small, outlined, light, no photo
https://www.civicgate.org/embed/login?size=sm&variant=outline&theme=light&avatar=0 - Script bundle — the whole integration
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script> <div data-cg-widget="login" data-client-id="your-app" data-redirect-uri="https://example.org/auth/callback"></div>No configure() call: this widget takes no key and no endpoint.
- Script bundle — branded, large, your own label
<div data-cg-widget="login" data-client-id="your-app" data-redirect-uri="https://example.org/auth/callback" data-scope="openid profile email civic:location" data-variant="brand" data-size="lg" data-label="Continue with CivicGate" data-theme="light"></div>data-label opts out of the responsive shortening, so choose one that fits your narrowest column.
- Script bundle — your own PKCE pair
<div data-cg-widget="login" data-client-id="your-app" data-redirect-uri="https://example.org/auth/callback" data-state="the-value-you-stored" data-code-challenge="E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"></div>With a challenge of your own nothing is stored on your behalf — the verifier is yours and the widget never sees it.
Notes (11)
- NO CREDENTIAL. It signs a reader in; it never reads data on anyone's behalf, so a configured key is ignored.
- The PKCE verifier never crosses an origin: the bundle mints it on YOUR origin so your callback can read it. A framed widget cannot — pass data-code-challenge and keep the verifier yourself.
- In the bundle this mounts an IFRAME: it renders a real session state, which must not be reconstructed from host-supplied markup.
- “Unknown” renders as the sign-in offer, never as “signed out” — in a third-party frame storage is partitioned, so a missing session is not proof of one.
- It reports the CivicGate session only. It cannot know whether your own site's login completed.
- Name, icon and brand colours come from the identity server's published identity, so a self-hosted deployment brands itself.
- A deployment serving neither oidc nor fedcm cannot sign readers into an application; the widget says so rather than rendering a dead button.
- Signing out ends the CivicGate session in that browser only — not your site's, and it revokes nothing already issued.
- civic:positions and civic:activity are political-belief data: consent is re-prompted every time, and refusal is normal.
- An unpublished scope is silently dropped (RFC 6749 §3.3) — a typo costs you the data with no error. Check the granted scope on the response.
- A HAND-WRITTEN iframe does not get the self-sizing. The frame always posts its height, but only the script bundle listens for it — so give a raw iframe about 150px for the signed-out button and roughly 200px if signed-in readers will see the identity row, or listen for a `cg:widget-height` message yourself and accept it ONLY from the window you created, at the origin you loaded.
<iframe src="https://www.civicgate.org/embed/login" width="100%" height="150" style="border:0" loading="lazy" title="CivicGate widget"></iframe> People references
Point at a person and render who they are.
Person card
One person's summary card: photo, name, party, role and state, linking to their CivicGate profile.
Available as: iframe (/embed/person/bernard_sanders?lens=1) · script bundle (data-cg-widget="person")
Any page — CMS field, blog post, plain HTML. No build step. The bundle draws the same card with inline styles and no hover lens, so the preview has the lens off on this tab — switch to Preact or Astro, or hover the iframe preview below, to see it.
Public data — no key, no account. Hover the card (or press its magnifier button) to open the detail lens. The preview sets data-width=200px; omit it and the card fills its container.
Also embeddable as an iframe — no JavaScript:
<iframe src="https://www.civicgate.org/embed/person/bernard_sanders?lens=1">. Options are the iframe parameters below.
iframe parameters (8)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
…/<idOrSlug> | A canonical CivicGate slug (bernard_sanders), an alias slug in either dash form (bernie-sanders), a Bioguide id (S000033) or an ocd-person id. | — (required) | Path segment. All four forms resolve to the same canonical record, and the card links to that record's canonical URL — so the link never eats a redirect. |
width | A CSS length: one number plus px | rem | em | % | ch | vw. | — (fluid) | PINS the card to an exact width, overriding the fluid default below. Applied to every state (skeleton, error, loaded) so the card does not resize when it finishes loading. An unrecognised value is ignored rather than passed through — it lands in a style attribute. |
maxWidth | A CSS length (same forms as width). | 300px | Ceiling for the FLUID default: with no width set the card fills its container and stops here. Raise it in a wide slot, lower it to keep a row of cards tighter. |
minWidth | A CSS length (same forms as width). | 200px | Floor for the fluid default. Clamped by the container, so a host narrower than this shrinks the card instead of overflowing. |
size | sm | md | lg | md | Photo size and overall density. sm = 36px, md = 48px, lg = 64px. |
lens | 1 | 0 | 0 | Enable the hover/tap detail overlay. OFF by default in an iframe: the overlay is positioned against the viewport, and inside a 150px-tall frame that viewport is 150px tall, so it would open clipped. Turn it on only with a frame of ~420px or more. |
bar | 1 | 0 | 1 | The party-coloured left bar. |
subtitle | Any text. | empty | An extra qualifier appended to the role · state line, e.g. “District 12” or “up for election 2026”. |
Script-bundle attributes (15)
| Attribute | Accepted values | Default | What it does |
|---|---|---|---|
data-id | The entity's CivicGate id. | — (one of data-id / data-slug required) | Wins if both are present. |
data-slug | The entity's CivicGate slug — the one in its URL. | — | Accepted anywhere data-id is. Slugs are what CivicGate URLs use, so they are usually the easier value to find. |
data-width | Any CSS length — 200px, 18rem, 100%. | — (fluid, see below) | PINS the card to an exact width. Leave it off in a grid cell or table, where the fluid default is right; set it only to fix a card in open page flow. |
data-max-width | Any CSS length. | 300px | Ceiling for the FLUID default: with no data-width the card fills its host and stops here. |
data-min-width | Any CSS length. | 200px | Floor for the fluid default. Clamped by the host, so a container narrower than this shrinks the card rather than overflowing it. |
data-size | sm | md | lg | md | Photo size and overall density (36 / 48 / 64px). |
data-subtitle | Any text. | empty | Extra qualifier appended to the role · state line. |
data-href | Any URL. | the person's canonical CivicGate profile | Override the link target — point the name at your own page about this person. |
data-target | _blank | _self | _blank | Where the link opens. Defaults to a new tab, because navigating a reader away from your page is rarely what you meant. |
data-party-bar | 1 | 0 | 1 | The party-coloured left bar. |
data-photo | 1 | 0 | 1 | Show the photo. With 0 the card is a single dense line — right for a long list. |
data-cg-widget | person | wall | feed | issue-report | place | place-wall | ballot | issue-topics | login | — (required) | Which widget to mount. This attribute is what makes the element a CivicGate widget. |
data-cg-key | A read-only API key (cg_live_…). | the key from CivicGate.configure() | Per-element override of the page key. Only the following feed needs one. |
data-cg-gateway | An absolute GraphQL endpoint URL. | https://www.civicgate.org/graphql | Per-element endpoint override, for a self-hosted CivicGate. |
data-show-loading | 1 | 0 | 0 | Show a “Refreshing…” line during background refresh. Off by default — a sidebar widget should not flicker. |
The greyed rows are the attributes every widget understands.
Component props (18) — the Preact / Astro path
| Prop | Accepted values | Default | What it does |
|---|---|---|---|
person | A PersonCardData object ({ id, name, party, state, roles, image, url }). | — | MODE (a): you already have the record. The card then provably issues NO request of its own — this is the case for a server-rendered list. |
id | A slug, alias slug, Bioguide id or ocd-person id. | — | MODE (b): the card resolves it through its service and shows a card-shaped skeleton meanwhile. Ignored when `person` is set. |
service | A PersonService ({ get, detail?, peek?, invalidate? }). | the @cg/sdk kernel service | The card's ONE data dependency. The default reads through the shared @cg/sdk kernel, so three cards for the same member on one page cost one request and one cached record. Inject your own to point somewhere else — a stub in a test, a service over your own API. |
gatewayUrl | An absolute GraphQL endpoint. | same-origin /graphql | Used only by the DEFAULT service. Set it on a third-party page, or to point at a self-hosted CivicGate. |
apiKey | A read-only API key. | none | Used only by the DEFAULT service. A person card is public data, so this is never required — it exists for pages that authenticate everything. |
size | sm | md | lg | md | Avatar size: 36 / 48 / 64px. |
partyBar | boolean | true | The party-coloured left bar. |
subtitle | string | — | Extra qualifier under the name, appended to role · state. |
href | string | the canonical profile URL | Override the link target. |
target | _self | _blank | _self | Link target. (The script bundle defaults this to _blank instead, because it renders on someone else's page.) |
lens | boolean | true | The hover / tap detail overlay: stats and documented issue positions. Reachable by keyboard and touch through a real magnifier button — hover is an accelerator, never the only way in. |
lensDelayMs | number | 300 | Hover dwell before the lens opens. 300ms is long enough that dragging the pointer across a grid of cards opens none of them. |
showLoadingWhenRefreshing | boolean | false | Make a background revalidation visible. False keeps good data on screen silently. |
controllerRef | { current: PersonCardHandle | null } | — | Filled with { refresh(), closeLens() } so the host can drive the card. |
onResolved | (person, status) => void | — | Fires whenever resolution settles — ready / not_found / forbidden / error. |
action | { href, label } | — | One secondary link under the meta line, e.g. “Profile & contact →”. A plain serialisable object rather than a slot, because props cross an Astro island boundary as JSON and a VNode does not survive the trip. |
class | string | — | Extra classes on the root element. |
children | Any nodes. | — | Rendered inside the card body under the meta line: actions, chips, a contact link. Anything interactive needs the `pc-above` class to sit above the card's stretched link. |
Examples (5)
- iframe — one card
<iframe src="https://www.civicgate.org/embed/person/bernard_sanders" width="100%" height="150" style="border:0" loading="lazy" title="CivicGate person card"></iframe> - iframe — large, with the detail lens
<iframe src="https://www.civicgate.org/embed/person/josh_shapiro?size=lg&lens=1" width="100%" height="420" style="border:0" loading="lazy" title="CivicGate person card"></iframe>The taller frame is required: the lens is positioned against the frame's own viewport, so a short frame clips it.
- iframe — by Bioguide id, dense
https://www.civicgate.org/embed/person/S000033?size=sm&bar=0 - Script bundle — by slug
<div data-cg-widget="person" data-slug="bernard_sanders"></div> - Script bundle — dense list row, linked to your own page
<div data-cg-widget="person" data-slug="josh_shapiro" data-size="sm" data-photo="0" data-href="/our-coverage/shapiro"></div>
Notes (5)
- Public data: no key and no account, in either integration mode.
- Every id form resolves — politician slug, FEC candidacy slug, hyphen or underscore. All redirect to the one canonical profile.
- The script bundle reads through the @cg/sdk kernel, so three cards for the same member on one page cost ONE request and share one cache with every other CivicGate widget there.
- The bundle's card has NO hover lens: it is a separate lazily-loaded chunk, so the bundle omits it. Preact and Astro get it.
- Coverage-honest: a person with no photo gets a silhouette, never a broken image; a missing role or state is omitted rather than rendered as an empty separator.
<iframe src="https://www.civicgate.org/embed/person/bernard_sanders?lens=1" width="100%" height="380" style="border:0" loading="lazy" title="CivicGate widget"></iframe> Walls & feeds
General post sharing and activity embeds — one subject's public updates, or one account's own following feed.
Subject wall
One subject's public updates — a bill's actions, a member's votes, a committee's hearings, a member's posts. Public data, no key.
Available as: iframe (/embed/wall/bill/hr-22-119) · script bundle (data-cg-widget="wall")
See every subject type and the id each one takes →
Any page — CMS field, blog post, plain HTML. No build step.
Loading posts…
Public data — no key, no account. Change data-type / data-id to any subject in the Reference section.
Also embeddable as an iframe — no JavaScript:
<iframe src="https://www.civicgate.org/embed/wall/bill/hr-22-119">. Options are the iframe parameters below.
iframe parameters (7)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
…/<type> | Any subject type — see the Reference section. | — (required) | First path segment after /embed/wall. Which kind of thing the wall is about. |
…/<id> | That subject's CivicGate id or slug. | — (required) | Second path segment. For most types this IS the slug in the entity's CivicGate URL; for `user` it is the stable user id, never the handle. |
type | Same values as the path segment. | none | Query-parameter form, for parity with the bundle's data-type. Used only when the path segments are absent. |
id / slug | Same values as the path segment. | none | Query-parameter form of the id. Either name works. |
limit | 1–50 (out-of-range values are clamped). | 10 | How many updates load per page. |
mode | manual | auto | manual | manual shows a “Load more” button; auto loads the next page as the reader nears the end (infinite scroll). |
title | Any text. | empty (no heading) | Heading rendered above the list. Embeds usually supply their own heading, so the default is none. |
Script-bundle attributes (9)
| Attribute | Accepted values | Default | What it does |
|---|---|---|---|
data-type | Any subject type — see the Reference section. | bill | Which kind of thing the wall is about. |
data-id | The entity's CivicGate id. | — (one of data-id / data-slug required) | Wins if both are present. |
data-slug | The entity's CivicGate slug — the one in its URL. | — | Accepted anywhere data-id is. Slugs are what CivicGate URLs use, so they are usually the easier value to find. |
data-limit | A positive integer (clamped to 50). | 5 | How many updates to show. |
data-mode | manual | auto | manual | manual shows a “Load more” button; auto keeps loading as the reader scrolls. |
data-cg-widget | person | wall | feed | issue-report | place | place-wall | ballot | issue-topics | login | — (required) | Which widget to mount. This attribute is what makes the element a CivicGate widget. |
data-cg-key | A read-only API key (cg_live_…). | the key from CivicGate.configure() | Per-element override of the page key. Only the following feed needs one. |
data-cg-gateway | An absolute GraphQL endpoint URL. | https://www.civicgate.org/graphql | Per-element endpoint override, for a self-hosted CivicGate. |
data-show-loading | 1 | 0 | 0 | Show a “Refreshing…” line during background refresh. Off by default — a sidebar widget should not flicker. |
The greyed rows are the attributes every widget understands.
Component props (10) — the Preact / Astro path
| Prop | Accepted values | Default | What it does |
|---|---|---|---|
gatewayUrl | An absolute GraphQL endpoint. | — (required) | Where to read from. |
subjectType | Any subject type — see the Reference section. | — | Both halves are required TOGETHER. One without the other silently falls back to the reader's personal feed, which on a profile would render the wrong person's updates — so the component resolves them as a pair. |
subjectId | That subject's id or slug. | — | See subjectType. |
readOnly | boolean | false | Strips every way to write or navigate away: no composer, no Post button, no filters, no Manage link, unlinked heading. ONE switch rather than five, so a read-only placement cannot acquire a write affordance later by someone flipping a default. |
limit | number | 10 | Page size. |
mode | manual | auto | manual | Load-more button vs infinite scroll. |
title | string | "Your feed" | Section heading. Pass "" to render no header at all — the usual choice when embedding. |
emptyLabel | string | "Nothing posted here yet." | Copy shown when the wall has no events. Ignored outside subject mode. |
contain | open | scroll | scroll-desktop | open | open grows with its content and the PAGE scrolls; scroll caps at the viewport height and the LIST scrolls; scroll-desktop is scroll on desktop and open on mobile (a nested scroll area traps a thumb on a phone). |
refreshMs | number | 12000 | Background poll interval. 0 disables polling. |
Examples (4)
- One bill's wall (iframe)
<iframe src="https://www.civicgate.org/embed/wall/bill/hr-22-119" width="100%" height="480" style="border:0" loading="lazy" title="CivicGate widget"></iframe> - A committee's wall, infinite scroll
https://www.civicgate.org/embed/wall/committee/hsag00?mode=auto&limit=25 - The same wall via the script bundle
<div data-cg-widget="wall" data-type="bill" data-id="hr-22-119" data-limit="10"></div> - Legacy spelling — still works
https://www.civicgate.org/embed/feed?subject=bill:hr-22-119This was the original URL for a wall. It is live on pages we do not control, so it keeps working indefinitely. New embeds should use /embed/wall/….
Notes (2)
- A wall is public data and takes no credential. Do not pass a key alongside a subject — the widget ignores it, deliberately, so serving a public wall can never spend someone's rate limit.
- A wall carries only the event types listed for its subject type. An empty wall means nothing happened recently, not an error.
<iframe src="https://www.civicgate.org/embed/wall/bill/hr-22-119" width="100%" height="480" style="border:0" loading="lazy" title="CivicGate widget"></iframe> Following feed
Everything ONE account subscribes to, newest first. That account's own view, so it needs that account's read-only key.
Available as: iframe (/embed/feed) · script bundle (data-cg-widget="feed")
Keys. This widget shows one account's own subscriptions, so it needs that
account's read-only key — create one under
Settings → API keys, then append ?key=cg_live_…
(or pass it to configure()). Keys can never write to your account — see
Credentials and
API keys.
See the filter groups the chips select →
Any page — CMS field, blog post, plain HTML. No build step. configure() may be called before or after the bundle loads — both re-scan the document.
Checking your session…
This preview uses YOUR CivicGate session, not a key — on civicgate.org the gateway is same-origin, so nothing secret is on this page. On your own site you pass a read-only key instead.
Also embeddable as an iframe — no JavaScript:
<iframe src="https://www.civicgate.org/embed/feed">. Options are the iframe parameters below.
iframe parameters (6)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
key | A read-only API key (cg_live_…). | — (required) | Whose subscriptions to show. Create one under Settings → API keys. |
limit | 1–50 (out-of-range values are clamped). | 10 | How many updates load per page. |
mode | manual | auto | manual | manual shows a “Load more” button; auto loads the next page as the reader nears the end (infinite scroll). |
filters | 1 | 0 | 0 | Show the subject-group filter chips (People / Bills / Users / Civic). |
title | Any text. | empty (no heading) | Heading rendered above the list. |
subject | <type>:<id> | none | LEGACY. Switches this URL into wall mode and ignores `key`. Kept working for existing embeds; new ones should use /embed/wall/<type>/<id>. |
Script-bundle attributes (6)
| Attribute | Accepted values | Default | What it does |
|---|---|---|---|
data-limit | A positive integer (clamped to 50). | 10 | How many updates to show. |
data-mode | manual | auto | manual | manual shows a “Load more” button; auto keeps loading as the reader scrolls. |
data-cg-widget | person | wall | feed | issue-report | place | place-wall | ballot | issue-topics | login | — (required) | Which widget to mount. This attribute is what makes the element a CivicGate widget. |
data-cg-key | A read-only API key (cg_live_…). | the key from CivicGate.configure() | Per-element override of the page key. Only the following feed needs one. |
data-cg-gateway | An absolute GraphQL endpoint URL. | https://www.civicgate.org/graphql | Per-element endpoint override, for a self-hosted CivicGate. |
data-show-loading | 1 | 0 | 0 | Show a “Refreshing…” line during background refresh. Off by default — a sidebar widget should not flicker. |
The greyed rows are the attributes every widget understands.
Component props (12) — the Preact / Astro path
| Prop | Accepted values | Default | What it does |
|---|---|---|---|
gatewayUrl | An absolute GraphQL endpoint. | — (required) | Where to read from. |
apiKey | A read-only API key. | none | The account whose subscriptions to show. Without it the widget uses the reader's own CivicGate session — which is why this page can preview it with no key at all. |
limit | number | 10 | Page size. |
mode | manual | auto | manual | Load-more button vs infinite scroll. |
showFilters | boolean | true | The subject-group filter chips. |
showComposer | boolean | false | The “write a post” composer above the list. Requires a session — a read-only key can never write. |
showPostButton | boolean | false | A Post toggle beside the heading that reveals the composer on demand. |
title | string | "Your feed" | Section heading. Pass "" for none. |
titleHref | string | null | /feed | Where the heading links. null renders a plain, unlinked heading. |
manageHref | string | null | /profile?tab=subscribed | Link to the subscription manager. null hides it. |
contain | open | scroll | scroll-desktop | open | See the wall's `contain` — same three layouts. |
refreshMs | number | 12000 | Background poll interval. 0 disables polling. |
Examples (2)
- Your own following feed, with filters
<iframe src="https://www.civicgate.org/embed/feed?key=cg_live_xxx&filters=1&limit=20&title=My%20feed" width="100%" height="520" style="border:0" loading="lazy" title="CivicGate feed"></iframe> - The same feed via the script bundle
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script> <script>CivicGate.configure({ apiKey: "cg_live_…" })</script> <div data-cg-widget="feed" data-limit="20" data-mode="auto"></div>
Notes (3)
- API keys are read-only — the gateway rejects every mutation on a key-authenticated request. Restrict a key by origin.
- A key-authenticated request is treated as ANONYMOUS for visibility, so an embedded key can never widen what a reader is allowed to see.
- No live preview with a key: any example here would show one account's private subscriptions to everyone, so the playground above uses your own session instead.
<iframe src="https://www.civicgate.org/embed/feed" width="100%" height="520" style="border:0" loading="lazy" title="CivicGate widget"></iframe> No live iframe preview: this widget needs your own API key, so any example here would be broken for everyone but its author.
Place wall — Latest for a place
A wall for a PLACE instead of one subject: your representatives' activity, state bills and national actions, newest first.
Available as: iframe (/embed/place-wall) · script bundle (data-cg-widget="place-wall")
See every event type a place wall can carry →
Any page — CMS field, blog post, plain HTML. No build step.
Latest for this place
Pin a place with data-state / data-district, or leave them off and the preview follows the locator above it — which is what data-place="derive" does on your own page. Signed in, it starts from your saved location.
Also embeddable as an iframe — no JavaScript:
<iframe src="https://www.civicgate.org/embed/place-wall">. Options are the iframe parameters below.
iframe parameters (5)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
state | A 2-letter state code. | — (national without it) | The state. |
county | A 5-digit county GEOID. | — | Narrow to a county. County and district are SIBLINGS — a district cuts across counties — so both may be set. |
district | A congressional district number. 0 = at-large. | — | Narrow to one district. |
level | country | state | county | city | district | district | How WIDE. Reads backwards until you see why: a district wall shows district + state + national, because your senator's vote genuinely is an event in your district — so a BROADER level shows FEWER, more widely-relevant events. Clamped to what the place resolved, and the clamped value is returned. |
limit | 1-50 | 20 | Page size. |
Script-bundle attributes (11)
| Attribute | Accepted values | Default | What it does |
|---|---|---|---|
data-place | derive | — (pinned) | OPT IN to following a Place locator on the same page. With it, the wall listens for cg:place-change and re-scopes itself whenever the reader moves that locator — no wiring on your side. Without it the wall is pinned by the attributes below, so dropping a locator onto the page later cannot make a fixed wall start moving. |
data-state | A 2-letter state code. | — | The state. Also the STARTING place when data-place="derive", until the first event arrives. |
data-county | A 5-digit county GEOID. | — | Narrow to a county. |
data-district | An integer. 0 = at-large. | — | Narrow to a district. |
data-level | country | state | county | city | district | district | How wide. Clamped to what resolved. |
data-limit | 1-50 | 20 | Page size. |
data-event-types | Comma-separated event types. | all | Restrict to specific updates. NOTE this is the EVENT axis, not the subject axis: a place wall's bill news arrives as sponsored_bill on a PERSON, so filtering by the bill SUBJECT returns nothing. |
data-cg-widget | person | wall | feed | issue-report | place | place-wall | ballot | issue-topics | login | — (required) | Which widget to mount. This attribute is what makes the element a CivicGate widget. |
data-cg-key | A read-only API key (cg_live_…). | the key from CivicGate.configure() | Per-element override of the page key. Only the following feed needs one. |
data-cg-gateway | An absolute GraphQL endpoint URL. | https://www.civicgate.org/graphql | Per-element endpoint override, for a self-hosted CivicGate. |
data-show-loading | 1 | 0 | 0 | Show a “Refreshing…” line during background refresh. Off by default — a sidebar widget should not flicker. |
The greyed rows are the attributes every widget understands.
Component props (5) — the Preact / Astro path
| Prop | Accepted values | Default | What it does |
|---|---|---|---|
gatewayUrl | An absolute GraphQL endpoint. | — | Required. |
state / countyFips / district | The place. | — | Starting place. The component follows the shared place service from then on. |
initialLevel | country | state | county | city | district | district | Starting breadth. |
maxHeight | Any CSS length, or "none". | 800px | How tall the list grows before it scrolls internally. "none" opts OUT of the scroll container entirely rather than setting a huge cap — an unbounded box still declaring overflow-y:auto traps scroll chaining on touch. Use it when the feed IS the page. |
minHeight | Any CSS length. | — | Floor, so a short list does not collapse to a sliver beside a tall neighbour. |
Emitted events (1)
| Event | Fires | event.detail |
|---|---|---|
cg:place-change | Any Place locator on the page moved. | The whole PlaceState. A wall with data-place="derive" consumes this; it emits nothing of its own. |
All on window. A framed widget re-posts its events to the host, where the bundle re-fires them — so one listener works whether you used the component, the bundle or an iframe.
Examples (4)
- 1 · Pinned to one district
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script> <div data-cg-widget="place-wall" data-state="PA" data-district="3" data-level="district"></div>Standalone. Nothing on the page can move it — the right choice for a site about one place.
- 2 · Following a Place locator on the page
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script> <!-- The reader picks the place here… --> <div data-cg-widget="place" data-view="header" data-hover="expand"></div> <!-- …and the wall follows it. No JavaScript of your own. --> <div data-cg-widget="place-wall" data-place="derive" data-state="PA"></div>The two widgets never reference each other — the locator EMITS cg:place-change and the wall LISTENS — so either one works with the other absent. data-state is just the starting place until the reader moves it.
- 3 · Driving it from your own place picker
window.dispatchEvent(new CustomEvent("cg:place-change", { detail: { level: "state", resolution: { state: "OH", countyFips: null, district: null } }, }));The wall listens for the event, not for our widget, so your own UI can drive it by emitting the same shape.
- 4 · As an iframe (always pinned)
<iframe src="https://www.civicgate.org/embed/place-wall?state=PA&district=3" width="100%" height="520" style="border:0"></iframe>A frame cannot hear the host page's events, so an iframe wall is pinned by its query string. Use the script bundle when you want it to follow a locator.
Notes (8)
- THREE WAYS TO NAME THE PLACE — pinned by attributes, derived from a locator on the page (data-place="derive"), or both, where the attributes are the starting place until the first event arrives.
- They work together and standalone. The locator and the wall never reference each other: one emits cg:place-change, the other listens. Either is useful with the other absent.
- A place is not a subject, so this is a third API call (placeFeed), not a filter over the feed or a subject wall.
- Two sources, merged and de-duplicated: the place's REPRESENTATIVES (a subject list, like a follow list) and events INDEXED to the place (state bills, national actions).
- Federal bills carry no place deliberately — tagging all ~20k `us` would be a firehose wearing a local label. Federal relevance arrives through the people who represent the place.
- A House member's events are DISTRICT-scoped, not state-scoped: otherwise every district's wall shows every other district's member.
- level is clamped to what the place resolved and the clamped value is returned, so a district request against a state-only place cannot silently answer with state events.
- On CivicGate's own /place page this is the "Latest" section — the same component, with a level control and kind filters, following the locator in the page header.
<iframe src="https://www.civicgate.org/embed/place-wall" width="100%" height="520" style="border:0" loading="lazy" title="CivicGate widget"></iframe> No live iframe preview: this widget needs your own API key, so any example here would be broken for everyone but its author.
Issues & topic discussion
Civic issue reporting and the discussion around it, embeddable on any page. Anonymous reporting works with no account at all.
Report an issue
A form for reporting a civic problem against a place, from a block up to federal. Works anonymously, with no account.
Available as: iframe (/embed/issue-report) · script bundle (data-cg-widget="issue-report")
See every place level and what each one needs →
Any page — CMS field, blog post, plain HTML. No build step.
Checking your CivicGate sign-in…
Public data — no key, no account. Anonymous reporting works in every identity state; sign-in is offered, never required.
Also embeddable as an iframe — no JavaScript:
<iframe src="https://www.civicgate.org/embed/issue-report">. Options are the iframe parameters below.
iframe parameters (7)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
zip | A 5-digit US ZIP. Non-digits are stripped and the value is cut to 5. | — (the reader's own place; see below) | Which place the widget is about. Given here it WINS over the reader's saved location: a host embedding this on a Philadelphia community page has stated which place it is about, and silently replacing that with a visitor's own ZIP would make the parameter do nothing. With no zip the widget falls back to the signed-in reader's saved location, then to “Locate me”, then to no place at all — which is allowed and files at federal. |
state | Any 2-letter USPS state code. Case-insensitive, cut to 2. | none | For a host that knows the state but not a ZIP. On its own it enables the State and Federal rungs only — the finer levels need a ZIP to resolve a district. |
level | address | block | zip | city | county | district | state | federal (county is accepted by the API and never offered here — see the Reference section). | the finest rung that resolved — ZIP with a ZIP, else State, else Federal | The rung the form starts on. CLAMPED to what actually resolved: a level whose required fields we do not hold is dropped rather than sent to the API, which would answer with a validation sentence the reporter has no way to act on. |
nav | 1 | 0 | 1 | Show the level ladder (Block → … → Federal). 0 pins the widget to the rung its host chose. The place row itself — which place, where it came from, and “Change place” — always renders, because a reader must be able to see which jurisdiction they are about to file against. |
discussion | 1 | 0 | 1 | The starting state of the “Open a discussion for this” switch; the reader can still change it. Discussion threads for reports are NOT wired up yet — the widget records the intent and says so, in words, on the confirmation screen. |
title | Any text. An EMPTY value means “render no heading”. | Report an issue | Heading above the form. Pass an empty title when your own page already carries one — the widget distinguishes “absent” (use the default) from “empty” (print nothing). |
source | Any text, cut to 16 characters. | widget | Recorded on the report row as where it came from. The on-site form records web; this route and the script bundle record widget unless you say otherwise, so your own embed's reports stay identifiable later. |
Script-bundle attributes (12)
| Attribute | Accepted values | Default | What it does |
|---|---|---|---|
data-allow-anonymous | 1 | 0 | 1 | Offer a signed-in member the choice to file anonymously. On, they see their own account AND a "Post anonymously" switch; enabling it dims their identity, states in words that their name is not attached and their session is not sent, and relabels the button "Report anonymously". Set 0 where attribution is required — the switch is then not rendered at all rather than shown and ignored. Signed-OUT reporting is always anonymous either way. |
data-zip | A 5-digit US ZIP. | — (the reader's own place) | The place the widget is about. Wins over the reader's saved location — see the iframe table. |
data-state | Any 2-letter USPS state code. | none | State-only placement. Enables the State and Federal rungs. |
data-level | address | block | zip | city | county | district | state | federal | the finest rung that resolved | Starting rung, clamped to what resolved. |
data-nav | 1 | 0 (valueless means 1) | 1 | Show the level ladder. 0 pins the widget to one rung. |
data-discussion | 1 | 0 (valueless means 1) | 1 | Starting state of the “Open a discussion” switch. |
data-title | Any text. An EMPTY value means “render no heading”. | Report an issue | Heading above the form. |
data-source | Any text, cut to 16 characters. | widget | Recorded on the report row as where it came from. |
data-height | A positive integer (pixels). | 560 | The height BEFORE the frame reports its own. The widget measures itself and posts its real height as soon as it paints — and again whenever it changes, so the box grows when the duplicate-check panel opens instead of clipping it. This value only stops the frame popping open from nothing, and is superseded within a frame or two. The host script clamps a reported height to 4000px. |
data-width | Any CSS length — 100%, 32rem, 420px. | 100% | Frame width. max-width is pinned to 100% alongside it, so an explicit width can never overflow a narrow host column. |
data-cg-site | An absolute CivicGate origin. | the site from CivicGate.configure({ site }), else https://www.civicgate.org | Which CivicGate deployment the frame loads from. For these two widgets this replaces data-cg-gateway: the frame is a CivicGate page, so it reaches the gateway itself rather than being handed an endpoint. |
data-cg-widget | person | wall | feed | issue-report | place | place-wall | ballot | issue-topics | login | — (required) | Which widget to mount. This attribute is what makes the element a CivicGate widget. |
The greyed row is the marker every widget carries. This widget mounts a
CivicGate-origin iframe, which holds no client and no credential, so
the shared client attributes — data-cg-key, data-cg-gateway, data-show-loading —
have no effect here and are deliberately not listed. Use
data-cg-site to point at a different CivicGate deployment.
Component props (11) — the Preact / Astro path
| Prop | Accepted values | Default | What it does |
|---|---|---|---|
gatewayUrl | An absolute or same-origin GraphQL endpoint. | — (required) | Where to read and write. Reads are public and the report mutation accepts an anonymous caller, so no credential accompanies it. |
site | An absolute CivicGate origin. | "" (same-origin, relative links) | Used only for links that must LEAVE an iframe — a discussion thread, the sign-in window. Empty means relative, which is what you want when the component is on a CivicGate page. |
zip | string | null | null | The place. Wins over the reader's saved location. |
state | string | null | null | Two-letter state, for a host that knows the state but not a ZIP. |
level | address | block | zip | city | county | district | state | federal | null | null (the finest rung that resolved) | Starting rung, clamped to what resolved. |
discussion | boolean | true | Starting state of the “Open a discussion for this” switch. |
nav | boolean | true | Show the level ladder. False pins the widget to the place its host chose. |
title | string | "Report an issue" | Heading text. Pass "" to render no heading. |
source | string | "web" | Recorded on the row as where the report came from — web | widget | api by convention, but any short string is stored. |
topicsHref | string | null | null | Where “See what's already reported” points. null hides the link — so a placement with no listing beside it shows no dead end. |
frameWidget | string | "issue-report" | The name this widget posts its height under. Only meaningful inside an /embed/… frame — outside one the height reporter never starts — and it must match the name the host script is listening for. |
Examples (5)
- iframe — one ZIP
<iframe src="https://www.civicgate.org/embed/issue-report?zip=19104" width="100%" height="620" style="border:0" loading="lazy" title="Report an issue — CivicGate"></iframe> - iframe — pinned to one rung, no heading
https://www.civicgate.org/embed/issue-report?zip=19104&level=district&nav=0&title=nav=0 removes the ladder and level=district fixes the rung, so every report from this embed is filed against PA-3. The empty title suppresses the widget's own heading because the host page has one.
- iframe — a state-wide placement
https://www.civicgate.org/embed/issue-report?state=PA&source=pa-newsWith no ZIP only the State and Federal rungs are offered. The source tag makes this embed's reports identifiable later.
- Script bundle — the whole integration
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script> <div data-cg-widget="issue-report" data-zip="19104"></div>No configure() call: this widget takes no key and no endpoint.
- Script bundle — pinned, sized, tagged
<div data-cg-widget="issue-report" data-zip="19104" data-level="zip" data-nav="0" data-discussion="0" data-title="" data-source="ourpaper" data-width="32rem" data-height="480"></div>
Notes (9)
- NO CREDENTIAL. Reporting is a mutation, and keys are read-only — a configured key is ignored rather than passed through.
- In the bundle this mounts an IFRAME (unlike person / wall / feed): it is a form, it may carry a session, and it must not inherit host styles.
- Anti-duplicate search runs from 3 characters, debounced and place-scoped — the reader can join an existing report instead of filing a second.
- Anonymous is first class: nothing is gated on a session, and a signed-in member filing anonymously has their token withheld, not ignored server-side.
- “Locate me” is offered to signed-in readers only: it turns browser coordinates into a ZIP through CivicGate's server-side geocoder, and the gateway refuses to spend that billable key anonymously. A button that always errors is worse than an absent one. In the script bundle the frame is granted allow="geolocation" — permission is still the reader's to give.
- Not yet wired: photo/file attachments and discussion threads on reports. The UI says so rather than offering a dead control.
- A report's exact location is never published. The jurisdiction shown on the form is what appears on the record.
- Validation failures are surfaced VERBATIM from the gateway (“A ZIP-level report needs a ZIP.”) — restating the server's rules in the widget would be a second copy that drifts.
- A hand-written iframe does not self-size — only the bundle listens for the height message. Give a raw iframe a generous fixed height.
<iframe src="https://www.civicgate.org/embed/issue-report" width="100%" height="620" style="border:0" loading="lazy" title="CivicGate widget"></iframe> Place Discussion
The issues already reported for a place, newest first, each linked to its discussion.
Available as: iframe (/embed/issue-topics) · script bundle (data-cg-widget="issue-topics")
See every place level and what each one needs →
Any page — CMS field, blog post, plain HTML. No build step.
Public data — no key, no account. Change the ZIP or level and the list follows.
Also embeddable as an iframe — no JavaScript:
<iframe src="https://www.civicgate.org/embed/issue-topics">. Options are the iframe parameters below.
iframe parameters (8)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
zip | A 5-digit US ZIP. Non-digits are stripped and the value is cut to 5. | — (the reader's own place) | Which place to list. Given here it wins over the reader's saved location, for the same reason as the reporter. |
state | Any 2-letter USPS state code. Case-insensitive, cut to 2. | none | State-only placement. Offers the State and Federal rungs. |
level | zip | city | county | district | state | federal — a shorter set than the reporter offers. | zip when a ZIP resolved, else state, else federal | Which rung to list. Deliberately shorter than the reportable set: the gateway scopes block, ZIP and city-by-ZIP reports on the SAME zip condition, so three separate rungs would return three identical lists. Block and city reports appear in the ZIP list, and every row prints its own jurisdiction so you can always see which one it belongs to. |
limit | 1–50 (out-of-range values are clamped). | 10 | How many reports load per page. A “Load more” button appears while more remain, and the footer prints “N of M”, so the reader is never left guessing whether they have seen everything. |
status | Any status string. The column is free text by design, so a jurisdiction can add its own step without a migration. | none (every status) | Show only reports at this status. Matched exactly. An unrecognised value is title-cased for display rather than dropped, so a jurisdiction's own step still renders as words. |
nav | 1 | 0 | 1 | Show the level ladder. 0 pins the list to the rung its host chose. The place row always renders. |
identity | 1 | 0 | 0 | Show the identity line — who the reader is signed in as, or a “Sign in with CivicGate” affordance. OFF by default because reading a public list needs no identity at all. |
title | Any text. An EMPTY value means “render no heading”. | Place Discussion | Heading above the list. |
Script-bundle attributes (12)
| Attribute | Accepted values | Default | What it does |
|---|---|---|---|
data-zip | A 5-digit US ZIP. | — (the reader's own place) | Which place to list. |
data-state | Any 2-letter USPS state code. | none | State-only placement. |
data-level | zip | city | county | district | state | federal | zip when a ZIP resolved, else state, else federal | Which rung to list. |
data-limit | A positive integer (clamped to 50). | 10 | How many reports load per page. |
data-status | Any status string. | none (every status) | Show only reports at this status. Matched exactly. |
data-nav | 1 | 0 (valueless means 1) | 1 | Show the level ladder. |
data-identity | 1 | 0 (valueless means 1) | 0 | Show the identity line. Off by default — a public list needs no identity. |
data-title | Any text. An EMPTY value means “render no heading”. | Place Discussion | Heading above the list. |
data-height | A positive integer (pixels). | 420 | The height BEFORE the frame reports its own. The widget measures itself and posts its real height as soon as it paints — and again whenever it changes, so the box grows when the duplicate-check panel opens instead of clipping it. This value only stops the frame popping open from nothing, and is superseded within a frame or two. The host script clamps a reported height to 4000px. |
data-width | Any CSS length — 100%, 32rem, 420px. | 100% | Frame width. max-width is pinned to 100% alongside it, so an explicit width can never overflow a narrow host column. |
data-cg-site | An absolute CivicGate origin. | the site from CivicGate.configure({ site }), else https://www.civicgate.org | Which CivicGate deployment the frame loads from. For these two widgets this replaces data-cg-gateway: the frame is a CivicGate page, so it reaches the gateway itself rather than being handed an endpoint. |
data-cg-widget | person | wall | feed | issue-report | place | place-wall | ballot | issue-topics | login | — (required) | Which widget to mount. This attribute is what makes the element a CivicGate widget. |
The greyed row is the marker every widget carries. This widget mounts a
CivicGate-origin iframe, which holds no client and no credential, so
the shared client attributes — data-cg-key, data-cg-gateway, data-show-loading —
have no effect here and are deliberately not listed. Use
data-cg-site to point at a different CivicGate deployment.
Component props (12) — the Preact / Astro path
| Prop | Accepted values | Default | What it does |
|---|---|---|---|
gatewayUrl | An absolute or same-origin GraphQL endpoint. | — (required) | Where to read from. The listing is public, so it carries no credential. |
site | An absolute CivicGate origin. | "" (same-origin, relative links) | Used only for links that must leave an iframe — a report's discussion thread. Empty means relative links, which is right on a CivicGate page. |
zip | string | null | null | The place to list. Wins over the reader's saved location. |
state | string | null | null | Two-letter state, for a host that knows the state but not a ZIP. |
level | zip | city | county | district | state | federal | null | null (zip when a ZIP resolved, else state, else federal) | Which rung to list, clamped to what resolved. |
limit | number | 10 | Page size. “Load more” appends the next page. |
status | string | null | null | Show only reports at this status. null shows every status. |
nav | boolean | true | Show the level ladder. False pins the list to the place its host chose. |
title | string | "Reported issues" | Heading text. Pass "" to render no heading — the usual choice when the host section already has one. |
showIdentity | boolean | false | Show the identity line. Off by default: a listing is public and identity is not needed to read it. |
reportHref | string | null | null | Where “Report an issue” points, in the empty state and in the footer. null hides the link, so a placement with no reporter beside it shows no dead end. |
frameWidget | string | "issue-topics" | The name this widget posts its height under. Only meaningful inside an /embed/… frame, and it must match the name the host script listens for. |
Examples (5)
- iframe — one ZIP
<iframe src="https://www.civicgate.org/embed/issue-topics?zip=19104" width="100%" height="520" style="border:0" loading="lazy" title="Reported issues — CivicGate"></iframe> - iframe — one rung, longer page, no heading
https://www.civicgate.org/embed/issue-topics?zip=19104&level=district&limit=25&nav=0&title= - iframe — open reports only, with the identity line
https://www.civicgate.org/embed/issue-topics?zip=19104&status=reported&identity=1The identity line is what lets a reader sign in from the listing before moving to the reporter.
- Script bundle — the whole integration
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script> <div data-cg-widget="issue-topics" data-zip="19104" data-limit="10"></div> - Script bundle — the pair, side by side
<div data-cg-widget="issue-topics" data-zip="19104" data-limit="5" data-nav="0"></div> <div data-cg-widget="issue-report" data-zip="19104" data-nav="0"></div>Two independent frames. They do not share state, so pin BOTH to the same place — a ladder left on in one of them would let the two drift apart in front of the reader.
Notes (7)
- Public data: no key, no account, in either integration mode. Like the reporter, a configured key is ignored rather than forwarded.
- In the bundle this mounts an IFRAME, like the reporter. It self-sizes; a hand-written iframe does not.
- Prints only what the gateway returned: no invented counts, no discussion link for a report without a thread, and an honest empty state.
- The place filter always includes FEDERAL reports alongside the selected rung, so every row shows its own place label.
- Rows show the author's display name, or “Anonymous” where the reporter chose it. A report's exact location is never published.
- A failed load says so and offers “Try again” rather than rendering an empty list, which would read as “nothing reported here”.
- A hand-written iframe does not self-size — only the bundle listens for the height message.
<iframe src="https://www.civicgate.org/embed/issue-topics" width="100%" height="520" style="border:0" loading="lazy" title="CivicGate widget"></iframe> Extended data
Edge cases and richer data sets — the things a site might want to show alongside the basics.
Federal funding map
A US map shaded by federal dollars landing in each state.
Available as: iframe (/embed/funding-map) — not in the script bundle.
iframe parameters — none
This widget takes no parameters.
Notes (1)
- iframe only — the choropleth renderer is too large to ship in the bundle.
<iframe src="https://www.civicgate.org/embed/funding-map" width="100%" height="520" style="border:0" loading="lazy" title="CivicGate widget"></iframe> In-state funding by district
One state's congressional districts shaded by the federal dollars landing in each.
Available as: iframe (/embed/district-funding/CA) — not in the script bundle.
iframe parameters (1)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
…/<state> | Any 2-letter USPS state code (CA, TX, NY, …). Case-insensitive. | — (required) | Path segment, not a query parameter. Selects the state whose districts are drawn. |
Examples (2)
- Texas
https://www.civicgate.org/embed/district-funding/TX - New York
https://www.civicgate.org/embed/district-funding/nyLower case works — the code is upper-cased server-side.
Notes (1)
- iframe only, for the same reason as the funding map.
<iframe src="https://www.civicgate.org/embed/district-funding/CA" width="100%" height="480" style="border:0" loading="lazy" title="CivicGate widget"></iframe> Member connections graph
A member of Congress at the centre of the people and committees they work with, drawn from public sponsorship data.
Available as: iframe (/embed/network/S000148) — not in the script bundle.
iframe parameters (1)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
…/<bioguideId | slug> | A Bioguide id (S000148) or a CivicGate person slug (chuck_schumer, or the hyphenated form). Both resolve. | — (required) | Path segment. Selects the member at the center of the graph. |
Examples (2)
- By Bioguide id
https://www.civicgate.org/embed/network/S000148 - By slug
https://www.civicgate.org/embed/network/chuck_schumerThe slug is resolved to the member's canonical id server-side before the graph is built.
Notes (1)
- iframe only.
<iframe src="https://www.civicgate.org/embed/network/S000148" width="100%" height="560" style="border:0" loading="lazy" title="CivicGate widget"></iframe> Ballot — what a place is voting on
The elections, contests and candidates on the ballot for a place — with an explicit statement of how much of that ballot CivicGate actually holds.
Available as: iframe (/embed/ballot/us-pa-d03) · script bundle (data-cg-widget="ballot")
Behaviour every widget shares →
No in-page editor for this one, on purpose: the script bundle mounts the
same /embed/… frame shown in the live preview at the bottom of this section, so
that preview is the widget rather than a reconstruction of it. To try a parameter,
change the query string on the preview URL — every attribute below maps to exactly one.
iframe parameters (17)
| Parameter | Accepted values | Default | What it does |
|---|---|---|---|
…/<placeKey> | A CivicGate place key: us · us-pa · us-pa-c42101 (county GEOID) · us-pa-d03 (congressional district, ZERO-PADDED). | — (the reader's own saved place, then the country) | Path segment. The precise way to name a place, and the same vocabulary the place walls use. Also accepted as ?place=. An unrecognised key is IGNORED rather than erroring, and the widget falls back — so check the place label it prints, which is always the place the SERVER resolved, never the one you asked for. |
place | The same place key as the path segment. | — | Query-parameter form, for parity with the script bundle's data-place. |
state | A 2-letter USPS state code. | — | The pieces form, for a caller holding a resolved ZIP rather than a key. Ignored when a place key is given. |
county | A 5-digit county GEOID (42101). Non-digits stripped; the last 5 are used. | — | County and district are SIBLINGS — a congressional district cuts across counties — so a resolved ZIP legitimately has both and neither contains the other. |
district | A congressional district number, 0–99. 0 is at-large and is a REAL value, not “absent”. | — | Narrow to one district. Zero-padded into the key for you. |
level | country | state | county | district | the narrowest rung the place can satisfy | How WIDE. Reads backwards until you see why: a district ballot includes the state and federal races too, because your senator genuinely is on your district's ballot — so a BROADER level shows FEWER races. A rung the place cannot fill is shown in the control but DISABLED, reading “— none”, never silently dropped. NOTE this is the ballot's own four-rung vocabulary, not the place locator's ladder: zip, city, block and address have no contests keyed to them. |
follow | 1 | 0 | 1 when no place was given, 0 when one was | Follow a Place locator instead of staying pinned. In an iframe this needs the script bundle to forward the host's events (see data-place="derive"); a hand-written iframe cannot hear them, so it is always pinned. A ballot pinned by its parameters never starts moving on its own. |
past | 1 | 0 | 0 | HISTORY MODE — drops the “upcoming only” filter, so the list is past AND upcoming elections, NEWEST FIRST. CivicGate holds NO election results, so this shows who was ON the ballot, not who won; the widget prints both of those facts in words above the list and never implies a winner. |
within-days | A positive integer. 0 or absent = no horizon. | — (no horizon) | Only elections within this many days. CivicGate's own homepage uses 90. The empty state names the horizon it checked, so “nothing in the next 90 days” is never mistaken for “nothing scheduled”. |
candidates | A positive integer. | 6 | How many candidates each contest shows before “Show all N”. The server already orders them incumbent first, then by money raised, then by name, so the visible few are the recognisable ones rather than an arbitrary slice. A dozen candidates in a primary is ordinary. |
controls | 1 | 0 | 1 | Show the breadth control. |
history | 1 | 0 | 1 | Show the “Past elections” toggle. |
size | sm | md | lg | md | Candidate card size (36 / 48 / 64px avatar). |
lens | 1 | 0 | 1 | Hover/tap detail lens on a candidate card. Only ever offered for a candidate who HAS a CivicGate profile — offering it otherwise could only answer “not found”. |
bar | 1 | 0 | 1 | Party-coloured left bar on candidate cards. |
title | Any string. "" renders NO heading. | On your ballot | The heading. An empty value is honoured, for a host page that already has one. |
max-height | Any CSS length, or none. | none | How tall the list grows before it scrolls internally. In a frame the default is none — the frame reports its own height, so an inner scroller would give the reader two nested scrollbars. |
Script-bundle attributes (19)
| Attribute | Accepted values | Default | What it does |
|---|---|---|---|
data-place | A place key (us-pa-d03), OR the literal derive. | — (the reader's own saved place) | ONE attribute, two meanings, told apart by the value, because to you they are the same question — which place? derive means “follow whatever Place locator is on this page”; anything else is a place key. Two separate attributes would let you set both and silently get an answer that ignores one. |
data-id / data-slug | A place key. | — | Accepted as the place key too, so the identity attributes mean the same thing here as on every other id-taking widget. data-place wins. |
data-state | A 2-letter state code. | — | The pieces form. Also the STARTING place when data-place="derive", until the first event arrives. |
data-county | A 5-digit county GEOID. | — | Narrow to a county. |
data-district | An integer 0–99. 0 = at-large. | — | Narrow to a district. |
data-level | country | state | county | district | the narrowest rung that resolved | How wide. Clamped to what the place can satisfy. |
data-past | 1 | 0 (valueless means 1) | 0 | History mode — past AND upcoming, newest first. Results are NOT in CivicGate and the widget says so. |
data-within-days | A positive integer. | — (no horizon) | Only elections within this many days. |
data-candidates | A positive integer. | 6 | Candidates shown per contest before “Show all N”. |
data-controls | 1 | 0 | 1 | Show the breadth control. |
data-history | 1 | 0 | 1 | Show the “Past elections” toggle. |
data-size | sm | md | lg | md | Candidate card size. |
data-lens | 1 | 0 | 1 | Hover detail lens on candidate cards. |
data-bar | 1 | 0 | 1 | Party-coloured left bar. |
data-title | Any string. "" renders no heading. | On your ballot | The heading. |
data-height | A positive integer (pixels). | 620 | The height BEFORE the frame reports its own. The widget measures itself and posts its real height as soon as it paints — and again whenever it changes, so the box grows when the duplicate-check panel opens instead of clipping it. This value only stops the frame popping open from nothing, and is superseded within a frame or two. The host script clamps a reported height to 4000px. |
data-width | Any CSS length — 100%, 32rem, 420px. | 100% | Frame width. max-width is pinned to 100% alongside it, so an explicit width can never overflow a narrow host column. |
data-cg-site | An absolute CivicGate origin. | the site from CivicGate.configure({ site }), else https://www.civicgate.org | Which CivicGate deployment the frame loads from. For these two widgets this replaces data-cg-gateway: the frame is a CivicGate page, so it reaches the gateway itself rather than being handed an endpoint. |
data-cg-widget | person | wall | feed | issue-report | place | place-wall | ballot | issue-topics | login | — (required) | Which widget to mount. This attribute is what makes the element a CivicGate widget. |
The greyed row is the marker every widget carries. This widget mounts a
CivicGate-origin iframe, which holds no client and no credential, so
the shared client attributes — data-cg-key, data-cg-gateway, data-show-loading —
have no effect here and are deliberately not listed. Use
data-cg-site to point at a different CivicGate deployment.
Component props (15) — the Preact / Astro path
| Prop | Accepted values | Default | What it does |
|---|---|---|---|
gatewayUrl | An absolute GraphQL endpoint. | — | Required. |
place | A place key — us-pa-d03. | — | Pinned place, the precise form. Wins over the pieces below. |
state / countyFips / district | The pieces. | — | Pinned place, for a caller holding a resolved ZIP. district accepts 0 (at-large) as a real value. |
follow | boolean | true when no static place was given | Subscribe to the shared place service AND the cg:place-change DOM event. Pass it explicitly alongside a static place to get “start here, then follow”. |
level | country | state | county | district | the narrowest rung that resolved | Starting breadth. Clamped; a reader's explicit choice survives the place moving whenever the new place can still satisfy it. |
includePast | boolean | false | History mode — past AND upcoming, newest first (it drops the date filter rather than inverting it). |
withinDays | A positive integer, or null. | null | Horizon in days. The homepage passes 90. |
title | string. "" renders no heading. | On your ballot | The heading. |
showLevel / showHistory | boolean | true | Whether each control is offered. |
urlSync | boolean | false | Mirror the breadth and history choice into ?ballotLevel= / ?ballotPast= and restore them on load. A history REPLACE, never a push — a breadth control must not fill the reader's back button. Off by default: a widget must not rewrite a host page's query string uninvited. |
maxCandidates | A positive integer. | 6 | Candidates per contest before “Show all N”. |
size / partyBar / lens | sm|md|lg · boolean · boolean | md · true · true | Candidate card presentation. The lens is additionally suppressed for any candidate with no profile. |
target | _self | _blank | _self | Link target. Embeds use _blank. |
maxHeight | Any CSS length, or "none". | none | Internal scroll cap. "none" opts OUT of the scroll container entirely rather than setting a huge value — an unbounded box still declaring overflow-y:auto traps scroll chaining on touch. |
class | A class string. | — | Extra classes on the root. |
Emitted events (1)
| Event | Fires | event.detail |
|---|---|---|
cg:place-change | Any Place locator on the page moved. | The whole PlaceState — { level, resolution: { zip, state, stateName, district, countyFips, city, … }, source, status, locked }. The ballot CONSUMES this when it is following a locator; it emits nothing of its own. Your own picker can drive it by dispatching the same shape. |
All on window. A framed widget re-posts its events to the host, where the bundle re-fires them — so one listener works whether you used the component, the bundle or an iframe.
Examples (7)
- 1 · Static place — a site about one district
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script> <div data-cg-widget="ballot" data-place="us-pa-d03"></div>Pinned. Nothing on the page can move it. The place key is the last segment of the URL pattern us-<state>-d<NN> — district numbers are zero-padded, and 00 is at-large.
- 2 · Dynamic — follows the Place locator on the page
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script> <!-- The reader picks the place here… --> <div data-cg-widget="place" data-view="header"></div> <!-- …and the ballot follows it. No JavaScript of your own. --> <div data-cg-widget="ballot" data-place="derive" data-state="PA"></div>The two widgets never reference each other — the locator EMITS cg:place-change and the ballot LISTENS — so either works with the other absent. data-state is only the starting place until the reader moves the locator.
- 3 · Driving it from your own place picker
window.dispatchEvent(new CustomEvent("cg:place-change", { detail: { level: "district", resolution: { state: "OH", countyFips: null, district: 3 } }, }));It listens for the EVENT, not for our widget, so your own UI can drive it by emitting the same shape.
- 4 · As an iframe — always pinned
<iframe src="https://www.civicgate.org/embed/ballot/us-pa-d03?within-days=90" width="100%" height="620" style="border:0" title="What's on the ballot"></iframe>A hand-written frame cannot hear the host page's events and does not self-size — give it a generous fixed height, and use the script bundle if you want it to follow a locator.
- 5 · With an API key configured on the page — the key is IGNORED
<script src="https://www.civicgate.org/widget/civicgate-widget.js" defer></script> <script>CivicGate.configure({ apiKey: "cg_live_…" })</script> <!-- Your feed widget uses the key. The ballot does not, and must not. --> <div data-cg-widget="feed" data-limit="10"></div> <div data-cg-widget="ballot" data-place="us-pa-d03"></div>Elections, contests and candidate filings are public records. Serving public data must never spend an account's rate limit on data that needed no identity at all — the same rule the subject wall follows — so this widget deliberately does not read a key even when the page has one.
- 6 · The next 90 days only, no controls — a homepage tout
<div data-cg-widget="ballot" data-place="derive" data-within-days="90" data-controls="0" data-history="0" data-candidates="3" data-title="Coming up where you live"></div>The empty state names the horizon it checked, so “nothing in the next 90 days” can never be read as “nothing is scheduled”.
- 7 · History — who was on the ballot, never who won
<div data-cg-widget="ballot" data-place="us-pa-d03" data-past="1"></div>Past AND upcoming, newest first. CivicGate holds no results data, and the widget states that above the list rather than leaving a reader to infer an outcome from the ordering. A past contest with no filings reads “No candidates are recorded”, not “not filed yet” — the wrong tense over a 2022 race is how a reader decides the widget is broken.
Notes (11)
- COVERAGE IS THE POINT, and it renders in EVERY state — loading, empty, error and full — before the races, never collapsed. CivicGate holds FEDERAL contests and no state or local ones: a Philadelphia voter's real November ballot has a governor's race, a lieutenant governor, state legislative seats and city races, and we can show exactly one of those. An unlabelled partial ballot is a confident, authoritative WRONG answer to the highest-stakes question this platform answers.
- An empty result says “that is what CivicGate holds for this place at this breadth”, never “there are no races here”. Those are different statements and only one of them is ours to make. The WHY comes from coverage.note, rendered VERBATIM — asking for a state breadth at a district place correctly returns nothing, because a House race is not decided statewide, and the note says exactly that. The widget adds a one-click “Narrow to My district” action rather than paraphrasing the advice: a sentence we maintain beside a sentence the gateway maintains is how the two end up disagreeing.
- coverage.federal = "none" MEANS SOMETHING DIFFERENT from coverage.state = "none", and the widget renders them differently. State and local are structurally absent — nothing is ingested — so those read “Not in CivicGate yet”. Federal “none” means no rows matched at the breadth you asked for, and reads “None found at this level”: CivicGate holds those races, they are simply not decided in that area. Printing “not in CivicGate” there would be a false coverage claim about the one tier we hold in full.
- NO ELECTION RESULTS. CivicGate does not hold winners. History mode shows who was on the ballot and says so; nothing in this widget implies an outcome.
- A candidate with no CivicGate profile is rendered WITHOUT a link and says why. Minting /people/<fec-id> for them would be a 404 dressed as a profile.
- Money is the FEC receipts figure as filed. A candidate with none reads “No FEC receipts reported”, never $0 — those are different facts.
- No credential. A configured apiKey is deliberately not read (see example 5).
- The place label shown is always the one the SERVER resolved, alongside its place key — asking for a county breadth against a district-only place resolves to the state, and printing the request would tell the reader we are showing something we are not.
- county and district are SIBLINGS, not a hierarchy: a congressional district cuts across counties, so a resolved ZIP legitimately belongs to both.
- In the script bundle this mounts an IFRAME (like place, issue-report and login): it is a rich styled surface, and the bundle ships no stylesheet into a host document. data-place="derive" still works because the bundle forwards the host's cg:place-* events INTO the frame — which a hand-written iframe cannot do.
- Candidates are ordered by the server: incumbent first, then money raised, then name. That is what makes truncating to six safe rather than arbitrary.
<iframe src="https://www.civicgate.org/embed/ballot/us-pa-d03" width="100%" height="620" style="border:0" loading="lazy" title="CivicGate widget"></iframe> Valid values for the wall, feed, issue and login widgets. Every table here is generated from the same catalog the code uses — the feed catalog for subjects and events, the issue widgets' own place module for levels, the login widget's own OIDC module for scopes — so they cannot drift from what the API actually accepts.
Place levels (8)
The jurisdiction ladder the two issue widgets speak, and what each rung needs before it can be selected.
Use as ?level= or data-level on Report an issue and Place Discussion.
A level is clamped to what actually resolved: a rung whose required fields we do
not hold is dropped rather than sent to the API, which would answer with a validation sentence
the reader has no way to act on. The two widgets offer different sets on purpose — the listing's
is shorter because the gateway scopes block and ZIP reports on the same zip
condition, so those rungs would return one list. Address is the same case and is likewise
not browsable yet — an address report appears in its ZIP's list, and a map picker for
selecting a block or address to BROWSE is separate work.
City and county are genuinely wider — City and county are genuinely wider —
a city expands to its ZIPs and a county matches its FIPS code — so both are offered.
| Level | Shown as | Report an issue | Reported issues | What it needs |
|---|---|---|---|---|
address | Address | Yes | No | A resolved ZIP AND a typed street address — the reporter is asked for it, and a ZIP alone is not accepted. Deliberately never auto-filled from the reader's own location: the place service still refuses to publish an address it DERIVED (publishablePlace snaps it to a ZIP), so what is stored here is only ever an address someone deliberately stated about a place. Reports are public, so the address is too. |
block | Block / area | Yes | No | A resolved ZIP. The reporter also asks for a free-text description of the block or area. |
zip | ZIP | Yes | Yes | A resolved ZIP. |
city | City | Yes | Yes | A resolved ZIP. Filed with the ZIP — CivicGate holds no Census place GEOID yet, so a city report carries none. |
county | County | Yes | Yes | A state AND a county FIPS code — both now resolved from a ZIP via the Census geocoder, so the rung IS offered whenever the county resolves. Reports link through to the county's own page. |
district | District | Yes | Yes | A resolved ZIP (the congressional district comes from it). |
state | State | Yes | Yes | A state — from a resolved ZIP, or from the state parameter on its own. |
federal | Federal | Yes | Yes | Nothing. Always available, and the fallback whenever a finer rung cannot be satisfied. |
county | County | No | No | A state AND a county FIPS code — both now resolved from a ZIP via the Census geocoder, so the rung IS offered whenever the county resolves. Reports link through to the county's own page. |
The greyed row is the one rung the API knows and the widgets never offer. It is listed rather
than omitted because level=county is accepted by the embed route and then falls
through to federal — silently, if this table did not say so.
The Yes/No columns are computed by calling the widgets' own level functions, so they are the
same answer the widget gives rather than a second opinion about it.
Sign-in scopes (8)
What an application may ask the identity server for, and what each scope actually releases.
Sent space-separated as ?scope= (or data-scope) on
Login with CivicGate, which becomes the
scope parameter of the authorization request to https://auth.civicgate.org/api/v1/oauth/authorize.
Nothing is released without the member's consent, and the token response
reports what was actually granted — check it, because a scope the server does not publish
is silently dropped rather than rejected, so a typo costs you the data
with no error to notice.
| Scope | In the default? | What it gives your application |
|---|---|---|
openid | Yes | Required. Issues an ID token identifying the member (the `sub` claim). An authorization request without it is rejected. |
profile | Yes | Display name, username and profile photo. |
email | Yes | Email address and whether it is verified. |
offline_access | No | A refresh token, so your application can keep the session alive without sending the member back through sign-in. |
civic:location | No | The member's saved place — city, state and ZIP. Never the street address: that never leaves CivicGate's server at any tier. |
civic:interests | No | The issues the member follows. |
civic:positionsre-prompts every time | No | The member's declared positions on issues. |
civic:activityre-prompts every time | No | Actions the member has taken — petitions signed, representatives contacted. |
The rows marked re-prompts every time are political-belief data about a private individual. The identity server asks for consent on every request for them even when the member granted them before — so ask for them only if your application genuinely uses them, or you are adding a consent screen your readers will learn to click through.
Subject types (18)
Everything a wall can be about, and the id each type takes.
Use as /embed/wall/<type>/<id> or data-type + data-id. The id is the one in that entity's CivicGate URL — except for user, where it is the stable user id, never the handle.
| Type | Group | What it is | Where its id comes from |
|---|---|---|---|
person | people | Member of Congress | /people/<id> |
official | people | Official | /official/<id> |
governor | people | Governor | /governor/<id> |
president | people | President | /president/<id> |
judge | people | Judge | /judge/<id> |
state_legislator | people | State legislator | /people/<id> |
appointee | people | Appointee | /official/<id> |
candidate | people | Candidate | /people/<id> |
bill | bills | Federal bill | /bill/<id> |
state_bill | bills | State bill | /state-bill/<id> |
user | users | User | /user/<id> |
post | users | Post | /post/<id> |
issue | civic | Issue | /issue/<id> |
committee | civic | Committee | /committees/<id> |
hearing | civic | Proceeding | /hearing/<id> |
election | civic | Election | /elections/<id> |
jurisdiction | civic | Place | /place |
policy_area | civic | Policy area | /bills?policyArea=%3Cid%3E |
Subject groups (4)
The filter chips the following feed shows when filters=1.
The filter chips shown when filters=1. Each selects every subject type in its group.
| Group | Label | Subject types it selects |
|---|---|---|
people | People | personofficialgovernorpresidentjudgestate_legislatorappointeecandidate |
bills | Bills | billstate_bill |
users | Users | userpost |
civic | Civic | issuecommitteehearingelectionjurisdictionpolicy_area |
Event types (38)
Every kind of update that can appear in a feed or on a wall.
A subject only ever carries the events listed against it. Rows marked feed only never generate a notification — they are low-signal updates that belong in a stream but not in an inbox.
| Event | Applies to | Category | What it means |
|---|---|---|---|
sponsored_bill | person | people | This person introduced a bill as primary sponsor. |
member_vote | person | people | This person cast a recorded floor vote. |
stock_trade | person | trades | This person reported a stock trade. |
position_conflict | person | people | A documented conflict between this person's money and their vote. |
person_fact | personofficialgovernorstate_legislator | people | A new sourced background fact was added for this person. |
role_change | personofficialgovernorappointeepresidentstate_legislator | people | This person started, ended, or changed an office. |
executive_order | president | people | The president signed an executive order. |
proclamation | president | people | The president issued a proclamation. |
presidential_veto | president | bills | The president vetoed a bill. |
donation | person | funding | A newly disclosed contribution to this person. |
state_sponsored_bill | state_legislator | people | This state legislator sponsored a bill. |
judicial_ruling | judge | rulings | This judge published an opinion. |
news_mentionfeed only | personofficialgovernorpresidentcandidatebillissuecommittee | news | A news article mentioned this subject. |
bill_status | bill | bills | This bill moved to a new status. |
bill_action | bill | bills | A new legislative action was recorded on this bill. |
bill_text | bill | bills | New or updated full text was published for this bill. |
bill_vote | bill | bills | A roll-call vote was held on this bill. |
state_bill_action | state_bill | bills | A new action was recorded on this state bill. |
state_bill_text | state_bill | bills | New text was published for this state bill. |
user_post | user | community | This user posted to their wall. |
user_status | user | community | This user posted a short status update. |
user_profilefeed only | user | community | This user updated their public profile. |
user_content | user | community | This user shared something from CivicGate. |
post_reply | postuser | community | Someone replied to this post. |
post_mediafeed only | post | community | Media finished uploading to this post. |
issue_billfeed only | issue | issues | A bill was tagged to this issue. |
issue_bill_vote | issue | issues | A bill tagged to this issue had a roll-call vote. |
issue_bill_status | issue | issues | A bill tagged to this issue reached a new legislative milestone. |
policy_area_bill | policy_area | issues | A new bill was filed under this policy area. |
committee_bill | committee | committees | A bill was referred to this committee. |
hearing_scheduled | committeehearing | committees | This committee scheduled a hearing, markup or business meeting. |
hearing_held | committeehearing | committees | This committee held a hearing, markup or business meeting. |
committee_vote | committeebillpersonhearing | committees | A recorded vote was held in committee — an amendment, a motion to report, a subpoena. |
hearing_transcriptfeed only | committeehearing | committees | A transcript became available for this proceeding. |
hearing_testimony | personofficialgovernorjudgeappointee | people | This person testified at a congressional hearing. |
election_contest | election | elections | A contest was added to this election. |
election_update | election | elections | Details of this election changed. |
election_reminder | jurisdictionelection | elections | An election in a place you follow is coming up. |
Behaviour every widget shares
Theming, what a widget loads, provenance, and how to ask for one we do not have.
- Widgets inherit light/dark from the visitor's system preference.
- They load only the data their parameters ask for, and carry a small "View on CivicGate" link back to the source.
- All CivicGate figures are traceable: widgets link out to the upstream record wherever one exists.
- Want a widget we don't list yet? Tell us — the set grows from real requests.