> ## Documentation Index
> Fetch the complete documentation index at: https://docs.siftly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Strapi Integration

> Connect Siftly to your Strapi CMS and publish GEO-optimized content directly to your collections via the Strapi REST API.

## Overview

This guide is for Strapi users who want to publish Siftly-generated content and apply GEO/SEO field recommendations (meta tags, JSON-LD, keywords) directly to their Strapi collections. After following it, you'll be able to push optimized content with full SEO metadata into your Strapi schema and render it correctly on your frontend.

**Difficulty:** ⚠️ Some technical setup — Strapi is headless, so you control the schema and frontend rendering.

Strapi is an open-source headless CMS that lets you design your own content types and serve them through a REST or GraphQL API. The Siftly–Strapi integration pushes content directly into your collection using the Strapi REST API, respecting your schema's field names and Draft & Publish settings.

<Frame>
  <img src="https://mintcdn.com/siftly/4tAGLoP6M7hAzxgx/images/strapi-connection.svg?fit=max&auto=format&n=4tAGLoP6M7hAzxgx&q=85&s=d23a3bd7c77db4959bc6570469b89829" alt="Siftly to Strapi publish flow — content editor to REST API to live entry" className="rounded-xl" width="900" height="280" data-path="images/strapi-connection.svg" />
</Frame>

This integration is built for **Strapi 5** (self-hosted and Strapi Cloud). Strapi 4 instances will also work for basic fields, but new features like the `?status=` query parameter and `documentId` responses require Strapi 5.

***

## Prerequisites

Before connecting, you'll need:

* A running Strapi 5 instance — either Strapi Cloud or a self-hosted deployment reachable over the public internet
* Admin access to the Strapi dashboard so you can create an API token
* A **collection type** with at least a title, slug and content/body field (e.g. the default `articles` collection)

<Note>
  Local Strapi instances on `http://localhost:1337` can only be used by Siftly when running Siftly in development mode. Production Siftly requires an HTTPS endpoint reachable from the internet, such as `https://cms.yourdomain.com`.
</Note>

***

## Step 1: Create a Strapi API token

Siftly authenticates with Strapi using a **server-to-server API token** — no OAuth involved. You generate this token once inside the Strapi admin panel.

1. Log in to your Strapi admin panel (`https://your-strapi-host/admin`)
2. Go to **Settings -> Global Settings -> API Tokens**
3. Click **Create new API Token**
4. Fill out the form:
   * **Name**: `Siftly` (or anything memorable)
   * **Description**: *optional*
   * **Token duration**: **Unlimited** is recommended so publishing doesn't break when the token silently expires
   * **Token type**: **Full access** (recommended) or **Custom** (see below)
5. Click **Save**
6. Copy the token shown on the confirmation screen — **Strapi only displays it once**

<Warning>
  Strapi shows the token only on the creation screen. If you navigate away before copying it, you'll need to regenerate the token. Store it in a password manager.
</Warning>

### Required permissions

| Siftly action              | Strapi permission                  | API call                                       |
| -------------------------- | ---------------------------------- | ---------------------------------------------- |
| Validate the connection    | `find` on your collection          | `GET /api/{collection}?pagination[pageSize]=1` |
| Publish content            | `create` on your collection        | `POST /api/{collection}?status=published`      |
| Save as draft              | `create` on your collection        | `POST /api/{collection}?status=draft`          |
| Publish now (D\&P enabled) | `publish` on your collection       | part of the same `POST` above                  |
| Upload images              | `upload` on Upload (Media Library) | `POST /api/upload`                             |

#### Option A — Full access (easiest)

Select **Full access** when creating the token. This grants every permission on every content type, including the Upload (Media Library) API.

#### Option B — Custom token (least privilege)

1. Set **Token type** to **Custom**
2. Expand your collection (e.g. `Article`)
3. Tick: `find`, `create`, and `publish` (if Draft & Publish is enabled)
4. Expand **Upload** (Media Library)
5. Tick: `upload` — required so Siftly can upload hero images and inline content images to your Strapi Media Library

<Warning>
  If the **Upload** permission is missing, Siftly can still publish text content, but images will reference external URLs instead of being hosted in your Strapi Media Library. For best results, always grant the `upload` permission.
</Warning>

***

## Step 2: Connect Strapi in Siftly

1. In Siftly, go to **Settings -> Integrations**
2. Click **Connect** next to **Strapi**
3. Fill in:

| Field         | Description                                                                  | Example                     |
| ------------- | ---------------------------------------------------------------------------- | --------------------------- |
| **Base URL**  | Root URL of your Strapi instance. No trailing slash, no `/admin`, no `/api`. | `https://cms.mycompany.com` |
| **API Token** | The Full access or custom token from Step 1.                                 | `f0a1...`                   |

