Render a published Google Sheet through an HTML <template>. Point it at a sheet, an API key, and a template with data-column-name slots — it fetches the rows and stamps them into the DOM. Zero dependencies, ESM only. ~3 KB unminified.
<section>
<template id="article">
<article>
<h3 data-column-name="titel"></h3>
<p data-column-name="inhalt" data-html></p>
</article>
</template>
<div class="articles"></div>
</section>
<script type="module">
import gSheets from '@copperdesign/gsheets';
gSheets({
target: '.articles',
template: '#article',
sheetId: '1t6WRNO0Swv844uWZKkqC3Ot-0r67CI9kzKs5lZ7gNzg',
apiKey: 'YOUR_GOOGLE_API_KEY',
});
</script>That's the whole API.
- Reads any web-published Google Sheet. First row is treated as column headers; every subsequent row becomes a clone of the template, with
data-column-name="<header>"slots filled in. Header matching is case-insensitive. - Renders into a
<template>, not a hardcoded layout. You own the markup. - Escapes by default.
textContentis the default; opt intoinnerHTMLper-slot withdata-htmlfor cells you trust (your own sheet). - Optional consent gate. Provide a
consentadapter and the library paints a click-to-load CTA first, fetching only after opt-in. - State templates. Provide
loadingTemplate/emptyTemplate/errorTemplatefor the three lifecycle states. - Idempotent teardown. The returned function cancels the in-flight fetch, removes listeners, and clears the target. Safe to call from SPA cleanup.
npm install @copperdesign/gsheetsOr vendor index.js directly — it's one file with no dependencies.
The template is plain HTML inside a <template> element. Four attributes control rendering:
| Attribute | Effect |
|---|---|
data-column-name="titel" |
element.textContent = row.titel (escaped) |
data-column-name="inhalt" data-html |
element.innerHTML = row.inhalt (trusted) |
data-column-name="link" data-attr="href" |
element.setAttribute('href', row.link) |
data-column-name="…" data-remove-empty |
Remove the element when the value is empty (default: add hidden) |
The conventions match @copperdesign/gcal one-for-one — data-column-name instead of data-slot, otherwise identical.
import gSheets from '@copperdesign/gsheets';
const teardown = gSheets(options);gSheets({
// Required
target: '.articles', // selector or Element
template: '#article', // selector, <template>, or HTML string
sheetId: '…', // the long string in the sheet's URL
apiKey: '…', // Google API key with the Sheets API enabled
// Optional
range: 'A1:Z100', // A1-notation range. Default: 'A1:Z100'
spinner: '<div>Loading…</div>', // HTML used while fetching (if no loadingTemplate)
loadingTemplate: '#article-loading', // selector, Element, or HTML string
emptyTemplate: '#article-empty',
errorTemplate: '#article-error', // can bind data-column-name="message"
// Consent gate (omit for no gating)
consent: {
check: () => window.consent?.hasConsent?.('gsheets') ?? false,
request: async () => window.consent?.optIn?.('gsheets'),
ctaTemplate: '#article-cta', // shown when check() is false
event: 'consentchange', // DOM event to re-render on (default)
},
// Hooks
transformRow: (row, i, all) => ({ ...row, slug: slugify(row.titel) }),
onError: (err) => console.error(err),
});A teardown() function. Aborts any in-flight request, removes the consentchange listener, and empties the target.
The library never imports a specific consent SDK. You implement a small adapter:
import gSheets from '@copperdesign/gsheets';
gSheets({
/* … */,
consent: {
check: () => window.myConsent.has('gsheets'),
request: async () => window.myConsent.optIn('gsheets'),
ctaTemplate: '#article-cta',
},
});<template id="article-cta">
<div class="consent-card">
<p>Beim Laden der Tabelle werden Daten an Google übertragen.</p>
<button data-gsheets-optin>Inhalt laden</button>
</div>
</template>When consent is granted (synchronously or by request() resolving), the library fetches and renders. If a consentchange CustomEvent fires on document later (e.g. from a global cookie banner), it re-renders automatically.
The recommended pairing — a zero-dependency, click-to-load consent gate built to the same shape as gSheets. The adapter is three lines:
import gSheets from '@copperdesign/gsheets';
import easyCookieConsent from '@copperdesign/easy-cookie-consent';
const ecc = easyCookieConsent({
// easy-cookie-consent shows a global modal on load by default.
// If gSheets' CTA template is your only consent UI, set this to false.
// Leave it true (default) to pair the global banner with the per-embed CTA.
showModal: false,
// Re-render gSheets when consent flips elsewhere on the page
// (global modal, revoke link, …).
onConsent: () => document.dispatchEvent(new CustomEvent('consentchange')),
});
gSheets({
/* … */,
consent: {
check: () => ecc.hasConsent('gsheets'),
request: () => ecc.optIn('gsheets'),
ctaTemplate: '#article-cta',
},
});gSheets stays provider-agnostic — easy-cookie-consent is opt-in, not bundled.
<template id="article-empty">
<p>Keine Einträge.</p>
</template>
<template id="article-loading">
<p aria-busy="true">Daten werden geladen…</p>
</template>
<template id="article-error">
<p class="gsheets-error">Fehler: <span data-column-name="message"></span></p>
</template>The error template renders through the same binding path as row templates — data-column-name="message" (escaped) or data-html (trusted) work as expected.
Three things have to be in place before the library can fetch anything:
-
Publish the sheet. Open the sheet → File → Share → Publish to the web → publish the whole document (or a single tab). This is what lets the API key read the sheet without OAuth. The Sheet ID is the long string in the sheet's URL between
/d/and/edit. -
Create an API key. Google Cloud Console → APIs & Services → Library → enable Google Sheets API. Then Credentials → Create credentials → API key.
-
Restrict the key. It ships in your page source — anyone viewing your site can read it. Two restrictions stop it being reused elsewhere:
- Application restrictions → HTTP referrers (websites) → add every host the embed runs on. Google requires the
*wildcard form:https://example.com/*,https://www.example.com/*, plus any staging or preview domain (on Weebly, alsohttps://*.weebly.com/*). - API restrictions → Restrict key → select Google Sheets API only.
- Application restrictions → HTTP referrers (websites) → add every host the embed runs on. Google requires the
Without the restrictions the key still works, but any visitor who copies it can use your project's quota from anywhere.
Weebly doesn't have a build step or package manager, but the module is a single ES module and loads fine from a CDN. Two pieces, both in an Embed Code element on the page:
<template id="article">
<article>
<h3 data-column-name="titel"></h3>
<p data-column-name="inhalt" data-html></p>
</article>
</template>
<div class="articles"></div>
<script type="module">
import gSheets from 'https://unpkg.com/@copperdesign/gsheets@0.1.2';
gSheets({
target: '.articles',
template: '#article',
sheetId: 'YOUR_SHEET_ID',
apiKey: 'YOUR_GOOGLE_API_KEY',
});
</script>Notes:
- Pin the version (
@0.1.0above) so a future release doesn't change behavior on a site you don't actively maintain. jsDelivr works equivalently —https://cdn.jsdelivr.net/npm/@copperdesign/gsheets@0.1.2/+esm. - Before you paste this live, complete the steps in Google Cloud setup above — in particular, add
https://*.weebly.com/*and any custom domain under HTTP referrers, since the key ships in page source. - Edits to the sheet appear on next page load. No publish step on the Weebly side; the data is pulled live.
- If your theme has a cookie banner, drop in the consent adapter (see Consent flow) and the embed will gate itself behind opt-in automatically.
Modern evergreens. Requires native fetch, <template>, URLSearchParams. No build step required.
This module is the modern successor to a 2018 jQuery plugin (jquery.gsheet.js) and a later jQuery-free rewrite that inlined a consent gate. This release drops jQuery, makes the binding rules consistent with @copperdesign/gcal, defaults to safe textContent rendering, and makes consent gating a contract rather than a built-in.
MIT — see LICENSE.
Created by Christian Fillies.