The Content Index API is an experimental Web API that lets developers register offline-enabled web pages with the browser so users can more easily discover and reopen them later. It’s aimed at Progressive Web Apps that cache content for offline use—such as news sites saving articles in the background or apps making downloaded content available when the network is unavailable.
What it does
- Lets you add URLs and metadata for content that’s already available offline (within the scope of your current service worker).
- Enables the browser to potentially show those entries in a built-in UI for offline content.
- Also lets your web app query the index and present the same list to users inside your own interface.
How it fits into the platform
The API is an extension to service workers.
The entry point is ServiceWorkerRegistration.index (a ContentIndex instance).
Core methods:
- ContentIndex.add() — register an offline page with metadata (title, description, icons, category, etc.).
- ContentIndex.getAll() — retrieve all indexed entries for display/management.
- ContentIndex.delete() — remove an entry by its id.
Important constraints and best practices
- Indexed entries don’t automatically expire. You should provide a way to clear saved items and/or periodically prune older entries.
- The API supports indexing URLs that correspond to HTML documents. You generally can’t index a media file URL directly (like an .mp4); instead, index an offline-capable page that presents that media.
- It’s marked experimental on MDN—check browser compatibility carefully before relying on it in production.
The contentdelete event
A contentdelete event can be delivered to the service worker when the user agent removes indexed content via its own built-in UI. MDN notes this event does not fire when you remove items yourself using ContentIndex.delete(). This event is mainly useful for cleanup—e.g., deleting associated cached resources when the system removes an indexed entry.
Content Index API Demo
Content Index API Demo
Status: Checking support...
body {
font-family: sans-serif;
margin: 2em;
background-color: #f4f4f4;
color: #333;
}
#logs {
background: #f4f4f4;
padding: 10px;
margin-top: 20px;
}
// Standard cache setup
self.addEventListener('install', (event) => {
// In a real app, you MUST cache the URL you intend to index.
// The browser may refuse to index content if it detects it isn't cached.
const urlsToCache = [
'./',
'./index.html',
'./contentIndex_API.js',
'./article.html',
'./icon.svg'
];
event.waitUntil(
caches.open('demo-cache-v1').then((cache) => {
return cache.addAll(urlsToCache);
})
);
self.skipWaiting();
});
// 6. HANDLING SYSTEM DELETION
// This event fires if the USER deletes the content from the
// Browser/System UI (not from your web app's UI).
self.addEventListener('contentdelete', (event) => {
console.log(`Content deleted by user: ${event.id}`);
// Use this opportunity to clean up related cache data
event.waitUntil(
caches.open('demo-cache-v1').then((cache) => {
// Example: If the user deletes the index entry,
// we might want to remove the actual file from cache.
// return cache.delete('/article.html');
})
);
});
// HELPER: Simple logger for the demo
const log = (msg) => {
const logEl = document.getElementById('logs');
logEl.textContent += `> ${msg}\n`;
console.log(msg);
};
// 1. REGISTER SERVICE WORKER
const init = async () => {
if (!('serviceWorker' in navigator)) {
document.getElementById('status').textContent =
'Service Worker not supported.';
return;
}
try {
const registration = await navigator.serviceWorker.register('sw.js');
await navigator.serviceWorker.ready;
// 2. FEATURE DETECTION
if (!('index' in registration)) {
document.getElementById('status').textContent =
'Content Index API NOT supported in this browser.';
return;
}
document.getElementById('status').textContent = 'Ready. API Supported.';
document.getElementById('controls').style.display = 'block';
document.getElementById('btn-add').onclick = () => addToIndex(registration);
document.getElementById('btn-check').onclick = () =>
checkIndex(registration);
document.getElementById('btn-remove').onclick = () =>
removeFromIndex(registration);
} catch (error) {
console.error('SW Registration failed', error);
}
};
// 3. ADDING CONTENT TO THE INDEX
const addToIndex = async (registration) => {
try {
await registration.index.add({
// Unique ID for this item
id: 'article-123',
// The URL to launch. Must match the cached file.
url: './article.html',
// Display metadata
title: 'Offline Article Demo',
description:
'A sample article demonstrating offline content indexing capabilities.',
category: 'article',
icons: [
{
src: './icon.svg', // Points to local file
sizes: '128x128',
type: 'image/svg+xml' // Correct MIME type for SVG
}
]
});
log('Success: "article-123" added to Content Index.');
} catch (e) {
log(`Error adding to index: ${e.message}`);
}
};
// 4. RETRIEVING INDEXED CONTENT
const checkIndex = async (registration) => {
const entries = await registration.index.getAll();
log(`Current Index Count: ${entries.length}`);
for (const entry of entries) {
log(` - Found: ${entry.title} (ID: ${entry.id})`);
}
};
// 5. REMOVING CONTENT
const removeFromIndex = async (registration) => {
await registration.index.delete('article-123');
log('Success: "article-123" removed from Content Index.');
};
init();