4. Click **Next**

Siftly validates that the URL is reachable and attempts to discover available content types. If discovery succeeds, you'll see a dropdown of collection types in the next step.

***

## Step 3: Schema Mapping

### Collection Type Selection

After credentials are validated, select which collection to publish into:

* **Dropdown** (if Siftly discovered your collections): Select from the list
* **Text input** (if discovery wasn't possible with your token): Enter the **plural API ID** (e.g., `articles`)

Find the API ID in Strapi under **Content-Type Builder -> your type -> Advanced Settings -> API ID (Plural)**.

### Content Field Format

Strapi 5 has two common rich-text field types:

| Format                 | When to pick it                                                                   | What Siftly sends  |
| ---------------------- | --------------------------------------------------------------------------------- | ------------------ |
| **HTML / Markdown**    | Content field is `Text (long)`, `Rich text (Markdown)`, or any plain-string field | Raw HTML string    |
| **Rich text (Blocks)** | Content field is the Strapi 5 **Rich text (Blocks)** type                         | Strapi Blocks JSON |

### Field Mapping

Siftly discovers your collection's fields by fetching a sample document. Fields inside **Strapi components** are recursively flattened into dot-notation paths (e.g., `seo.metaTitle`).

Select fields from the dropdown for each mapping:

#### Required Fields

| Siftly Field | Default Strapi Name | Notes                                   |
| ------------ | ------------------- | --------------------------------------- |
| **Title**    | `title`             | Plain text                              |
| **Slug**     | `slug`              | Typically a Strapi **UID** field        |
| **Body**     | `content`           | Format controlled by the selector above |

#### Optional Fields

| Siftly Field         | Typical Strapi Name   | Field Type                           |
| -------------------- | --------------------- | ------------------------------------ |
| **Meta Title**       | `seo.metaTitle`       | Text, inside an SEO component        |
| **Meta Description** | `seo.metaDescription` | Text (long), inside an SEO component |
| **Keywords**         | `keywords`            | JSON array of strings                |
| **Categories**       | `categories`          | JSON array of strings                |
| **JSON-LD**          | `jsonLd`              | JSON field or Text (long)            |
| **Word Count**       | `wordCount`           | Number (integer)                     |
| **Hero Image**       | `cover`               | Image/media field                    |

#### JSON-LD Type Toggle

When a JSON-LD field is mapped, a **text/json** toggle appears:

* **json** (default): Sends as a parsed JSON object (Strapi JSON fields accept this natively)
* **text**: Sends as a serialized JSON string

#### Dot Notation for Components

Strapi Components live under a named key. Siftly supports dot notation:

```
seo.metaTitle        -> data.seo.metaTitle
seo.metaDescription  -> data.seo.metaDescription
```

***

## Custom Fields

Add extra fields your Strapi collection requires that aren't part of Siftly's standard schema.

### Field Types

| Type    | Description                  | Example                           |
| ------- | ---------------------------- | --------------------------------- |
| String  | Plain text                   | Author name, subtitle             |
| Number  | Numeric value                | Priority, reading time            |
| Boolean | True/false                   | Featured post, comments enabled   |
| JSON    | Structured object            | SEO config, social media metadata |
| Select  | Pick from predefined options | Category, status                  |

### Select Options with Separate Labels and Values

When adding a **Select** custom field, you define the available options in a textarea. Each option can have a separate **value** (sent to CMS) and **label** (shown in the dropdown):

```
value:label
```

**Example — Labels differ from values:**

```
1:Health
2:Tech
3:Food
4:Fashion
```

The dropdown shows `Health`, `Tech`, etc., but Siftly sends `1`, `2`, etc. to the CMS API.

**Example — Labels same as values:**

```
food
tech
health
fashion
```

When no `:` is present, the option text is used as both the label and value. This is backward-compatible with existing configurations.

<Tip>
  Use the `value:label` format when your CMS expects specific IDs, slugs, or enum values that differ from the human-readable names shown to content editors.
</Tip>

### Use Existing Field (Source Fields)

Link a custom field to auto-populate from existing content data:

* **Title**, **Slug**, **Meta Title**, **Meta Description**, **Excerpt**
* **Keywords**, **Categories**, **Word Count**
* **JSON-LD**, **Hero Image**
* **Date Published**, **Date Modified** (from JSON-LD graph)

### Editing Custom Fields

Click the **pencil icon** on any existing custom field to edit its configuration inline.

***

## Publishing

### Draft vs Published

| Siftly Status | Strapi Behavior                            | Visible on public site?      |
| ------------- | ------------------------------------------ | ---------------------------- |
| **Published** | Document created and immediately published | Yes                          |
| **Draft**     | Document created as a draft                | No — publish in Strapi Admin |

### Custom Fields at Publish Time

Fields with **Use Existing Field** show as "Auto-populated from \[source]" — no input needed. Manual fields appear pre-filled and editable.

### Idempotency

Every publish creates a new entry. Re-publishing sends a fresh `POST` and Strapi assigns a new `documentId`.

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="401 Invalid Strapi API token">
    Regenerate a **Full access** token and paste the complete token.
  </Accordion>

  <Accordion title="403 Insufficient permissions">
    Your token lacks `find`, `create`, or `publish`. Switch to Full access or add permissions.
  </Accordion>

  <Accordion title="404 Collection not found">
    The collection type must be the **plural API ID** (e.g., `articles`, not `Article`).
  </Accordion>

  <Accordion title="400 &#x22;must be an array&#x22; or &#x22;must be a string&#x22;">
    Content-field format mismatch. Switch between **HTML / Markdown** and **Rich text (Blocks)** in the mapping step.
  </Accordion>

  <Accordion title="Fields with Use Existing show empty values">
    This was fixed — ensure you're on the latest version. Fields with source\_field are auto-populated server-side at publish time.
  </Accordion>
</AccordionGroup>

***

## Where to apply Siftly's recommendations on Strapi

Strapi is headless — it stores data but doesn't render HTML. You control where SEO fields live in your schema and how your frontend renders them. Here's the recommended setup.

### Recommended: Use the `@strapi/plugin-seo` plugin

Strapi has an official SEO plugin that adds a reusable **SEO component** to your content types:

```bash theme={null}
npm install @strapi/plugin-seo
# or
yarn add @strapi/plugin-seo
```

After installing, add the SEO component to your article collection type in the Content-Type Builder. This gives you:

* `seo.metaTitle` — Meta title field
* `seo.metaDescription` — Meta description field
* `seo.metaImage` — OG image (media field)
* `seo.keywords` — Keywords
* `seo.canonicalURL` — Canonical URL override
* `seo.structuredData` — JSON-LD structured data (JSON field)

Siftly maps directly to these fields using dot notation (e.g., `seo.metaTitle`).

### Meta Title

**Schema location:** `seo.metaTitle` (via SEO plugin) or a custom `metaTitle` text field.

**Frontend rendering (Next.js example):**

```tsx theme={null}
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
  const article = await getArticle(params.slug);
  return {
    title: article.seo?.metaTitle || article.title,
  };
}
```

### Meta Description

**Schema location:** `seo.metaDescription` (via SEO plugin) or a custom `metaDescription` text field.

**Frontend rendering:**

```tsx theme={null}
export async function generateMetadata({ params }) {
  const article = await getArticle(params.slug);
  return {
    title: article.seo?.metaTitle || article.title,
    description: article.seo?.metaDescription || article.excerpt,
  };
}
```

### Open Graph Image

**Schema location:** `seo.metaImage` (media field via SEO plugin) or a custom image URL text field.

<Note>
  Siftly uploads hero images to your Strapi Media Library and links them to media fields automatically. Map your hero image field to `cover` or `seo.metaImage` and Siftly handles the upload and relation linking at publish time.
</Note>

### Canonical URL

**Schema location:** `seo.canonicalURL` or a custom `canonicalUrl` text field.

**Frontend rendering:**

```tsx theme={null}
export async function generateMetadata({ params }) {
  const article = await getArticle(params.slug);
  return {
    alternates: {
      canonical: article.seo?.canonicalURL || `/blog/${article.slug}`,
    },
  };
}
```

### Tags & Categories

**Schema location:** Use a JSON array field (`keywords`) or a Relation field linking to a Tags collection.

Siftly pushes keywords as a JSON array of strings. Your frontend can render them as `<meta name="keywords">` or use them for internal filtering/navigation.

### JSON-LD Structured Data

**Schema location:** `seo.structuredData` (JSON field via SEO plugin) or a custom `jsonLd` text (long) field.

Siftly pushes the full JSON-LD object as a stringified JSON value. Your frontend renders it in the `<head>`:

```tsx theme={null}
// app/blog/[slug]/page.tsx
export default async function ArticlePage({ params }) {
  const article = await getArticle(params.slug);
  
  return (
    <>
      {article.seo?.structuredData && (
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{
            __html: JSON.stringify(article.seo.structuredData),
          }}
        />
      )}
      <article>{/* content */}</article>
    </>
  );
}
```

### Keywords

**Schema location:** `keywords` JSON array field, or `seo.keywords` if using the SEO plugin.

**Frontend rendering:**

```html theme={null}
<meta name="keywords" content={article.keywords?.join(', ')} />
```

***

## Pushing Siftly recommendations automatically

<Steps>
  <Step title="Generate content in Siftly">
    Siftly produces your article plus meta title, meta description, keywords, and JSON-LD.
  </Step>

  <Step title="Click Publish → Strapi">
    Select your Strapi collection. Choose Draft or Published status.
  </Step>

  <Step title="All mapped fields pushed in one call">
    Siftly sends a single `POST /api/{collection}` with all mapped fields — content body, SEO component fields, keywords, JSON-LD — in one API call.
  </Step>

  <Step title="Frontend picks up new content">
    Your frontend (Next.js, Nuxt, Gatsby, etc.) fetches the new entry on the next request or build. All SEO metadata renders from the populated fields.
  </Step>
</Steps>

***

## Platform-specific quirks & limitations

<Warning>
  **Nothing is automatic.** Strapi is headless — it stores data but renders nothing. Every meta tag, JSON-LD block, and OG tag must be explicitly rendered by your frontend code. If your frontend doesn't read and render `seo.metaTitle`, it won't appear in HTML regardless of what Siftly pushes to Strapi.
</Warning>

<Note>
  **SEO plugin version:** The `@strapi/plugin-seo` component structure may vary between Strapi 4 and Strapi 5. Verify field paths in your Content-Type Builder after installation.
</Note>

* **Media uploads:** Siftly uploads hero images and inline content images to your Strapi Media Library automatically at publish time. The `upload` permission on the API token is required (included in Full access tokens).
* **Relation fields:** If your categories/tags are a separate collection type with Relations, Siftly cannot create or link relations automatically. Use JSON array fields instead.
* **Component validation:** If your SEO component has required fields (e.g., `metaTitle` is required), Siftly must map and populate them — otherwise the publish will fail with a 400 validation error.
* **Draft & Publish:** When enabled, entries created as "Published" are immediately visible to your frontend's published-content queries. Draft entries require manual publish in Strapi Admin.
* **Incremental Static Regeneration:** If your frontend uses ISR (Next.js) or similar, new content may take up to your revalidation period to appear. Trigger a revalidation webhook from Strapi for instant updates.

***

## Validating the setup

After publishing your first entry from Siftly:

<Steps>
  <Step title="Check the entry in Strapi Admin">
    Open your Strapi Admin → collection → find the new entry. Verify all fields (title, slug, body, SEO component, JSON-LD) are populated correctly.
  </Step>

  <Step title="View the live page source">
    Open the rendered page on your frontend. Right-click → **View Page Source**. Search for:

    * `<title>` — should contain your meta title value from Strapi
    * `<meta name="description"` — should contain your meta description
    * `<script type="application/ld+json">` — should contain your JSON-LD
  </Step>

  <Step title="Run Google Rich Results Test">
    Go to [Google Rich Results Test](https://search.google.com/test/rich-results) and paste your page URL. Confirm JSON-LD is detected and valid.
  </Step>

  <Step title="Run Schema.org Validator">
    Go to [Schema.org Validator](https://validator.schema.org/) for additional validation.
  </Step>
</Steps>

***

## Difficulty & setup recap

<Card title="Strapi Setup Summary" icon="database">
  | Aspect                  | Rating                                                        |
  | ----------------------- | ------------------------------------------------------------- |
  | **Initial setup**       | ⚠️ Medium — API token + schema design + field mapping         |
  | **Applying SEO fields** | ⚠️ Requires `@strapi/plugin-seo` or custom schema design      |
  | **JSON-LD**             | ⚠️ Stored in Strapi, but frontend must render it explicitly   |
  | **Ongoing editing**     | ✅ Easy — publish from Siftly, content appears in Strapi Admin |
  | **Developer needed?**   | Yes — for initial schema setup and frontend rendering code    |
</Card>

***

## Related

<CardGroup cols={2}>
  <Card title="CMS Integrations Overview" icon="grid-2" href="/integrations/overview">
    Compare all supported platforms and the unified field mapping system.
  </Card>

  <Card title="Quickstart Guide" icon="rocket" href="/quickstart">
    Set up your brand and run your first GEO analysis.
  </Card>

  <Card title="Content Generation" icon="file-pen" href="/features/content-generation">
    How Siftly generates GEO-optimized content and recommendations.
  </Card>

  <Card title="Choosing a CMS for AI Visibility" icon="book" href="https://siftly.ai/blog/cms-and-ai-visibility">
    Our guide to picking the right CMS for GEO optimization.
  </Card>
</CardGroup>
