# How to Build a Simple CMS With Google Sheets (Manage Website Content Without a Dashboard)

Not every website needs WordPress. Not every project needs a headless CMS with a monthly bill. Sometimes you just need a simple way to let someone update website content without touching code.

Google Sheets can do that. And with Jsonsheets, your spreadsheet becomes a live API that feeds content directly to your website.

Here's how to build a lightweight CMS that anyone on your team can manage.

The Idea

Instead of hardcoding text, images, and page content into your website, you store everything in a Google Sheet. Your website fetches that content through an API at load time. When someone updates the spreadsheet, the website updates automatically.

No admin dashboard to build. No login system. No database migrations. Just a spreadsheet and an API.

Step 1: Structure Your Content in Google Sheets

Create a spreadsheet with separate tabs for different content types.

**Tab: "pages"**

| slug | title | description | hero\_image | body |
| --- | --- | --- | --- | --- |
| home | Welcome to Acme Co | We build tools for modern teams | [https://example.com/hero.jpg](https://example.com/hero.jpg) | Acme Co helps startups ship faster... |
| about | About Us | Meet the team behind Acme | [https://example.com/team.jpg](https://example.com/team.jpg) | Founded in 2024, Acme Co started... |
| contact | Get In Touch | We'd love to hear from you |  | Email us at [hello@acme.com](https://replit.com/@gurbhagatsandhu/Sheet-Json) or... |

**Tab: "navigation"**

| label | href | order | visible |
| --- | --- | --- | --- |
| Home | / | 1 | true |
| About | /about | 2 | true |
| Blog | /blog | 3 | true |
| Contact | /contact | 4 | true |
| Careers | /careers | 5 | false |

**Tab: "settings"**

| key | value |
| --- | --- |
| site\_name | Acme Co |
| tagline | Tools for modern teams |
| logo\_url | [https://example.com/logo.png](https://example.com/logo.png) |
| primary\_color | #4F46E5 |
| footer\_text | 2026 Acme Co. All rights reserved. |

Each tab serves a different purpose, and each one gets its own API endpoint automatically.

Step 2: Connect to Jsonsheets

1. Sign in at [jsonsheets.com](https://jsonsheets.com)
    
2. Browse your Drive and select the spreadsheet
    
3. Jsonsheets detects all three tabs and creates endpoints for each
    

Your endpoints:

```javascript
GET /api/v1/acme-website/pages
GET /api/v1/acme-website/navigation
GET /api/v1/acme-website/settings
```

Step 3: Fetch Content in Your Website

Here's how to load page content dynamically.

**Vanilla JavaScript:**

```javascript
const API_KEY = "js_your_api_key_here";
const BASE_URL = "https://jsonsheets.com/api/v1/acme-website";

async function fetchContent(tab, filters = "") {
  const url = `${BASE_URL}/${tab}${filters ? "?" + filters : ""}`;
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${API_KEY}` }
  });
  const json = await res.json();
  return json.data;
}

// Load a specific page by slug
async function loadPage(slug) {
  const pages = await fetchContent("pages", `slug=${slug}`);
  if (pages.length === 0) return null;

  const page = pages[0];
  document.title = page.title;
  document.getElementById("page-title").textContent = page.title;
  document.getElementById("page-body").innerHTML = page.body;

  if (page.hero_image) {
    document.getElementById("hero-img").src = page.hero_image;
  }
}

// Load navigation
async function loadNav() {
  const navItems = await fetchContent("navigation", "visible=true");
  const nav = document.getElementById("main-nav");

  navItems
    .sort((a, b) => parseInt(a.order) - parseInt(b.order))
    .forEach(item => {
      const link = document.createElement("a");
      link.href = item.href;
      link.textContent = item.label;
      nav.appendChild(link);
    });
}

// Load site settings
async function loadSettings() {
  const settings = await fetchContent("settings");
  const config = {};
  settings.forEach(row => { config[row.key] = row.value; });

  document.getElementById("site-name").textContent = config.site_name;
  document.getElementById("footer-text").textContent = config.footer_text;
}

// Initialize
loadNav();
loadSettings();
loadPage("home");
```

**React version:**

```javascript
import { useState, useEffect } from "react";

const API_KEY = "js_your_api_key_here";
const BASE = "https://jsonsheets.com/api/v1/acme-website";

function useCMSContent(tab, filters) {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const params = filters ? "?" + new URLSearchParams(filters) : "";
    fetch(`${BASE}/${tab}${params}`, {
      headers: { Authorization: `Bearer ${API_KEY}` },
    })
      .then(res => res.json())
      .then(json => setData(json.data))
      .finally(() => setLoading(false));
  }, [tab, JSON.stringify(filters)]);

  return { data, loading };
}

