Guide to Headless CMS for those that usually work with HTML, CSS and Vanilla JS
I hear your frustration, and you're absolutely right – there's a gap in documentation for folks who want to use Sanity with just vanilla HTML, CSS, and JavaScript. You're not unskilled at all; the ecosystem has indeed focused heavily on framework-specific guides, and that makes it harder to see the simple path. Let me give you a straightforward explanation of how to set this up for a basic portfolio.
The Two-Part Setup
Sanity splits into two pieces:
- Sanity Studio - where you manage/edit content (this needs Node.js for initial setup)
- Your actual website - which can be pure HTML/CSS/JS
Step 1: Set Up Studio (One-Time Thing)
You'll need Node.js installed just to create the Studio. Run:
npm create sanity@latestFollow the prompts - it'll ask you to create a project, choose a schema template (pick "blog" or "clean" for simplicity), and deploy. This gives you a Studio at yourproject.sanity.studio where you'll add your portfolio projects, blog posts, or whatever content you want.
Step 2: Fetch Content with Plain JavaScript
This is the part that should be simpler to find! Once you have content in your Studio, you can fetch it from any HTML page using Sanity's Query API. It's just a regular HTTP endpoint that returns JSON.
Here's a complete working example:
<!DOCTYPE html>
<html>
<head>
<title>My Portfolio</title>
<style>
.project { margin: 20px; padding: 20px; border: 1px solid #ccc; }
</style>
</head>
<body>
<h1>My Projects</h1>
<div id="projects"></div>
<script>
// Get these from your Sanity project settings
const projectId = 'YOUR_PROJECT_ID';
const dataset = 'production';
// GROQ query (Sanity's query language - similar to SQL)
const query = '*[_type == "project"]';
const encodedQuery = encodeURIComponent(query);
const url = `https://${projectId}.api.sanity.io/v2021-10-21/data/query/${dataset}?query=${encodedQuery}`;
fetch(url)
.then(response => response.json())
.then(data => {
const projects = data.result;
const container = document.getElementById('projects');
projects.forEach(project => {
const div = document.createElement('div');
div.className = 'project';
div.innerHTML = `
<h2>${project.title}</h2>
<p>${project.description || ''}</p>
`;
container.appendChild(div);
});
})
.catch(error => console.error('Error fetching projects:', error));
</script>
</body>
</html>Handling Images
Images in Sanity need special URL construction. When you get an image reference from Sanity, it looks like image-abc123-1200x800-jpg. You can build the URL manually:
function buildImageUrl(imageAsset, projectId, dataset) {
const ref = imageAsset.asset._ref;
// ref format: "image-{id}-{width}x{height}-{format}"
const parts = ref.split('-');
const id = parts[1];
const dimensions = parts[2];
const format = parts[3];
return `https://cdn.sanity.io/images/${projectId}/${dataset}/${id}-${dimensions}.${format}`;
}Or you can use the @sanity/image-url package if you want transformations (resizing, cropping, format conversion), though that requires installing via NPM. For a truly vanilla setup, the manual URL building above works fine.
Why This Feels Hidden
You're right that this should be more prominent. The reason framework guides dominate is:
- Sanity Studio itself is built with React (but you only use it as an editor, not in your code)
- Frameworks handle image optimization and live preview features automatically
- Most tutorials assume you're building a "modern" app with a build step
But the core truth: Sanity is just an HTTP API that returns JSON. You can absolutely use it with vanilla JavaScript.
What You Need to Know
- Your project ID (found in Sanity project settings)
- GROQ - Sanity's query language (it's like SQL but for JSON)
- The Query API endpoint format:
https://{projectId}.api.sanity.io/v2021-10-21/data/query/{dataset}?query={your-query}
That's it! No React, no Next.js, no build tools needed for your actual website.
A Quick GROQ Primer
*[_type == "project"]- Get all documents of type "project"*[_type == "project"][0..5]- Get first 6 projects*[_type == "project"]{title, description, image}- Only return specific fields
The Studio setup is the only "complex" part, and even that's just running one command. Once that's done, you're just making fetch requests to get JSON data – exactly what you'd do with any other API.
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.