ContentGecko CMS
Implementation guide for connecting a custom website to Gecko CMS, ContentGecko's hosted headless CMS for blog content.
This guide explains how to connect a custom website to Gecko CMS, ContentGecko’s hosted headless CMS for blog content.
Gecko CMS is a good fit when:
- You have a custom-built site (Astro, Next.js, Nuxt, SvelteKit, etc.)
- You want ContentGecko to handle content publishing
- You only need a read API on your side — no custom write endpoints
If you prefer to host content yourself and expose create/update APIs to ContentGecko, use the Custom CMS integration instead.
How it works
ContentGecko → Publish article → Gecko CMS storage → Read API → Your website → Render HTML → Visitors
- Your team writes and approves articles in ContentGecko.
- When an article is uploaded/published, ContentGecko syncs it into Gecko CMS.
- Your website fetches published posts from the public read API and renders them.
You do not build POST/PUT endpoints. ContentGecko owns the write path.
Part 1 — ContentGecko setup
These steps are done in the ContentGecko app by someone with access to the content plan.
1. Select Gecko CMS as the primary CMS
- Open Content plan settings.
- Go to the Integrations tab.
- Under Primary CMS Integration, choose Gecko CMS.
- Click Select on the Gecko CMS integration card if prompted.
2. Create a Gecko CMS site
- In the Gecko CMS website setup section, click Create Gecko CMS site.
- ContentGecko creates:
- A site key — public identifier for your site
- An API key (read token) — shown once when the site is created
Copy these values and share them securely with your website developer.
3. Generate or rotate the API key
API keys are only shown when generated. If your developer does not have a key:
- Click Generate API key.
- Copy the key immediately — it will not be shown again.
Store the key server-side only (see Security).
4. Publish content from ContentGecko
Once Gecko CMS is selected as the primary CMS, upload articles from ContentGecko as usual (article editor → Upload). ContentGecko syncs the article into Gecko CMS automatically.
Translated articles are supported. Upload translations from ContentGecko; each language is stored separately and fetched with the lang query parameter.
Part 2 — Website integration
These steps are for your developer.
Credentials
After setup, ContentGecko shows:
| Value | Description |
|---|---|
| Public API base URL | Base URL for read requests, ending in /cms/public |
| Site key | Identifies your CMS site |
| API key | Bearer token for authenticated reads |
Example shape (your values will differ):
Public API base URL: https://<your-api-host>/cms/public
Site key: a1b2c3d4e5f6...
API key: <64-character hex token>
Environment variables
Add these to your hosting environment (Vercel, Netlify, AWS, etc.). Do not expose the API key in client-side JavaScript.
CONTENTGECKO_CMS_API_URL=https://<your-api-host>/cms/public
CONTENTGECKO_CMS_SITE=<your-site-key>
CONTENTGECKO_CMS_TOKEN=<your-api-key>
The exact API URL is shown in ContentGecko under Gecko CMS website setup.
Public read API
Authentication
Every request must include:
Authorization: Bearer <your-api-key>
Missing or invalid tokens return 401 Unauthorized.
List posts
GET /posts?site=<site-key>&lang=<lang>&limit=<limit>&offset=<offset>
| Query param | Required | Default | Description |
|---|---|---|---|
site | Yes | — | Your site key |
lang | No | Site default language | ISO language code (e.g. en, es, et) |
limit | No | 20 | Page size (max 100) |
offset | No | 0 | Pagination offset |
Example request:
curl -s \
-H "Authorization: Bearer $CONTENTGECKO_CMS_TOKEN" \
"$CONTENTGECKO_CMS_API_URL/posts?site=$CONTENTGECKO_CMS_SITE&lang=en&limit=20&offset=0"
Example response:
{
"items": [
{
"slug": "how-to-optimize-seo",
"title": "How to Optimize Your SEO Strategy",
"metaDescription": "Practical SEO tips for growing organic traffic.",
"featuredImageUrl": "https://cdn.example.com/hero.webp",
"publishedAt": "2026-05-01T09:00:00.000Z",
"updatedAt": "2026-05-01T10:00:00.000Z",
"lang": "en",
"urlPath": "/blog/how-to-optimize-seo"
}
],
"meta": {
"limit": 20,
"offset": 0
}
}
List responses intentionally exclude article body content. Use the detail endpoint for full content.
Get one post
GET /posts/{slug}?site=<site-key>&lang=<lang>
The {slug} value comes from the list response. URL-encode slugs that contain special characters.
Example request:
curl -s \
-H "Authorization: Bearer $CONTENTGECKO_CMS_TOKEN" \
"$CONTENTGECKO_CMS_API_URL/posts/how-to-optimize-seo?site=$CONTENTGECKO_CMS_SITE&lang=en"
Example response:
{
"slug": "how-to-optimize-seo",
"title": "How to Optimize Your SEO Strategy",
"metaDescription": "Practical SEO tips for growing organic traffic.",
"featuredImageUrl": "https://cdn.example.com/hero.webp",
"publishedAt": "2026-05-01T09:00:00.000Z",
"updatedAt": "2026-05-01T10:00:00.000Z",
"lang": "en",
"urlPath": "/blog/how-to-optimize-seo",
"contentHtml": "<p>Introduction...</p><h2>Key tips</h2><p>...</p>",
"contentMarkdown": "Introduction...\n\n## Key tips\n\n..."
}
| Field | List | Detail | Notes |
|---|---|---|---|
slug | ✓ | ✓ | URL slug |
title | ✓ | ✓ | Page title |
metaDescription | ✓ | ✓ | SEO meta description |
featuredImageUrl | ✓ | ✓ | Hero/featured image URL |
publishedAt | ✓ | ✓ | Publication timestamp |
updatedAt | ✓ | ✓ | Last update timestamp |
lang | ✓ | ✓ | Language code |
urlPath | ✓ | ✓ | Suggested public path (see Routing) |
contentHtml | — | ✓ | Render-ready HTML body |
contentMarkdown | — | ✓ | Original markdown (optional to use) |
Error responses
Errors return JSON with a message field:
{
"message": "CMS post not found"
}
| Status | Meaning |
|---|---|
400 | Missing or invalid query parameter |
401 | Missing or invalid API key |
404 | Site or post not found |
Framework examples
Shared fetch helper
const cmsFetch = async (path) => {
const response = await fetch(`${process.env.CONTENTGECKO_CMS_API_URL}${path}`, {
headers: {
Authorization: `Bearer ${process.env.CONTENTGECKO_CMS_TOKEN}`
}
})
if (!response.ok) {
throw new Error(`CMS request failed: ${response.status}`)
}
return response.json()
}
export const listBlogPosts = ({ limit = 20, offset = 0, lang = 'en' } = {}) => {
const site = process.env.CONTENTGECKO_CMS_SITE
const params = new URLSearchParams({ site, lang, limit: String(limit), offset: String(offset) })
return cmsFetch(`/posts?${params}`)
}
export const getBlogPost = ({ slug, lang = 'en' }) => {
const site = process.env.CONTENTGECKO_CMS_SITE
const params = new URLSearchParams({ site, lang })
return cmsFetch(`/posts/${encodeURIComponent(slug)}?${params}`)
}
Use this helper only in server-side code (API routes, SSR, build-time data fetching).
Astro
Blog index — fetch the list at build time or on the server:
---
import { listBlogPosts } from '../lib/contentgecko-cms'
const { items } = await listBlogPosts({ lang: 'en' })
---
<ul>
{items.map(post => (
<li>
<a href={post.urlPath}>{post.title}</a>
</li>
))}
</ul>
Article page — render HTML server-side:
---
import { getBlogPost } from '../lib/contentgecko-cms'
const { slug } = Astro.params
const post = await getBlogPost({ slug, lang: 'en' })
---
<article>
<h1>{post.title}</h1>
<Fragment set:html={post.contentHtml} />
</article>
Set page metadata from post.title, post.metaDescription, post.featuredImageUrl, and post.publishedAt.
Next.js (App Router)
// app/blog/[slug]/page.tsx
import { getBlogPost } from '@/lib/contentgecko-cms'
export default async function BlogPostPage ({ params }: { params: { slug: string } }) {
const post = await getBlogPost({ slug: params.slug, lang: 'en' })
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.contentHtml }} />
</article>
)
}
Fetch in Server Components or Route Handlers only — not in client components.
Static site generators
For SSG (Eleventy, Hugo wrappers, etc.), fetch posts at build time using the same API. Rebuild or use ISR/webhooks when new content is published.
Routing and URLs
Each post includes a urlPath field. By default this looks like:
/blog/{slug}
Use urlPath for internal links on your blog index. Map your site routes to match — for example, an Astro or Next.js route at /blog/[slug] should call getBlogPost({ slug }).
If your blog lives at a different path (e.g. /articles/...), configure that when creating the Gecko CMS site in ContentGecko, or align your frontend routes with the returned urlPath.
Multilingual sites
Gecko CMS stores each language as a separate post record.
- Upload the default-language article from ContentGecko.
- Upload translations from ContentGecko’s translation workflow.
- On your website, pass the correct
langquery parameter when listing or fetching posts.
Example — Spanish blog index:
GET /posts?site=<site-key>&lang=es&limit=20&offset=0
Build separate index/detail routes per locale, or one dynamic route that reads the locale from the URL and passes it to the API.
Caching
Responses include cache headers:
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=600
ETag: "<hash>"
Recommendations:
- SSG / build-time: Cache aggressively at build; rebuild when content changes.
- SSR: Respect
Cache-Controlon your CDN or origin. - Conditional requests: Send
If-None-Match: <etag>on repeat fetches; the API returns304 Not Modifiedwhen content is unchanged.
Security
| Do | Don’t |
|---|---|
| Store the API key in server environment variables | Put the API key in browser JavaScript |
| Fetch CMS data in SSR, API routes, or at build time | Call the CMS API directly from the browser |
| Rotate the API key if it is exposed | Commit keys to git |
| Share keys through a password manager | Send keys in plain email if avoidable |
The public read API requires authentication. Treat the API key like a secret even though it only grants read access to published content.
Content format
- Body: ContentGecko publishes
contentHtmlas ready-to-render HTML. Prefer this for display. - Markdown:
contentMarkdownis also available if you want to process content yourself. - Featured image: Provided as a full URL in
featuredImageUrl. It is not duplicated at the top of the HTML body. - Meta: Use
metaDescriptionfor<meta name="description">and Open Graph tags.
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
401 Unauthorized | Missing/wrong API key | Check Authorization: Bearer ... header and env var |
404 CMS site not found | Wrong site key | Copy site key from ContentGecko settings |
404 CMS post not found | Wrong slug or language | Verify slug and lang match a published post |
| Empty blog after upload | Article not uploaded yet | Upload/publish from ContentGecko first |
| Stale content on site | CDN or build cache | Rebuild site or purge CDN cache |
| CORS errors in browser | Client-side fetch | Move fetch to server-side code |
Implementation checklist
ContentGecko (marketing / content team)
- Gecko CMS selected as primary CMS integration
- Gecko CMS site created
- Site key and API key shared securely with developer
- Test article uploaded from ContentGecko
Website (developer)
- Environment variables configured on hosting platform
- Server-side fetch helper implemented
- Blog index page lists posts from list endpoint
- Article detail page renders
contentHtml - Routes match
urlPath(default/blog/{slug}) - Page title, meta description, and OG tags set from post fields
- Multilingual
langparameter wired up (if applicable) - API key kept out of client bundles
- Caching / rebuild strategy defined for new content
Support
For ContentGecko account, publishing, or translation questions, contact your ContentGecko representative.
For website integration questions, share this document with your developer and the credentials from Content plan settings → Integrations → Gecko CMS website setup.