function Page({ slug }) {
  const { data, loading } = useCMSContent("pages", { slug });
  const page = data[0];

  if (loading) return <p>Loading...</p>;
  if (!page) return <p>Page not found.</p>;

  return (
    <article>
      {page.hero_image && <img src={page.hero_image} alt={page.title} />}
      <h1>{page.title}</h1>
      <p>{page.description}</p>
      <div>{page.body}</div>
    </article>
  );
}

function Navigation() {
  const { data } = useCMSContent("navigation", { visible: "true" });

  return (
    <nav>
      {data
        .sort((a, b) => a.order - b.order)
        .map(item => (
          <a key={item.href} href={item.href}>{item.label}</a>
        ))}
    </nav>
  );
}
```

Step 4: Let Your Team Manage Content

This is where the magic happens. Your content editors don't need to learn a CMS. They just open the Google Sheet and:

* **Edit text** — Change any cell in the "pages" tab and the website updates
    
* **Add new pages** — Add a row with a new slug, title, and body
    
* **Reorder navigation** — Change the order numbers in the "navigation" tab
    
* **Hide pages** — Set `visible` to `false` in the navigation tab
    
* **Update branding** — Change colors, tagline, or logo in the "settings" tab
    

No deployments. No pull requests. No waiting for a developer.

Advanced: Multi-Language Support

Add a `language` column to your pages tab:

| slug | language | title | body |
| --- | --- | --- | --- |
| home | en | Welcome | Welcome to Acme... |
| home | es | Bienvenido | Bienvenido a Acme... |
| home | fr | Bienvenue | Bienvenue chez Acme... |

Then fetch by language:

```javascript
GET /api/v1/acme-website/pages?slug=home&language=es
```

Your content team can manage translations side by side in the same spreadsheet.

Advanced: Blog Posts in a Spreadsheet

Add a "blog" tab:

| slug | title | author | published\_date | category | excerpt | body | is\_published |
| --- | --- | --- | --- | --- | --- | --- | --- |
| launch-day | We're Live | Sarah | 2026-01-15 | News | Today we launch... | Full article text here... | true |
| roadmap-2026 | Our 2026 Roadmap | Mike | 2026-02-01 | Product | Here's what's coming... | Full article text here... | true |
| draft-post | Upcoming Feature | Sarah |  | Product |  | Work in progress... | false |

Fetch published posts:

```javascript
GET /api/v1/acme-website/blog?is_published=true
```

Draft posts stay invisible until someone changes `is_published` to `true` in the spreadsheet.

Caching for Performance

You might worry about loading speed if every page load hits an API. Jsonsheets handles this with built-in caching:

* **Server-side caching** — Repeated requests are served from cache, not from Google's servers
    
* **ETag support** — Your website can send conditional requests and get fast 304 responses when content hasn't changed
    
* **Configurable TTL** — Control how often the cache refreshes based on how frequently your content changes
    

For a typical marketing site where content changes a few times a week, the cache keeps things fast without serving stale content.

When a Spreadsheet CMS Makes Sense

**Great for:**

* Marketing sites and landing pages
    
* Portfolio websites
    
* Small business sites
    
* Event pages and one-pagers
    
* Client projects where the client manages their own content
    
* MVPs and prototypes
    
* Microsites and campaign pages
    

**Consider a dedicated CMS when:**

* You need rich text editing with embedded media
    
* Content has complex relationships (categories, tags, related posts)
    
* You have dozens of content editors with different permissions
    
* You need content versioning and approval workflows
    

The Advantages Over Traditional CMS Platforms

|  | Spreadsheet CMS | WordPress | Headless CMS |
| --- | --- | --- | --- |
| Setup time | 5 minutes | 30+ minutes | 1+ hours |
| Learning curve | None (it's a spreadsheet) | Medium | High |
| Monthly cost | From free | Hosting + plugins | $29-$300+/month |
| Backend to maintain | None | PHP + MySQL | Depends |
| Non-technical editors | Easy | Medium | Hard |
| API included | Yes | Needs plugin | Yes |

Get Started

Building a spreadsheet-powered CMS takes less than 10 minutes:

1. Structure your content in Google Sheets
    
2. Connect the sheet at [jsonsheets.com](https://jsonsheets.com)
    
3. Fetch content in your website via API
    
4. Hand the spreadsheet to your content team
    

No servers. No databases. No admin panels to build. Just a spreadsheet your team already knows how to use.

**Build your spreadsheet CMS at** [**jsonsheets.com**](https://jsonsheets.com)
