Install any skill in seconds. Free to start, no credit card required.
Get Started Free →You are an expert in Contentful, the API-first content platform for enterprise teams. You help developers integrate Contentful's Content Delivery API (CDN-backed, read), Content Management API (write), and Content Preview API (draft content) into websites and apps — using typed content models, localization, rich text rendering, image transformations, and webhooks for build triggers.
.claude/skills/terminalskills-contentful/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-08 | ✗→✓ | ▲ Improved | 255% | 0% |
| case-16 | ✓→✗ | ▼ Worse | 82% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 293% | 0% |
| case-13 | ✓→✓ | = Same ✓ | 164% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 148% | 0% |
You are an expert in Contentful, the API-first content platform for enterprise teams. You help developers integrate Contentful's Content Delivery API (CDN-backed, read), Content Management API (write), and Content Preview API (draft content) into websites and apps — using typed content models, localization, rich text rendering, image transformations, and webhooks for build triggers.
typescript// src/lib/contentful.ts — Contentful client setup import { createClient, type Entry, type Asset } from "contentful"; const client = createClient({ space: process.env.CONTENTFUL_SPACE_ID!, accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN!, // For draft preview: // accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN, // host: "preview.contentful.com", }); // TypeScript interfaces matching content model interface BlogPostFields { title: string; slug: string; excerpt: string; body: Document; // Rich Text featuredImage: Asset; author: Entry<AuthorFields>; tags: string[]; publishDate: string; } // Fetch entries with type safety async function getBlogPosts(limit = 10): Promise<Entry<BlogPostFields>[]> { const response = await client.getEntries<BlogPostFields>({ content_type: "blogPost", order: ["-fields.publishDate"], limit, include: 2, // Resolve 2 levels of linked entries "fields.slug[exists]": true, // Only entries with slug }); return response.items; } async function getBlogPostBySlug(slug: string) { const response = await client.getEntries<BlogPostFields>({ content_type: "blogPost", "fields.slug": slug, include: 3, limit: 1, }); return response.items[0] || null; }
tsx// src/components/RichTextRenderer.tsx — Render Contentful rich text import { documentToReactComponents, Options } from "@contentful/rich-text-react-renderer"; import { BLOCKS, INLINES, Document } from "@contentful/rich-text-types"; import Image from "next/image"; const renderOptions: Options = { renderNode: { [BLOCKS.EMBEDDED_ASSET]: (node) => { const { title, file } = node.data.target.fields; return ( <Image src={`https:${file.url}`} alt={title} width={file.details.image.width} height={file.details.image.height} className="rounded-lg my-6" /> ); }, [BLOCKS.EMBEDDED_ENTRY]: (node) => { const entry = node.data.target; if (entry.sys.contentType.sys.id === "codeBlock") { return ( <pre className="bg-gray-900 p-4 rounded-lg overflow-x-auto"> <code className={`language-${entry.fields.language}`}> {entry.fields.code} </code> </pre> ); } return null; }, [INLINES.HYPERLINK]: (node, children) => ( <a href={node.data.uri} className="text-blue-600 underline" target="_blank" rel="noopener"> {children} </a> ), }, }; export function RichText({ document }: { document: Document }) { return <div className="prose max-w-none">{documentToReactComponents(document, renderOptions)}</div>; }
tsx// Contentful Images API — resize, crop, format on the fly function contentfulImageUrl(asset: Asset, options?: { width?: number; height?: number; quality?: number; format?: "webp" | "avif" | "jpg" | "png"; fit?: "pad" | "fill" | "scale" | "crop" | "thumb"; focus?: "face" | "center" | "top" | "bottom"; }) { const url = new URL(`https:${asset.fields.file.url}`); if (options?.width) url.searchParams.set("w", String(options.width)); if (options?.height) url.searchParams.set("h", String(options.height)); if (options?.quality) url.searchParams.set("q", String(options.quality)); if (options?.format) url.searchParams.set("fm", options.format); if (options?.fit) url.searchParams.set("fit", options.fit); if (options?.focus) url.searchParams.set("f", options.focus); return url.toString(); } // Usage: auto-optimize for web <Image src={contentfulImageUrl(post.fields.featuredImage, { width: 800, format: "webp", quality: 80, })} alt={post.fields.title} />
bashnpm install contentful # Delivery SDK npm install @contentful/rich-text-react-renderer # Rich text → React npm install contentful-management # Management API (write)
contentful-typescript-codegen to generate TypeScript types from your content model@contentful/rich-text-react-renderer with custom renderers for embedded assets and entrieslocale: "de" or locale: "*" for all localesinclude: 2-3 to resolve linked entries; avoids N+1 queries for related content| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | pass→pass | 2,877 | 2,950 | +3% | 1 | 1 | 0% | 516 | 2,026 | +293% | 0 | 0 | — |
case-13 | pass→pass | 4,849 | 3,668 | -24% | 1 | 1 | 0% | 826 | 2,179 | +164% | 0 | 0 | — |
case-01 | pass→pass | 6,331 | 7,694 | +22% | 1 | 1 | 0% | 1,287 | 3,195 | +148% | 0 | 0 | — |
case-02 | pass→pass | 6,052 | 7,563 | +25% | 1 | 1 | 0% | 1,311 | 3,093 | +136% | 0 | 0 | — |
case-03 | pass→pass | 11,708 | 12,499 | +7% | 1 | 1 | 0% | 2,519 | 4,189 | +66% | 0 | 0 | — |
case-05 | pass→pass | 3,451 | 2,947 | -15% | 1 | 1 | 0% | 524 | 2,033 | +288% | 0 | 0 | — |
case-06 | pass→pass | 5,405 | 4,162 | -23% | 1 | 1 | 0% | 1,017 | 2,449 | +141% | 0 | 0 | — |
case-07 | pass→pass | 7,858 | 6,792 | -14% | 1 | 1 | 0% | 1,312 | 2,729 | +108% | 0 | 0 | — |
case-08 | fail→pass | 3,662 | 4,598 | +26% | 1 | 1 | 0% | 556 | 1,976 | +255% | 0 | 0 | — |
case-09 | pass→pass | 2,730 | 3,919 | +44% | 1 | 1 | 0% | 378 | 2,183 | +478% | 0 | 0 | — |
case-10 | pass→pass | 5,068 | 2,979 | -41% | 1 | 1 | 0% | 926 | 2,097 | +126% | 0 | 0 | — |
case-11 | pass→pass | 5,472 | 4,835 | -12% | 1 | 1 | 0% | 1,003 | 2,461 | +145% | 0 | 0 | — |
case-12 | pass→pass | 7,374 | 5,802 | -21% | 1 | 1 | 0% | 1,470 | 2,633 | +79% | 0 | 0 | — |
case-14 | pass→pass | 1,809 | 2,849 | +57% | 1 | 1 | 0% | 343 | 2,012 | +487% | 0 | 0 | — |
case-15 | pass→pass | 5,196 | 6,584 | +27% | 1 | 1 | 0% | 905 | 2,319 | +156% | 0 | 0 | — |
case-16 | pass→fail | 7,127 | 5,628 | -21% | 1 | 1 | 0% | 1,373 | 2,502 | +82% | 0 | 0 | — |
case-17 | pass→pass | 7,528 | 5,583 | -26% | 1 | 1 | 0% | 1,256 | 2,587 | +106% | 0 | 0 | — |
case-18 | pass→pass | 5,117 | 4,954 | -3% | 1 | 1 | 0% | 804 | 2,329 | +190% | 0 | 0 | — |
case-19 | pass→pass | 9,820 | 8,895 | -9% | 1 | 1 | 0% | 1,621 | 3,028 | +87% | 0 | 0 | — |
case-20 | pass→pass | 11,523 | 14,084 | +22% | 1 | 1 | 0% | 2,308 | 4,139 | +79% | 0 | 0 | — |
case-21 | pass→pass | 11,548 | 9,383 | -19% | 1 | 1 | 0% | 2,141 | 3,302 | +54% | 0 | 0 | — |
case-22 | pass→pass | 2,360 | 3,372 | +43% | 1 | 1 | 0% | 360 | 1,974 | +448% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.